mirror of
https://gitlab.com/famedly/conduit.git
synced 2025-08-11 17:50:59 +00:00
feat: MSC4289, Explicitly privilege room creators (1/2)
This commit is contained in:
parent
be867db3d9
commit
b5e318561c
7 changed files with 282 additions and 227 deletions
22
Cargo.lock
generated
22
Cargo.lock
generated
|
@ -2521,7 +2521,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma"
|
name = "ruma"
|
||||||
version = "0.12.5"
|
version = "0.12.5"
|
||||||
source = "git+https://github.com/ruma/ruma.git#7bae3d0c0b8edf008899ac2b04cf9722182eef68"
|
source = "git+https://github.com/ruma/ruma.git#71069c43650d2a797181aaa2ecc63037eaece907"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"assign",
|
"assign",
|
||||||
"js_int",
|
"js_int",
|
||||||
|
@ -2540,7 +2540,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-appservice-api"
|
name = "ruma-appservice-api"
|
||||||
version = "0.12.2"
|
version = "0.12.2"
|
||||||
source = "git+https://github.com/ruma/ruma.git#7bae3d0c0b8edf008899ac2b04cf9722182eef68"
|
source = "git+https://github.com/ruma/ruma.git#71069c43650d2a797181aaa2ecc63037eaece907"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js_int",
|
"js_int",
|
||||||
"ruma-common",
|
"ruma-common",
|
||||||
|
@ -2552,7 +2552,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-client-api"
|
name = "ruma-client-api"
|
||||||
version = "0.20.4"
|
version = "0.20.4"
|
||||||
source = "git+https://github.com/ruma/ruma.git#7bae3d0c0b8edf008899ac2b04cf9722182eef68"
|
source = "git+https://github.com/ruma/ruma.git#71069c43650d2a797181aaa2ecc63037eaece907"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"as_variant",
|
"as_variant",
|
||||||
"assign",
|
"assign",
|
||||||
|
@ -2575,7 +2575,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-common"
|
name = "ruma-common"
|
||||||
version = "0.15.4"
|
version = "0.15.4"
|
||||||
source = "git+https://github.com/ruma/ruma.git#7bae3d0c0b8edf008899ac2b04cf9722182eef68"
|
source = "git+https://github.com/ruma/ruma.git#71069c43650d2a797181aaa2ecc63037eaece907"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"as_variant",
|
"as_variant",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
|
@ -2607,7 +2607,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-events"
|
name = "ruma-events"
|
||||||
version = "0.30.4"
|
version = "0.30.4"
|
||||||
source = "git+https://github.com/ruma/ruma.git#7bae3d0c0b8edf008899ac2b04cf9722182eef68"
|
source = "git+https://github.com/ruma/ruma.git#71069c43650d2a797181aaa2ecc63037eaece907"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"as_variant",
|
"as_variant",
|
||||||
"indexmap 2.9.0",
|
"indexmap 2.9.0",
|
||||||
|
@ -2631,7 +2631,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-federation-api"
|
name = "ruma-federation-api"
|
||||||
version = "0.11.2"
|
version = "0.11.2"
|
||||||
source = "git+https://github.com/ruma/ruma.git#7bae3d0c0b8edf008899ac2b04cf9722182eef68"
|
source = "git+https://github.com/ruma/ruma.git#71069c43650d2a797181aaa2ecc63037eaece907"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"headers",
|
"headers",
|
||||||
|
@ -2653,7 +2653,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-identifiers-validation"
|
name = "ruma-identifiers-validation"
|
||||||
version = "0.10.1"
|
version = "0.10.1"
|
||||||
source = "git+https://github.com/ruma/ruma.git#7bae3d0c0b8edf008899ac2b04cf9722182eef68"
|
source = "git+https://github.com/ruma/ruma.git#71069c43650d2a797181aaa2ecc63037eaece907"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js_int",
|
"js_int",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
|
@ -2662,7 +2662,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-macros"
|
name = "ruma-macros"
|
||||||
version = "0.15.2"
|
version = "0.15.2"
|
||||||
source = "git+https://github.com/ruma/ruma.git#7bae3d0c0b8edf008899ac2b04cf9722182eef68"
|
source = "git+https://github.com/ruma/ruma.git#71069c43650d2a797181aaa2ecc63037eaece907"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"proc-macro-crate",
|
"proc-macro-crate",
|
||||||
|
@ -2677,7 +2677,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-push-gateway-api"
|
name = "ruma-push-gateway-api"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
source = "git+https://github.com/ruma/ruma.git#7bae3d0c0b8edf008899ac2b04cf9722182eef68"
|
source = "git+https://github.com/ruma/ruma.git#71069c43650d2a797181aaa2ecc63037eaece907"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js_int",
|
"js_int",
|
||||||
"ruma-common",
|
"ruma-common",
|
||||||
|
@ -2689,7 +2689,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-signatures"
|
name = "ruma-signatures"
|
||||||
version = "0.17.1"
|
version = "0.17.1"
|
||||||
source = "git+https://github.com/ruma/ruma.git#7bae3d0c0b8edf008899ac2b04cf9722182eef68"
|
source = "git+https://github.com/ruma/ruma.git#71069c43650d2a797181aaa2ecc63037eaece907"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
|
@ -2705,7 +2705,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-state-res"
|
name = "ruma-state-res"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
source = "git+https://github.com/ruma/ruma.git#7bae3d0c0b8edf008899ac2b04cf9722182eef68"
|
source = "git+https://github.com/ruma/ruma.git#71069c43650d2a797181aaa2ecc63037eaece907"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js_int",
|
"js_int",
|
||||||
"ruma-common",
|
"ruma-common",
|
||||||
|
|
|
@ -23,11 +23,16 @@ use ruma::{
|
||||||
},
|
},
|
||||||
int,
|
int,
|
||||||
serde::JsonObject,
|
serde::JsonObject,
|
||||||
CanonicalJsonObject, OwnedRoomAliasId, RoomAliasId, RoomId,
|
CanonicalJsonObject, CanonicalJsonValue, OwnedRoomAliasId, OwnedUserId, RoomAliasId, RoomId,
|
||||||
};
|
};
|
||||||
|
use serde::Deserialize;
|
||||||
use serde_json::{json, value::to_raw_value};
|
use serde_json::{json, value::to_raw_value};
|
||||||
use std::{cmp::max, collections::BTreeMap, sync::Arc};
|
use std::{
|
||||||
use tracing::{info, warn};
|
cmp::max,
|
||||||
|
collections::{BTreeMap, HashSet},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
use tracing::{error, info, warn};
|
||||||
|
|
||||||
/// # `POST /_matrix/client/r0/createRoom`
|
/// # `POST /_matrix/client/r0/createRoom`
|
||||||
///
|
///
|
||||||
|
@ -142,9 +147,33 @@ pub async fn create_room_route(
|
||||||
.expect("Supported room version must have rules.")
|
.expect("Supported room version must have rules.")
|
||||||
.authorization;
|
.authorization;
|
||||||
|
|
||||||
|
let mut users = BTreeMap::new();
|
||||||
|
if !rules.explicitly_privilege_room_creators {
|
||||||
|
users.insert(sender_user.clone(), int!(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Figure out preset. We need it for preset specific events
|
||||||
|
let preset = body.preset.clone().unwrap_or(match &body.visibility {
|
||||||
|
room::Visibility::Private => RoomPreset::PrivateChat,
|
||||||
|
room::Visibility::Public => RoomPreset::PublicChat,
|
||||||
|
_ => RoomPreset::PrivateChat, // Room visibility should not be custom
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut additional_creators: HashSet<OwnedUserId, _> = HashSet::new();
|
||||||
|
|
||||||
|
if preset == RoomPreset::TrustedPrivateChat {
|
||||||
|
if rules.additional_room_creators {
|
||||||
|
additional_creators.extend(body.invite.clone())
|
||||||
|
} else {
|
||||||
|
for invited_user in &body.invite {
|
||||||
|
users.insert(invited_user.clone(), int!(100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let content = match &body.creation_content {
|
let content = match &body.creation_content {
|
||||||
Some(content) => {
|
Some(raw_content) => {
|
||||||
let mut content = content
|
let mut content = raw_content
|
||||||
.deserialize_as_unchecked::<CanonicalJsonObject>()
|
.deserialize_as_unchecked::<CanonicalJsonObject>()
|
||||||
.expect("Invalid creation content");
|
.expect("Invalid creation content");
|
||||||
|
|
||||||
|
@ -157,6 +186,27 @@ pub async fn create_room_route(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if rules.additional_room_creators && !additional_creators.is_empty() {
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct AdditionalCreators {
|
||||||
|
additional_creators: Vec<OwnedUserId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(AdditionalCreators {
|
||||||
|
additional_creators: ac,
|
||||||
|
}) = raw_content.deserialize_as_unchecked()
|
||||||
|
{
|
||||||
|
additional_creators.extend(ac);
|
||||||
|
}
|
||||||
|
|
||||||
|
content.insert(
|
||||||
|
"additional_creators".into(),
|
||||||
|
json!(&additional_creators).try_into().map_err(|_| {
|
||||||
|
Error::BadRequest(ErrorKind::BadJson, "Invalid additional creators")
|
||||||
|
})?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
content.insert(
|
content.insert(
|
||||||
"room_version".into(),
|
"room_version".into(),
|
||||||
json!(room_version.as_str()).try_into().map_err(|_| {
|
json!(room_version.as_str()).try_into().map_err(|_| {
|
||||||
|
@ -166,24 +216,22 @@ pub async fn create_room_route(
|
||||||
content
|
content
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let content = if rules.use_room_create_sender {
|
let content = RoomCreateEventContent {
|
||||||
RoomCreateEventContent::new_v11()
|
additional_creators: additional_creators.into_iter().collect(),
|
||||||
} else {
|
room_version,
|
||||||
RoomCreateEventContent::new_v1(sender_user.clone())
|
..if rules.use_room_create_sender {
|
||||||
|
RoomCreateEventContent::new_v11()
|
||||||
|
} else {
|
||||||
|
RoomCreateEventContent::new_v1(sender_user.clone())
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let mut content = serde_json::from_str::<CanonicalJsonObject>(
|
|
||||||
|
serde_json::from_str::<CanonicalJsonObject>(
|
||||||
to_raw_value(&content)
|
to_raw_value(&content)
|
||||||
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid creation content"))?
|
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid creation content"))?
|
||||||
.get(),
|
.get(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.expect("room create event content created by us is valid")
|
||||||
content.insert(
|
|
||||||
"room_version".into(),
|
|
||||||
json!(room_version.as_str()).try_into().map_err(|_| {
|
|
||||||
Error::BadRequest(ErrorKind::BadJson, "Invalid creation content")
|
|
||||||
})?,
|
|
||||||
);
|
|
||||||
content
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -250,26 +298,9 @@ pub async fn create_room_route(
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// 3. Power levels
|
// 3. Power levels
|
||||||
|
|
||||||
// Figure out preset. We need it for preset specific events
|
|
||||||
let preset = body.preset.clone().unwrap_or(match &body.visibility {
|
|
||||||
room::Visibility::Private => RoomPreset::PrivateChat,
|
|
||||||
room::Visibility::Public => RoomPreset::PublicChat,
|
|
||||||
_ => RoomPreset::PrivateChat, // Room visibility should not be custom
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut users = BTreeMap::new();
|
|
||||||
users.insert(sender_user.clone(), int!(100));
|
|
||||||
|
|
||||||
if preset == RoomPreset::TrustedPrivateChat {
|
|
||||||
for invite_ in &body.invite {
|
|
||||||
users.insert(invite_.clone(), int!(100));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut power_levels_content = serde_json::to_value(RoomPowerLevelsEventContent {
|
let mut power_levels_content = serde_json::to_value(RoomPowerLevelsEventContent {
|
||||||
users,
|
users,
|
||||||
..Default::default()
|
..RoomPowerLevelsEventContent::new(&rules)
|
||||||
})
|
})
|
||||||
.expect("event is valid, we just created it");
|
.expect("event is valid, we just created it");
|
||||||
|
|
||||||
|
@ -587,7 +618,8 @@ pub async fn upgrade_room_route(
|
||||||
let rules = body
|
let rules = body
|
||||||
.new_version
|
.new_version
|
||||||
.rules()
|
.rules()
|
||||||
.expect("Supported room version must have rules.");
|
.expect("Supported room version must have rules.")
|
||||||
|
.authorization;
|
||||||
|
|
||||||
// Create a replacement room
|
// Create a replacement room
|
||||||
let replacement_room = RoomId::new(services().globals.server_name());
|
let replacement_room = RoomId::new(services().globals.server_name());
|
||||||
|
@ -663,7 +695,7 @@ pub async fn upgrade_room_route(
|
||||||
));
|
));
|
||||||
|
|
||||||
// Send a m.room.create event containing a predecessor field and the applicable room_version
|
// Send a m.room.create event containing a predecessor field and the applicable room_version
|
||||||
if rules.authorization.use_room_create_sender {
|
if rules.use_room_create_sender {
|
||||||
create_event_content.remove("creator");
|
create_event_content.remove("creator");
|
||||||
} else {
|
} else {
|
||||||
create_event_content.insert(
|
create_event_content.insert(
|
||||||
|
@ -674,6 +706,18 @@ pub async fn upgrade_room_route(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if rules.additional_room_creators && !body.additional_creators.is_empty() {
|
||||||
|
create_event_content.insert(
|
||||||
|
"additional_creators".into(),
|
||||||
|
json!(&body.additional_creators).try_into().map_err(|_| {
|
||||||
|
Error::BadRequest(
|
||||||
|
ErrorKind::BadJson,
|
||||||
|
"Failed to convert provided additional additional creators to JSON",
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
create_event_content.insert(
|
create_event_content.insert(
|
||||||
"room_version".into(),
|
"room_version".into(),
|
||||||
json!(&body.new_version)
|
json!(&body.new_version)
|
||||||
|
@ -764,7 +808,7 @@ pub async fn upgrade_room_route(
|
||||||
|
|
||||||
// Replicate transferable state events to the new room
|
// Replicate transferable state events to the new room
|
||||||
for event_type in transferable_state_events {
|
for event_type in transferable_state_events {
|
||||||
let event_content =
|
let mut event_content =
|
||||||
match services()
|
match services()
|
||||||
.rooms
|
.rooms
|
||||||
.state_accessor
|
.state_accessor
|
||||||
|
@ -774,6 +818,31 @@ pub async fn upgrade_room_route(
|
||||||
None => continue, // Skipping missing events.
|
None => continue, // Skipping missing events.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if event_type == StateEventType::RoomPowerLevels && rules.explicitly_privilege_room_creators
|
||||||
|
{
|
||||||
|
let mut pl_event_content: CanonicalJsonObject =
|
||||||
|
serde_json::from_str(event_content.get()).map_err(|e| {
|
||||||
|
error!(
|
||||||
|
"Invalid m.room.power_levels event content in room {}: {e}",
|
||||||
|
body.room_id
|
||||||
|
);
|
||||||
|
Error::BadDatabase("Invalid m.room.power_levels event content in room")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if let Some(CanonicalJsonValue::Object(users)) = pl_event_content.get_mut("users") {
|
||||||
|
users.remove(sender_user.as_str());
|
||||||
|
|
||||||
|
if rules.additional_room_creators {
|
||||||
|
for user in &body.additional_creators {
|
||||||
|
users.remove(user.as_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event_content = to_raw_value(&pl_event_content)
|
||||||
|
.expect("Must serialize, only changes made was removing keys")
|
||||||
|
}
|
||||||
|
|
||||||
services()
|
services()
|
||||||
.rooms
|
.rooms
|
||||||
.timeline
|
.timeline
|
||||||
|
|
|
@ -1756,7 +1756,9 @@ impl Service {
|
||||||
|
|
||||||
// 3. Power levels
|
// 3. Power levels
|
||||||
let mut users = BTreeMap::new();
|
let mut users = BTreeMap::new();
|
||||||
users.insert(conduit_user.to_owned(), 100.into());
|
if !rules.explicitly_privilege_room_creators {
|
||||||
|
users.insert(conduit_user.to_owned(), 100.into());
|
||||||
|
}
|
||||||
|
|
||||||
services()
|
services()
|
||||||
.rooms
|
.rooms
|
||||||
|
@ -1766,7 +1768,7 @@ impl Service {
|
||||||
event_type: TimelineEventType::RoomPowerLevels,
|
event_type: TimelineEventType::RoomPowerLevels,
|
||||||
content: to_raw_value(&RoomPowerLevelsEventContent {
|
content: to_raw_value(&RoomPowerLevelsEventContent {
|
||||||
users,
|
users,
|
||||||
..Default::default()
|
..RoomPowerLevelsEventContent::new(&rules)
|
||||||
})
|
})
|
||||||
.expect("event is valid, we just created it"),
|
.expect("event is valid, we just created it"),
|
||||||
unsigned: None,
|
unsigned: None,
|
||||||
|
@ -2010,8 +2012,16 @@ impl Service {
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Set power level
|
// Set power level
|
||||||
|
let room_version = services().rooms.state.get_room_version(&room_id)?;
|
||||||
|
let rules = room_version
|
||||||
|
.rules()
|
||||||
|
.expect("Supported room version must have rules.")
|
||||||
|
.authorization;
|
||||||
|
|
||||||
let mut users = BTreeMap::new();
|
let mut users = BTreeMap::new();
|
||||||
users.insert(conduit_user.to_owned(), 100.into());
|
if !rules.explicitly_privilege_room_creators {
|
||||||
|
users.insert(conduit_user.to_owned(), 100.into());
|
||||||
|
}
|
||||||
users.insert(user_id.to_owned(), 100.into());
|
users.insert(user_id.to_owned(), 100.into());
|
||||||
|
|
||||||
services()
|
services()
|
||||||
|
@ -2022,7 +2032,7 @@ impl Service {
|
||||||
event_type: TimelineEventType::RoomPowerLevels,
|
event_type: TimelineEventType::RoomPowerLevels,
|
||||||
content: to_raw_value(&RoomPowerLevelsEventContent {
|
content: to_raw_value(&RoomPowerLevelsEventContent {
|
||||||
users,
|
users,
|
||||||
..Default::default()
|
..RoomPowerLevelsEventContent::new(&rules)
|
||||||
})
|
})
|
||||||
.expect("event is valid, we just created it"),
|
.expect("event is valid, we just created it"),
|
||||||
unsigned: None,
|
unsigned: None,
|
||||||
|
|
|
@ -13,7 +13,7 @@ use ruma::{
|
||||||
},
|
},
|
||||||
IncomingResponse, OutgoingRequest, SendAccessToken,
|
IncomingResponse, OutgoingRequest, SendAccessToken,
|
||||||
},
|
},
|
||||||
events::{room::power_levels::RoomPowerLevelsEventContent, StateEventType, TimelineEventType},
|
events::TimelineEventType,
|
||||||
push::{Action, PushConditionRoomCtx, PushFormat, Ruleset, Tweak},
|
push::{Action, PushConditionRoomCtx, PushFormat, Ruleset, Tweak},
|
||||||
serde::Raw,
|
serde::Raw,
|
||||||
uint, RoomId, UInt, UserId,
|
uint, RoomId, UInt, UserId,
|
||||||
|
@ -139,21 +139,12 @@ impl Service {
|
||||||
let mut notify = None;
|
let mut notify = None;
|
||||||
let mut tweaks = Vec::new();
|
let mut tweaks = Vec::new();
|
||||||
|
|
||||||
let power_levels: RoomPowerLevelsEventContent = services()
|
let power_levels = services().rooms.state_accessor.power_levels(&pdu.room_id)?;
|
||||||
.rooms
|
|
||||||
.state_accessor
|
|
||||||
.room_state_get(&pdu.room_id, &StateEventType::RoomPowerLevels, "")?
|
|
||||||
.map(|ev| {
|
|
||||||
serde_json::from_str(ev.content.get())
|
|
||||||
.map_err(|_| Error::bad_database("invalid m.room.power_levels event"))
|
|
||||||
})
|
|
||||||
.transpose()?
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
for action in self.get_actions(
|
for action in self.get_actions(
|
||||||
user,
|
user,
|
||||||
&ruleset,
|
&ruleset,
|
||||||
&power_levels,
|
power_levels.into(),
|
||||||
&pdu.to_sync_room_event(),
|
&pdu.to_sync_room_event(),
|
||||||
&pdu.room_id,
|
&pdu.room_id,
|
||||||
)? {
|
)? {
|
||||||
|
@ -188,16 +179,10 @@ impl Service {
|
||||||
&self,
|
&self,
|
||||||
user: &UserId,
|
user: &UserId,
|
||||||
ruleset: &'a Ruleset,
|
ruleset: &'a Ruleset,
|
||||||
power_levels: &RoomPowerLevelsEventContent,
|
power_levels: PushConditionPowerLevelsCtx,
|
||||||
pdu: &Raw<AnySyncTimelineEvent>,
|
pdu: &Raw<AnySyncTimelineEvent>,
|
||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
) -> Result<&'a [Action]> {
|
) -> Result<&'a [Action]> {
|
||||||
let power_levels = PushConditionPowerLevelsCtx {
|
|
||||||
users: power_levels.users.clone(),
|
|
||||||
users_default: power_levels.users_default,
|
|
||||||
notifications: power_levels.notifications.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let ctx = PushConditionRoomCtx {
|
let ctx = PushConditionRoomCtx {
|
||||||
room_id: room_id.to_owned(),
|
room_id: room_id.to_owned(),
|
||||||
member_count: 10_u32.into(), // TODO: get member count efficiently
|
member_count: 10_u32.into(), // TODO: get member count efficiently
|
||||||
|
|
|
@ -2,7 +2,6 @@ mod data;
|
||||||
|
|
||||||
pub use data::Data;
|
pub use data::Data;
|
||||||
use rand::seq::SliceRandom;
|
use rand::seq::SliceRandom;
|
||||||
use tracing::error;
|
|
||||||
|
|
||||||
use crate::{services, Error, Result};
|
use crate::{services, Error, Result};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
|
@ -11,10 +10,7 @@ use ruma::{
|
||||||
client::{alias::get_alias, error::ErrorKind},
|
client::{alias::get_alias, error::ErrorKind},
|
||||||
federation,
|
federation,
|
||||||
},
|
},
|
||||||
events::{
|
events::StateEventType,
|
||||||
room::power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
|
|
||||||
StateEventType,
|
|
||||||
},
|
|
||||||
OwnedRoomAliasId, OwnedRoomId, RoomAliasId, RoomId, UserId,
|
OwnedRoomAliasId, OwnedRoomId, RoomAliasId, RoomId, UserId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -55,27 +51,14 @@ impl Service {
|
||||||
{
|
{
|
||||||
Ok(true)
|
Ok(true)
|
||||||
// Checking whether the user is able to change canonical aliases of the room
|
// Checking whether the user is able to change canonical aliases of the room
|
||||||
} else if let Some(event) = services().rooms.state_accessor.room_state_get(
|
|
||||||
&room_id,
|
|
||||||
&StateEventType::RoomPowerLevels,
|
|
||||||
"",
|
|
||||||
)? {
|
|
||||||
serde_json::from_str(event.content.get())
|
|
||||||
.map_err(|_| Error::bad_database("Invalid event content for m.room.power_levels"))
|
|
||||||
.map(|content: RoomPowerLevelsEventContent| {
|
|
||||||
RoomPowerLevels::from(content)
|
|
||||||
.user_can_send_state(user_id, StateEventType::RoomCanonicalAlias)
|
|
||||||
})
|
|
||||||
// If there is no power levels event, only the room creator can change canonical aliases
|
|
||||||
} else if let Some(event) = services().rooms.state_accessor.room_state_get(
|
|
||||||
&room_id,
|
|
||||||
&StateEventType::RoomCreate,
|
|
||||||
"",
|
|
||||||
)? {
|
|
||||||
Ok(event.sender == user_id)
|
|
||||||
} else {
|
} else {
|
||||||
error!("Room {} has no m.room.create event (VERY BAD)!", room_id);
|
services()
|
||||||
Err(Error::bad_database("Room has no m.room.create event"))
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.power_levels(&room_id)
|
||||||
|
.map(|power_levels| {
|
||||||
|
power_levels.user_can_send_state(user_id, StateEventType::RoomCanonicalAlias)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ use ruma::{
|
||||||
StateEventType,
|
StateEventType,
|
||||||
},
|
},
|
||||||
room::{JoinRuleSummary, RoomMembership},
|
room::{JoinRuleSummary, RoomMembership},
|
||||||
state_res::Event,
|
state_res::{events::RoomCreateEvent, Event},
|
||||||
EventId, JsOption, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId,
|
EventId, JsOption, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId,
|
||||||
};
|
};
|
||||||
use serde_json::value::to_raw_value;
|
use serde_json::value::to_raw_value;
|
||||||
|
@ -361,43 +361,19 @@ impl Service {
|
||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
federation: bool,
|
federation: bool,
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
self.room_state_get(room_id, &StateEventType::RoomPowerLevels, "")?
|
self.power_levels(room_id).map(|power_levels| {
|
||||||
.map(|e| {
|
power_levels.user_can_redact_event_of_other(sender)
|
||||||
serde_json::from_str(e.content.get())
|
|| power_levels.user_can_redact_own_event(sender)
|
||||||
.map(|c: RoomPowerLevelsEventContent| c.into())
|
&& if let Ok(Some(pdu)) = services().rooms.timeline.get_pdu(redacts) {
|
||||||
.map(|e: RoomPowerLevels| {
|
if federation {
|
||||||
e.user_can_redact_event_of_other(sender)
|
pdu.sender().server_name() == sender.server_name()
|
||||||
|| e.user_can_redact_own_event(sender)
|
|
||||||
&& if let Ok(Some(pdu)) = services().rooms.timeline.get_pdu(redacts)
|
|
||||||
{
|
|
||||||
if federation {
|
|
||||||
pdu.sender().server_name() == sender.server_name()
|
|
||||||
} else {
|
|
||||||
pdu.sender == sender
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.map_err(|_| {
|
|
||||||
Error::bad_database("Invalid m.room.power_levels event in database")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
// Falling back on m.room.create to judge power levels
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
if let Some(pdu) = self.room_state_get(room_id, &StateEventType::RoomCreate, "")? {
|
|
||||||
Ok(pdu.sender == sender
|
|
||||||
|| if let Ok(Some(pdu)) = services().rooms.timeline.get_pdu(redacts) {
|
|
||||||
pdu.sender == sender
|
|
||||||
} else {
|
} else {
|
||||||
false
|
pdu.sender == sender
|
||||||
})
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(Error::bad_database(
|
false
|
||||||
"No m.room.power_levels or m.room.create events in database for room",
|
}
|
||||||
))
|
})
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if guests are able to join a given room
|
/// Checks if guests are able to join a given room
|
||||||
|
@ -485,4 +461,42 @@ impl Service {
|
||||||
}
|
}
|
||||||
room_ids
|
room_ids
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the effective power levels of a room, regardless of if there is an
|
||||||
|
/// `m.rooms.power_levels` state.
|
||||||
|
pub fn power_levels(&self, room_id: &RoomId) -> Result<RoomPowerLevels> {
|
||||||
|
let power_levels: Option<RoomPowerLevelsEventContent> = self
|
||||||
|
.room_state_get(room_id, &StateEventType::RoomPowerLevels, "")?
|
||||||
|
.map(|ev| {
|
||||||
|
serde_json::from_str(ev.content.get())
|
||||||
|
.map_err(|_| Error::bad_database("invalid m.room.power_levels event."))
|
||||||
|
})
|
||||||
|
.transpose()?;
|
||||||
|
|
||||||
|
let room_create = self
|
||||||
|
.room_state_get(room_id, &StateEventType::RoomCreate, "")?
|
||||||
|
.map(RoomCreateEvent::new)
|
||||||
|
.ok_or_else(|| Error::bad_database("Found room without m.room.create event."))?;
|
||||||
|
|
||||||
|
let room_version = room_create.room_version().map_err(|e| {
|
||||||
|
error!("Invalid room version in room create content for room {room_id}: {e}");
|
||||||
|
Error::BadDatabase("Room Create content had invalid room version")
|
||||||
|
})?;
|
||||||
|
let rules = room_version
|
||||||
|
.rules()
|
||||||
|
.expect("Supported room version must have rules.")
|
||||||
|
.authorization;
|
||||||
|
|
||||||
|
room_create
|
||||||
|
// NOTE: This is because project hydra's client-side was made public before the server
|
||||||
|
// side. This will be fixed by using `creators` instead in part 2/2.
|
||||||
|
.creator(&rules)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("Failed to get creators of room id {}: {e}", room_id);
|
||||||
|
Error::BadDatabase("RoomCreateEvent has invalid creators")
|
||||||
|
})
|
||||||
|
.map(|creator| {
|
||||||
|
RoomPowerLevels::new(power_levels.into(), &rules, [creator.into_owned()])
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,12 +15,11 @@ use ruma::{
|
||||||
push_rules::PushRulesEvent,
|
push_rules::PushRulesEvent,
|
||||||
room::{
|
room::{
|
||||||
canonical_alias::RoomCanonicalAliasEventContent, create::RoomCreateEventContent,
|
canonical_alias::RoomCanonicalAliasEventContent, create::RoomCreateEventContent,
|
||||||
encrypted::Relation, member::MembershipState,
|
encrypted::Relation, member::MembershipState, redaction::RoomRedactionEventContent,
|
||||||
power_levels::RoomPowerLevelsEventContent, redaction::RoomRedactionEventContent,
|
|
||||||
},
|
},
|
||||||
GlobalAccountDataEventType, StateEventType, TimelineEventType,
|
GlobalAccountDataEventType, StateEventType, TimelineEventType,
|
||||||
},
|
},
|
||||||
push::{Action, Ruleset, Tweak},
|
push::{Action, PushConditionPowerLevelsCtx, Ruleset, Tweak},
|
||||||
state_res::{self, Event},
|
state_res::{self, Event},
|
||||||
uint, user_id, CanonicalJsonObject, CanonicalJsonValue, EventId, MilliSecondsSinceUnixEpoch,
|
uint, user_id, CanonicalJsonObject, CanonicalJsonValue, EventId, MilliSecondsSinceUnixEpoch,
|
||||||
OwnedEventId, OwnedRoomId, OwnedServerName, RoomId, ServerName, UserId,
|
OwnedEventId, OwnedRoomId, OwnedServerName, RoomId, ServerName, UserId,
|
||||||
|
@ -290,96 +289,99 @@ impl Service {
|
||||||
drop(insert_lock);
|
drop(insert_lock);
|
||||||
|
|
||||||
// See if the event matches any known pushers
|
// See if the event matches any known pushers
|
||||||
let power_levels: RoomPowerLevelsEventContent = services()
|
//
|
||||||
.rooms
|
// Will fail if this is the first event (the create event), which is fine, since it cannot
|
||||||
.state_accessor
|
// notify anyone else anyways
|
||||||
.room_state_get(&pdu.room_id, &StateEventType::RoomPowerLevels, "")?
|
if pdu.kind != TimelineEventType::RoomCreate
|
||||||
.map(|ev| {
|
|| pdu.state_key.as_deref().is_none_or(|key| !key.is_empty())
|
||||||
serde_json::from_str(ev.content.get())
|
{
|
||||||
.map_err(|_| Error::bad_database("invalid m.room.power_levels event"))
|
if let Ok(power_levels) = services()
|
||||||
})
|
.rooms
|
||||||
.transpose()?
|
.state_accessor
|
||||||
.unwrap_or_default();
|
.power_levels(&pdu.room_id)
|
||||||
|
.map(PushConditionPowerLevelsCtx::from)
|
||||||
|
{
|
||||||
|
let sync_pdu = pdu.to_sync_room_event();
|
||||||
|
|
||||||
let sync_pdu = pdu.to_sync_room_event();
|
let mut notifies = Vec::new();
|
||||||
|
let mut highlights = Vec::new();
|
||||||
|
|
||||||
let mut notifies = Vec::new();
|
let mut push_target = services()
|
||||||
let mut highlights = Vec::new();
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.get_our_real_users(&pdu.room_id)?;
|
||||||
|
|
||||||
let mut push_target = services()
|
if pdu.kind == TimelineEventType::RoomMember {
|
||||||
.rooms
|
if let Some(state_key) = &pdu.state_key {
|
||||||
.state_cache
|
let target_user_id = UserId::parse(state_key.clone())
|
||||||
.get_our_real_users(&pdu.room_id)?;
|
.expect("This state_key was previously validated");
|
||||||
|
|
||||||
if pdu.kind == TimelineEventType::RoomMember {
|
if !push_target.contains(&target_user_id) {
|
||||||
if let Some(state_key) = &pdu.state_key {
|
let mut target = push_target.as_ref().clone();
|
||||||
let target_user_id = UserId::parse(state_key.clone())
|
target.insert(target_user_id);
|
||||||
.expect("This state_key was previously validated");
|
push_target = Arc::new(target);
|
||||||
|
}
|
||||||
if !push_target.contains(&target_user_id) {
|
|
||||||
let mut target = push_target.as_ref().clone();
|
|
||||||
target.insert(target_user_id);
|
|
||||||
push_target = Arc::new(target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for user in push_target.iter() {
|
|
||||||
// Don't notify the user of their own events
|
|
||||||
if user == &pdu.sender {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let rules_for_user = services()
|
|
||||||
.account_data
|
|
||||||
.get(
|
|
||||||
None,
|
|
||||||
user,
|
|
||||||
GlobalAccountDataEventType::PushRules.to_string().into(),
|
|
||||||
)?
|
|
||||||
.map(|event| {
|
|
||||||
serde_json::from_str::<PushRulesEvent>(event.get())
|
|
||||||
.map_err(|_| Error::bad_database("Invalid push rules event in db."))
|
|
||||||
})
|
|
||||||
.transpose()?
|
|
||||||
.map(|ev: PushRulesEvent| ev.content.global)
|
|
||||||
.unwrap_or_else(|| Ruleset::server_default(user));
|
|
||||||
|
|
||||||
let mut highlight = false;
|
|
||||||
let mut notify = false;
|
|
||||||
|
|
||||||
for action in services().pusher.get_actions(
|
|
||||||
user,
|
|
||||||
&rules_for_user,
|
|
||||||
&power_levels,
|
|
||||||
&sync_pdu,
|
|
||||||
&pdu.room_id,
|
|
||||||
)? {
|
|
||||||
match action {
|
|
||||||
Action::Notify => notify = true,
|
|
||||||
Action::SetTweak(Tweak::Highlight(true)) => {
|
|
||||||
highlight = true;
|
|
||||||
}
|
}
|
||||||
_ => {}
|
}
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if notify {
|
for user in push_target.iter() {
|
||||||
notifies.push(user.clone());
|
// Don't notify the user of their own events
|
||||||
}
|
if user == &pdu.sender {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if highlight {
|
let rules_for_user = services()
|
||||||
highlights.push(user.clone());
|
.account_data
|
||||||
}
|
.get(
|
||||||
|
None,
|
||||||
|
user,
|
||||||
|
GlobalAccountDataEventType::PushRules.to_string().into(),
|
||||||
|
)?
|
||||||
|
.map(|event| {
|
||||||
|
serde_json::from_str::<PushRulesEvent>(event.get())
|
||||||
|
.map_err(|_| Error::bad_database("Invalid push rules event in db."))
|
||||||
|
})
|
||||||
|
.transpose()?
|
||||||
|
.map(|ev: PushRulesEvent| ev.content.global)
|
||||||
|
.unwrap_or_else(|| Ruleset::server_default(user));
|
||||||
|
|
||||||
for push_key in services().pusher.get_pushkeys(user) {
|
let mut highlight = false;
|
||||||
services().sending.send_push_pdu(&pdu_id, user, push_key?)?;
|
let mut notify = false;
|
||||||
|
|
||||||
|
for action in services().pusher.get_actions(
|
||||||
|
user,
|
||||||
|
&rules_for_user,
|
||||||
|
power_levels.clone(),
|
||||||
|
&sync_pdu,
|
||||||
|
&pdu.room_id,
|
||||||
|
)? {
|
||||||
|
match action {
|
||||||
|
Action::Notify => notify = true,
|
||||||
|
Action::SetTweak(Tweak::Highlight(true)) => {
|
||||||
|
highlight = true;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if notify {
|
||||||
|
notifies.push(user.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if highlight {
|
||||||
|
highlights.push(user.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
for push_key in services().pusher.get_pushkeys(user) {
|
||||||
|
services().sending.send_push_pdu(&pdu_id, user, push_key?)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db
|
||||||
|
.increment_notification_counts(&pdu.room_id, notifies, highlights)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.db
|
|
||||||
.increment_notification_counts(&pdu.room_id, notifies, highlights)?;
|
|
||||||
|
|
||||||
match pdu.kind {
|
match pdu.kind {
|
||||||
TimelineEventType::RoomRedaction => {
|
TimelineEventType::RoomRedaction => {
|
||||||
let room_version_id = services().rooms.state.get_room_version(&pdu.room_id)?;
|
let room_version_id = services().rooms.state.get_room_version(&pdu.room_id)?;
|
||||||
|
@ -1167,16 +1169,8 @@ impl Service {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let power_levels: RoomPowerLevelsEventContent = services()
|
let power_levels = services().rooms.state_accessor.power_levels(room_id)?;
|
||||||
.rooms
|
|
||||||
.state_accessor
|
|
||||||
.room_state_get(room_id, &StateEventType::RoomPowerLevels, "")?
|
|
||||||
.map(|ev| {
|
|
||||||
serde_json::from_str(ev.content.get())
|
|
||||||
.map_err(|_| Error::bad_database("invalid m.room.power_levels event"))
|
|
||||||
})
|
|
||||||
.transpose()?
|
|
||||||
.unwrap_or_default();
|
|
||||||
let mut admin_servers = power_levels
|
let mut admin_servers = power_levels
|
||||||
.users
|
.users
|
||||||
.iter()
|
.iter()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue