add some m.room.member checks on putting direct state events
Signed-off-by: strawberry <strawberry@puppygock.gay>
This commit is contained in:
parent
50cadbee96
commit
a5d70f7356
6 changed files with 117 additions and 60 deletions
|
@ -1,15 +1,13 @@
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use conduwuit::{err, pdu::PduBuilder, utils::BoolExt, Err, Error, PduEvent, Result};
|
use conduwuit::{err, pdu::PduBuilder, utils::BoolExt, Err, PduEvent, Result};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::client::{
|
api::client::state::{get_state_events, get_state_events_for_key, send_state_event},
|
||||||
error::ErrorKind,
|
|
||||||
state::{get_state_events, get_state_events_for_key, send_state_event},
|
|
||||||
},
|
|
||||||
events::{
|
events::{
|
||||||
room::{
|
room::{
|
||||||
canonical_alias::RoomCanonicalAliasEventContent,
|
canonical_alias::RoomCanonicalAliasEventContent,
|
||||||
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
|
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
|
||||||
join_rules::{JoinRule, RoomJoinRulesEventContent},
|
join_rules::{JoinRule, RoomJoinRulesEventContent},
|
||||||
|
member::{MembershipState, RoomMemberEventContent},
|
||||||
},
|
},
|
||||||
AnyStateEventContent, StateEventType,
|
AnyStateEventContent, StateEventType,
|
||||||
},
|
},
|
||||||
|
@ -23,11 +21,6 @@ use crate::{Ruma, RumaResponse};
|
||||||
/// # `PUT /_matrix/client/*/rooms/{roomId}/state/{eventType}/{stateKey}`
|
/// # `PUT /_matrix/client/*/rooms/{roomId}/state/{eventType}/{stateKey}`
|
||||||
///
|
///
|
||||||
/// Sends a state event into the room.
|
/// Sends a state event into the room.
|
||||||
///
|
|
||||||
/// - The only requirement for the content is that it has to be valid json
|
|
||||||
/// - Tries to send the event into the room, auth rules will determine if it is
|
|
||||||
/// allowed
|
|
||||||
/// - If event is new `canonical_alias`: Rejects if alias is incorrect
|
|
||||||
pub(crate) async fn send_state_event_for_key_route(
|
pub(crate) async fn send_state_event_for_key_route(
|
||||||
State(services): State<crate::State>,
|
State(services): State<crate::State>,
|
||||||
body: Ruma<send_state_event::v3::Request>,
|
body: Ruma<send_state_event::v3::Request>,
|
||||||
|
@ -41,7 +34,7 @@ pub(crate) async fn send_state_event_for_key_route(
|
||||||
&body.room_id,
|
&body.room_id,
|
||||||
&body.event_type,
|
&body.event_type,
|
||||||
&body.body.body,
|
&body.body.body,
|
||||||
body.state_key.clone(),
|
&body.state_key,
|
||||||
if body.appservice_info.is_some() {
|
if body.appservice_info.is_some() {
|
||||||
body.timestamp
|
body.timestamp
|
||||||
} else {
|
} else {
|
||||||
|
@ -55,11 +48,6 @@ pub(crate) async fn send_state_event_for_key_route(
|
||||||
/// # `PUT /_matrix/client/*/rooms/{roomId}/state/{eventType}`
|
/// # `PUT /_matrix/client/*/rooms/{roomId}/state/{eventType}`
|
||||||
///
|
///
|
||||||
/// Sends a state event into the room.
|
/// Sends a state event into the room.
|
||||||
///
|
|
||||||
/// - The only requirement for the content is that it has to be valid json
|
|
||||||
/// - Tries to send the event into the room, auth rules will determine if it is
|
|
||||||
/// allowed
|
|
||||||
/// - If event is new `canonical_alias`: Rejects if alias is incorrect
|
|
||||||
pub(crate) async fn send_state_event_for_empty_key_route(
|
pub(crate) async fn send_state_event_for_empty_key_route(
|
||||||
State(services): State<crate::State>,
|
State(services): State<crate::State>,
|
||||||
body: Ruma<send_state_event::v3::Request>,
|
body: Ruma<send_state_event::v3::Request>,
|
||||||
|
@ -172,10 +160,10 @@ async fn send_state_event_for_key_helper(
|
||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
event_type: &StateEventType,
|
event_type: &StateEventType,
|
||||||
json: &Raw<AnyStateEventContent>,
|
json: &Raw<AnyStateEventContent>,
|
||||||
state_key: String,
|
state_key: &str,
|
||||||
timestamp: Option<ruma::MilliSecondsSinceUnixEpoch>,
|
timestamp: Option<ruma::MilliSecondsSinceUnixEpoch>,
|
||||||
) -> Result<OwnedEventId> {
|
) -> Result<OwnedEventId> {
|
||||||
allowed_to_send_state_event(services, room_id, event_type, json).await?;
|
allowed_to_send_state_event(services, room_id, event_type, state_key, json).await?;
|
||||||
let state_lock = services.rooms.state.mutex.lock(room_id).await;
|
let state_lock = services.rooms.state.mutex.lock(room_id).await;
|
||||||
let event_id = services
|
let event_id = services
|
||||||
.rooms
|
.rooms
|
||||||
|
@ -184,7 +172,7 @@ async fn send_state_event_for_key_helper(
|
||||||
PduBuilder {
|
PduBuilder {
|
||||||
event_type: event_type.to_string().into(),
|
event_type: event_type.to_string().into(),
|
||||||
content: serde_json::from_str(json.json().get())?,
|
content: serde_json::from_str(json.json().get())?,
|
||||||
state_key: Some(state_key),
|
state_key: Some(String::from(state_key)),
|
||||||
timestamp,
|
timestamp,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
@ -201,6 +189,7 @@ async fn allowed_to_send_state_event(
|
||||||
services: &Services,
|
services: &Services,
|
||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
event_type: &StateEventType,
|
event_type: &StateEventType,
|
||||||
|
state_key: &str,
|
||||||
json: &Raw<AnyStateEventContent>,
|
json: &Raw<AnyStateEventContent>,
|
||||||
) -> Result {
|
) -> Result {
|
||||||
match event_type {
|
match event_type {
|
||||||
|
@ -212,10 +201,7 @@ async fn allowed_to_send_state_event(
|
||||||
// Forbid m.room.encryption if encryption is disabled
|
// Forbid m.room.encryption if encryption is disabled
|
||||||
| StateEventType::RoomEncryption =>
|
| StateEventType::RoomEncryption =>
|
||||||
if !services.globals.allow_encryption() {
|
if !services.globals.allow_encryption() {
|
||||||
return Err(Error::BadRequest(
|
return Err!(Request(Forbidden("Encryption is disabled on this homeserver.")));
|
||||||
ErrorKind::forbidden(),
|
|
||||||
"Encryption has been disabled",
|
|
||||||
));
|
|
||||||
},
|
},
|
||||||
// admin room is a sensitive room, it should not ever be made public
|
// admin room is a sensitive room, it should not ever be made public
|
||||||
| StateEventType::RoomJoinRules => {
|
| StateEventType::RoomJoinRules => {
|
||||||
|
@ -225,10 +211,9 @@ async fn allowed_to_send_state_event(
|
||||||
serde_json::from_str::<RoomJoinRulesEventContent>(json.json().get())
|
serde_json::from_str::<RoomJoinRulesEventContent>(json.json().get())
|
||||||
{
|
{
|
||||||
if join_rule.join_rule == JoinRule::Public {
|
if join_rule.join_rule == JoinRule::Public {
|
||||||
return Err(Error::BadRequest(
|
return Err!(Request(Forbidden(
|
||||||
ErrorKind::forbidden(),
|
"Admin room is a sensitive room, it cannot be made public"
|
||||||
"Admin room is not allowed to be public.",
|
)));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -236,26 +221,22 @@ async fn allowed_to_send_state_event(
|
||||||
},
|
},
|
||||||
// admin room is a sensitive room, it should not ever be made world readable
|
// admin room is a sensitive room, it should not ever be made world readable
|
||||||
| StateEventType::RoomHistoryVisibility => {
|
| StateEventType::RoomHistoryVisibility => {
|
||||||
if let Ok(admin_room_id) = services.admin.get_admin_room().await {
|
if let Ok(visibility_content) =
|
||||||
if admin_room_id == room_id {
|
serde_json::from_str::<RoomHistoryVisibilityEventContent>(json.json().get())
|
||||||
if let Ok(visibility_content) = serde_json::from_str::<
|
|
||||||
RoomHistoryVisibilityEventContent,
|
|
||||||
>(json.json().get())
|
|
||||||
{
|
{
|
||||||
if visibility_content.history_visibility
|
if let Ok(admin_room_id) = services.admin.get_admin_room().await {
|
||||||
|
if admin_room_id == room_id
|
||||||
|
&& visibility_content.history_visibility
|
||||||
== HistoryVisibility::WorldReadable
|
== HistoryVisibility::WorldReadable
|
||||||
{
|
{
|
||||||
return Err(Error::BadRequest(
|
return Err!(Request(Forbidden(
|
||||||
ErrorKind::forbidden(),
|
"Admin room is a sensitive room, it cannot be made world readable \
|
||||||
"Admin room is not allowed to be made world readable (public \
|
(public room history)."
|
||||||
room history).",
|
)));
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// TODO: allow alias if it previously existed
|
|
||||||
| StateEventType::RoomCanonicalAlias => {
|
| StateEventType::RoomCanonicalAlias => {
|
||||||
if let Ok(canonical_alias) =
|
if let Ok(canonical_alias) =
|
||||||
serde_json::from_str::<RoomCanonicalAliasEventContent>(json.json().get())
|
serde_json::from_str::<RoomCanonicalAliasEventContent>(json.json().get())
|
||||||
|
@ -289,6 +270,59 @@ async fn allowed_to_send_state_event(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
| StateEventType::RoomMember => {
|
||||||
|
let Ok(membership_content) =
|
||||||
|
serde_json::from_str::<RoomMemberEventContent>(json.json().get())
|
||||||
|
else {
|
||||||
|
return Err!(Request(BadJson(
|
||||||
|
"Membership content must have a valid JSON body with at least a valid \
|
||||||
|
membership state."
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(state_key) = UserId::parse(state_key) else {
|
||||||
|
return Err!(Request(BadJson(
|
||||||
|
"Membership event has invalid or non-existent state key"
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(authorising_user) = membership_content.join_authorized_via_users_server {
|
||||||
|
if membership_content.membership != MembershipState::Join {
|
||||||
|
return Err!(Request(BadJson(
|
||||||
|
"join_authorised_via_users_server is only for member joins"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if services
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.is_joined(state_key, room_id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
return Err!(Request(InvalidParam(
|
||||||
|
"{state_key} is already joined, an authorising user is not required."
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !services.globals.user_is_local(&authorising_user) {
|
||||||
|
return Err!(Request(InvalidParam(
|
||||||
|
"Authorising user {authorising_user} does not belong to this homeserver"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !services
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.is_joined(&authorising_user, room_id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
return Err!(Request(InvalidParam(
|
||||||
|
"Authorising user {authorising_user} is not in the room, they cannot \
|
||||||
|
authorise the join."
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
| _ => (),
|
| _ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,12 +61,11 @@ async fn create_join_event(
|
||||||
};
|
};
|
||||||
|
|
||||||
let event_room_id: OwnedRoomId = serde_json::from_value(
|
let event_room_id: OwnedRoomId = serde_json::from_value(
|
||||||
serde_json::to_value(
|
|
||||||
value
|
value
|
||||||
.get("room_id")
|
.get("room_id")
|
||||||
.ok_or_else(|| err!(Request(BadJson("Event missing room_id property."))))?,
|
.ok_or_else(|| err!(Request(BadJson("Event missing room_id property."))))?
|
||||||
)
|
.clone()
|
||||||
.expect("CanonicalJson is valid json value"),
|
.into(),
|
||||||
)
|
)
|
||||||
.map_err(|e| err!(Request(BadJson(warn!("room_id field is not a valid room ID: {e}")))))?;
|
.map_err(|e| err!(Request(BadJson(warn!("room_id field is not a valid room ID: {e}")))))?;
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,9 @@ use std::{
|
||||||
time::Instant,
|
time::Instant,
|
||||||
};
|
};
|
||||||
|
|
||||||
use conduwuit::{debug, err, implement, warn, Error, Result};
|
use conduwuit::{debug, err, implement, warn, Err, Result};
|
||||||
use futures::{FutureExt, TryFutureExt};
|
use futures::{FutureExt, TryFutureExt};
|
||||||
use ruma::{
|
use ruma::{events::StateEventType, CanonicalJsonValue, EventId, RoomId, ServerName, UserId};
|
||||||
api::client::error::ErrorKind, events::StateEventType, CanonicalJsonValue, EventId, RoomId,
|
|
||||||
ServerName, UserId,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{check_room_id, get_room_version_id};
|
use super::{check_room_id, get_room_version_id};
|
||||||
use crate::rooms::timeline::RawPduId;
|
use crate::rooms::timeline::RawPduId;
|
||||||
|
@ -58,15 +55,14 @@ pub async fn handle_incoming_pdu<'a>(
|
||||||
|
|
||||||
// 1.1 Check the server is in the room
|
// 1.1 Check the server is in the room
|
||||||
if !self.services.metadata.exists(room_id).await {
|
if !self.services.metadata.exists(room_id).await {
|
||||||
return Err(Error::BadRequest(ErrorKind::NotFound, "Room is unknown to this server"));
|
return Err!(Request(NotFound("Room is unknown to this server")));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1.2 Check if the room is disabled
|
// 1.2 Check if the room is disabled
|
||||||
if self.services.metadata.is_disabled(room_id).await {
|
if self.services.metadata.is_disabled(room_id).await {
|
||||||
return Err(Error::BadRequest(
|
return Err!(Request(Forbidden(
|
||||||
ErrorKind::forbidden(),
|
"Federation of this room is currently disabled on this server."
|
||||||
"Federation of this room is currently disabled on this server.",
|
)));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1.3.1 Check room ACL on origin field/server
|
// 1.3.1 Check room ACL on origin field/server
|
||||||
|
|
|
@ -68,7 +68,7 @@ pub(super) async fn handle_outlier_pdu<'a>(
|
||||||
let incoming_pdu = serde_json::from_value::<PduEvent>(
|
let incoming_pdu = serde_json::from_value::<PduEvent>(
|
||||||
serde_json::to_value(&val).expect("CanonicalJsonObj is a valid JsonValue"),
|
serde_json::to_value(&val).expect("CanonicalJsonObj is a valid JsonValue"),
|
||||||
)
|
)
|
||||||
.map_err(|_| Error::bad_database("Event is not a valid PDU."))?;
|
.map_err(|e| err!(Request(BadJson(debug_warn!("Event is not a valid PDU: {e}")))))?;
|
||||||
|
|
||||||
check_room_id(room_id, &incoming_pdu)?;
|
check_room_id(room_id, &incoming_pdu)?;
|
||||||
|
|
||||||
|
|
|
@ -901,6 +901,29 @@ impl Service {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if pdu.kind == TimelineEventType::RoomMember {
|
||||||
|
let content: RoomMemberEventContent = pdu.get_content()?;
|
||||||
|
|
||||||
|
if content.join_authorized_via_users_server.is_some()
|
||||||
|
&& content.membership != MembershipState::Join
|
||||||
|
{
|
||||||
|
return Err!(Request(BadJson(
|
||||||
|
"join_authorised_via_users_server is only for member joins"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if content
|
||||||
|
.join_authorized_via_users_server
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|authorising_user| {
|
||||||
|
!self.services.globals.user_is_local(authorising_user)
|
||||||
|
}) {
|
||||||
|
return Err!(Request(InvalidParam(
|
||||||
|
"Authorising user does not belong to this homeserver"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We append to state before appending the pdu, so we don't have a moment in
|
// We append to state before appending the pdu, so we don't have a moment in
|
||||||
// time with the pdu without it's state. This is okay because append_pdu can't
|
// time with the pdu without it's state. This is okay because append_pdu can't
|
||||||
// fail.
|
// fail.
|
||||||
|
|
|
@ -739,7 +739,12 @@ impl Service {
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut pdus = Vec::new();
|
let mut pdus = Vec::with_capacity(
|
||||||
|
events
|
||||||
|
.iter()
|
||||||
|
.filter(|event| matches!(event, SendingEvent::Pdu(_)))
|
||||||
|
.count(),
|
||||||
|
);
|
||||||
for event in &events {
|
for event in &events {
|
||||||
match event {
|
match event {
|
||||||
| SendingEvent::Pdu(pdu_id) => {
|
| SendingEvent::Pdu(pdu_id) => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue