mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-11-13 04:06:54 +00:00
refactor: Use a shared helper function for submitting state events at room creation
On room creation, A list of initial state events may be provided that should be submitted to the room after creation. These events should be treated as any other state event and submitted to the same checks. With this change, state events submitted on room creation will be submitted through the same helper as those through the usual endpoint. The submission helper is moved to the timeline service to make it available everywhere. This will be useful for implementing MSC4140 (issue #903).
This commit is contained in:
1 parent
8cde2ad0cc
commit
7f8c2aedc8
5 files changed
+388
-304
No files matched your search
@@ -2,7 +2,7 @@ use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use axum::extract::State;
|
||||
use conduwuit::{
|
||||
Err, Result, debug, debug_info, err, info,
|
||||
Err, Error, Result, debug, debug_info, err, info,
|
||||
matrix::{StateKey, pdu::PartialPdu},
|
||||
trace, warn,
|
||||
};
|
||||
@@ -11,10 +11,13 @@ use futures::FutureExt;
|
||||
use ruma::{
|
||||
CanonicalJsonObject, CanonicalJsonValue, Int, MilliSecondsSinceUnixEpoch, OwnedRoomAliasId,
|
||||
OwnedUserId, RoomAliasId, RoomId, RoomVersionId, UserId,
|
||||
api::client::room::{self, create_room},
|
||||
api::{
|
||||
client::room::{self, create_room},
|
||||
error::ErrorKind::Forbidden,
|
||||
},
|
||||
assign,
|
||||
events::{
|
||||
TimelineEventType,
|
||||
AnyStateEventContent, StateEventType, TimelineEventType,
|
||||
room::{
|
||||
canonical_alias::RoomCanonicalAliasEventContent,
|
||||
create::RoomCreateEventContent,
|
||||
@@ -512,30 +515,42 @@ pub(crate) async fn create_room_route(
|
||||
|
||||
// 7. Events listed in initial_state
|
||||
for event in &body.initial_state {
|
||||
let mut partial_pdu = event
|
||||
.deserialize_as_unchecked::<PartialPdu>()
|
||||
.map_err(|e| {
|
||||
err!(Request(InvalidParam(warn!("Invalid initial state event: {e:?}"))))
|
||||
let event_type: StateEventType = event.get_field("type")?.ok_or_else(|| {
|
||||
err!(Request(InvalidParam(warn!(
|
||||
r#"Invalid initial state event: missing "type" field "#
|
||||
))))
|
||||
})?;
|
||||
|
||||
let content: Raw<AnyStateEventContent> =
|
||||
event.get_field("content")?.ok_or_else(|| {
|
||||
err!(Request(InvalidParam(warn!(
|
||||
r#"Invalid initial state event: missing "content" field "#
|
||||
))))
|
||||
})?;
|
||||
|
||||
debug_info!("Room creation initial state event: {event:?}");
|
||||
let state_key: String = event.get_field("state_key")?.unwrap_or_default();
|
||||
|
||||
// Implicit state key defaults to ""
|
||||
partial_pdu.state_key.get_or_insert_with(StateKey::new);
|
||||
|
||||
// Silently skip encryption events if they are not allowed
|
||||
if partial_pdu.event_type == TimelineEventType::RoomEncryption
|
||||
&& !services.config.allow_encryption
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
services
|
||||
match services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(partial_pdu, sender_user, Some(&room_id), &state_lock)
|
||||
.send_state_event_for_key(
|
||||
sender_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
&event_type,
|
||||
&content,
|
||||
&state_key,
|
||||
None,
|
||||
)
|
||||
.boxed()
|
||||
.await?;
|
||||
.await
|
||||
{
|
||||
| Err(Error::Request(Forbidden, ..)) => {
|
||||
// Silently skip forbidden events
|
||||
continue;
|
||||
},
|
||||
| r => r,
|
||||
}?;
|
||||
}
|
||||
|
||||
// 8. Events implied by name and topic
|
||||
|
||||
+26
-281
@@ -8,22 +8,11 @@ use conduwuit::{
|
||||
use conduwuit_service::Services;
|
||||
use futures::{FutureExt, TryStreamExt};
|
||||
use ruma::{
|
||||
MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, UserId,
|
||||
RoomId,
|
||||
api::client::state::{
|
||||
get_state_event_for_key::{self, v3::StateEventFormat},
|
||||
get_state_events, send_state_event,
|
||||
},
|
||||
events::{
|
||||
AnyStateEventContent, StateEventType,
|
||||
room::{
|
||||
canonical_alias::RoomCanonicalAliasEventContent,
|
||||
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
|
||||
join_rules::{JoinRule, RoomJoinRulesEventContent},
|
||||
member::{MembershipState, RoomMemberEventContent},
|
||||
server_acl::RoomServerAclEventContent,
|
||||
},
|
||||
},
|
||||
serde::Raw,
|
||||
};
|
||||
use serde_json::{json, value::to_raw_value};
|
||||
|
||||
@@ -47,21 +36,31 @@ pub(crate) async fn send_state_event_for_key_route(
|
||||
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
|
||||
}
|
||||
|
||||
let event_id = send_state_event_for_key_helper(
|
||||
&services,
|
||||
sender_user,
|
||||
&body.room_id,
|
||||
&body.event_type,
|
||||
&body.body.body,
|
||||
&body.state_key,
|
||||
if body.identity.is_appservice() {
|
||||
body.timestamp
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)
|
||||
.boxed()
|
||||
.await?;
|
||||
let state_lock = services
|
||||
.rooms
|
||||
.state
|
||||
.mutex
|
||||
.lock::<RoomId>(&body.room_id)
|
||||
.await;
|
||||
|
||||
let event_id = services
|
||||
.rooms
|
||||
.timeline
|
||||
.send_state_event_for_key(
|
||||
sender_user,
|
||||
&body.room_id,
|
||||
&state_lock,
|
||||
&body.event_type,
|
||||
&body.body.body,
|
||||
&body.state_key,
|
||||
if body.identity.is_appservice() {
|
||||
body.timestamp
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
Ok(send_state_event::v3::Response::new(event_id))
|
||||
}
|
||||
@@ -185,257 +184,3 @@ pub(crate) async fn get_state_events_for_empty_key_route(
|
||||
.await
|
||||
.map(RumaResponse)
|
||||
}
|
||||
|
||||
async fn send_state_event_for_key_helper(
|
||||
services: &Services,
|
||||
sender: &UserId,
|
||||
room_id: &RoomId,
|
||||
event_type: &StateEventType,
|
||||
json: &Raw<AnyStateEventContent>,
|
||||
state_key: &str,
|
||||
timestamp: Option<MilliSecondsSinceUnixEpoch>,
|
||||
) -> Result<OwnedEventId> {
|
||||
let json: &mut Raw<AnyStateEventContent> = &mut json.clone();
|
||||
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 event_id = services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PartialPdu {
|
||||
event_type: event_type.to_string().into(),
|
||||
content: serde_json::from_str(json.json().get())?,
|
||||
state_key: Some(state_key.into()),
|
||||
timestamp,
|
||||
..Default::default()
|
||||
},
|
||||
sender,
|
||||
Some(room_id),
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(event_id)
|
||||
}
|
||||
|
||||
async fn allowed_to_send_state_event(
|
||||
services: &Services,
|
||||
room_id: &RoomId,
|
||||
event_type: &StateEventType,
|
||||
state_key: &str,
|
||||
json: &mut Raw<AnyStateEventContent>,
|
||||
) -> Result {
|
||||
match event_type {
|
||||
| StateEventType::RoomCreate => {
|
||||
return Err!(Request(BadJson(debug_warn!(
|
||||
%room_id,
|
||||
"You cannot update m.room.create after a room has been created."
|
||||
))));
|
||||
},
|
||||
| StateEventType::RoomServerAcl => {
|
||||
// prevents common ACL paw-guns as ACL management is difficult and prone to
|
||||
// irreversible mistakes
|
||||
match json.deserialize_as_unchecked::<RoomServerAclEventContent>() {
|
||||
| Ok(acl_content) => {
|
||||
let allow_has_wildcard = acl_content.allow.iter().any(|entry| entry == "*");
|
||||
let deny_has_wildcard = acl_content.deny.iter().any(|entry| entry == "*");
|
||||
let allow_has_server = acl_content
|
||||
.allow
|
||||
.iter()
|
||||
.any(|entry| entry == services.globals.server_name().as_str());
|
||||
|
||||
if acl_content.allow.is_empty() {
|
||||
return Err!(Request(BadJson(debug_warn!(
|
||||
%room_id,
|
||||
"Sending an ACL event with an empty allow key will permanently \
|
||||
brick the room for non-conduwuit's as this equates to no servers \
|
||||
being allowed to participate in this room."
|
||||
))));
|
||||
}
|
||||
|
||||
if allow_has_wildcard && deny_has_wildcard {
|
||||
return Err!(Request(BadJson(debug_warn!(
|
||||
%room_id,
|
||||
"Sending an ACL event with a deny and allow key value of \"*\" will \
|
||||
permanently brick the room for non-conduwuit's as this equates to \
|
||||
no servers being allowed to participate in this room."
|
||||
))));
|
||||
}
|
||||
|
||||
if deny_has_wildcard
|
||||
&& !acl_content.is_allowed(services.globals.server_name())
|
||||
&& !allow_has_server
|
||||
{
|
||||
return Err!(Request(BadJson(debug_warn!(
|
||||
%room_id,
|
||||
"Sending an ACL event with a deny key value of \"*\" and without \
|
||||
your own server name in the allow key will result in you being \
|
||||
unable to participate in this room."
|
||||
))));
|
||||
}
|
||||
|
||||
if !allow_has_wildcard
|
||||
&& !acl_content.is_allowed(services.globals.server_name())
|
||||
&& !allow_has_server
|
||||
{
|
||||
return Err!(Request(BadJson(debug_warn!(
|
||||
%room_id,
|
||||
"Sending an ACL event for an allow key without \"*\" and without \
|
||||
your own server name in the allow key will result in you being \
|
||||
unable to participate in this room."
|
||||
))));
|
||||
}
|
||||
},
|
||||
| Err(e) => {
|
||||
return Err!(Request(BadJson(debug_warn!(
|
||||
"Room server ACL event is invalid: {e}"
|
||||
))));
|
||||
},
|
||||
}
|
||||
},
|
||||
| StateEventType::RoomEncryption =>
|
||||
// Forbid m.room.encryption if encryption is disabled
|
||||
if !services.config.allow_encryption {
|
||||
return Err!(Request(Forbidden("Encryption is disabled on this homeserver.")));
|
||||
},
|
||||
| StateEventType::RoomJoinRules => {
|
||||
// admin room is a sensitive room, it should not ever be made public
|
||||
if let Ok(admin_room_id) = services.admin.get_admin_room().await {
|
||||
if admin_room_id == room_id {
|
||||
match json.deserialize_as_unchecked::<RoomJoinRulesEventContent>() {
|
||||
| Ok(join_rule) =>
|
||||
if join_rule.join_rule == JoinRule::Public {
|
||||
return Err!(Request(Forbidden(
|
||||
"Admin room is a sensitive room, it cannot be made public"
|
||||
)));
|
||||
},
|
||||
| Err(e) => {
|
||||
return Err!(Request(BadJson(debug_warn!(
|
||||
"Room join rules event is invalid: {e}"
|
||||
))));
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
| StateEventType::RoomHistoryVisibility => {
|
||||
// admin room is a sensitive room, it should not ever be made world readable
|
||||
if let Ok(admin_room_id) = services.admin.get_admin_room().await {
|
||||
match json.deserialize_as_unchecked::<RoomHistoryVisibilityEventContent>() {
|
||||
| Ok(visibility_content) => {
|
||||
if admin_room_id == room_id
|
||||
&& visibility_content.history_visibility
|
||||
== HistoryVisibility::WorldReadable
|
||||
{
|
||||
return Err!(Request(Forbidden(
|
||||
"Admin room is a sensitive room, it cannot be made world \
|
||||
readable (public room history)."
|
||||
)));
|
||||
}
|
||||
},
|
||||
| Err(e) => {
|
||||
return Err!(Request(BadJson(debug_warn!(
|
||||
"Room history visibility event is invalid: {e}"
|
||||
))));
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
| StateEventType::RoomCanonicalAlias => {
|
||||
match json.deserialize_as_unchecked::<RoomCanonicalAliasEventContent>() {
|
||||
| Ok(canonical_alias_content) => {
|
||||
let mut aliases = canonical_alias_content.alt_aliases.clone();
|
||||
|
||||
if let Some(alias) = canonical_alias_content.alias {
|
||||
aliases.push(alias);
|
||||
}
|
||||
|
||||
for alias in aliases {
|
||||
let (alias_room_id, _) = services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_alias(&alias)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
err!(Request(Unknown("Failed resolving alias \"{alias}\": {e}")))
|
||||
})?;
|
||||
|
||||
if alias_room_id != room_id {
|
||||
return Err!(Request(BadAlias(
|
||||
"Room alias {alias} does not belong to room {room_id}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
},
|
||||
| Err(e) => {
|
||||
return Err!(Request(InvalidParam(debug_warn!(
|
||||
"Room canonical alias event is invalid: {e}"
|
||||
))));
|
||||
},
|
||||
}
|
||||
},
|
||||
| StateEventType::RoomMember =>
|
||||
match json.deserialize_as_unchecked::<RoomMemberEventContent>() {
|
||||
| Ok(mut membership_content) => {
|
||||
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
|
||||
{
|
||||
// join_authorized_via_users_server must be thrown away, if user is
|
||||
// already a member of the room.
|
||||
if services
|
||||
.rooms
|
||||
.state_cache
|
||||
.is_joined(&state_key, room_id)
|
||||
.await
|
||||
{
|
||||
membership_content.join_authorized_via_users_server = None;
|
||||
*json = Raw::<AnyStateEventContent>::from_json_string(
|
||||
serde_json::to_string(&membership_content)?,
|
||||
)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if membership_content.membership != MembershipState::Join {
|
||||
return Err!(Request(BadJson(
|
||||
"join_authorised_via_users_server is only for member joins"
|
||||
)));
|
||||
}
|
||||
|
||||
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."
|
||||
)));
|
||||
}
|
||||
}
|
||||
},
|
||||
| Err(e) => {
|
||||
return Err!(Request(BadJson(
|
||||
"Membership content must have a valid JSON body with at least a valid \
|
||||
membership state: {e}"
|
||||
)));
|
||||
},
|
||||
},
|
||||
| _ => (),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
use ruma::{
|
||||
events::{AnyStateEventContent, room::member::RoomMemberEventContent},
|
||||
serde::Raw,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
//! Helpers for submitting events with the right checks performed
|
||||
|
||||
use conduwuit::{Err, Result, err, matrix::pdu::PartialPdu};
|
||||
use ruma::{
|
||||
MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, UserId,
|
||||
events::{
|
||||
AnyStateEventContent, StateEventType,
|
||||
room::{
|
||||
canonical_alias::RoomCanonicalAliasEventContent,
|
||||
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
|
||||
join_rules::{JoinRule, RoomJoinRulesEventContent},
|
||||
member::{MembershipState, RoomMemberEventContent},
|
||||
server_acl::RoomServerAclEventContent,
|
||||
},
|
||||
},
|
||||
serde::Raw,
|
||||
};
|
||||
|
||||
use crate::rooms::state::RoomMutexGuard;
|
||||
|
||||
impl super::Service {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn send_state_event_for_key(
|
||||
&self,
|
||||
sender: &UserId,
|
||||
room_id: &RoomId,
|
||||
state_lock: &RoomMutexGuard,
|
||||
event_type: &StateEventType,
|
||||
content: &Raw<AnyStateEventContent>,
|
||||
state_key: &str,
|
||||
timestamp: Option<MilliSecondsSinceUnixEpoch>,
|
||||
) -> Result<OwnedEventId> {
|
||||
let mut content: Raw<AnyStateEventContent> = content.clone();
|
||||
self.assert_allowed_to_send_state_event(room_id, event_type, state_key, &mut content)
|
||||
.await?;
|
||||
|
||||
let content = serde_json::from_str(content.json().get())
|
||||
.map_err(|e| err!(Request(BadJson("Invalid JSON body: {e}"))))?;
|
||||
|
||||
let event_id = self
|
||||
.build_and_append_pdu(
|
||||
PartialPdu {
|
||||
event_type: event_type.to_string().into(),
|
||||
content,
|
||||
state_key: Some(state_key.into()),
|
||||
timestamp,
|
||||
..Default::default()
|
||||
},
|
||||
sender,
|
||||
Some(room_id),
|
||||
state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(event_id)
|
||||
}
|
||||
|
||||
async fn assert_allowed_to_send_state_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: &StateEventType,
|
||||
state_key: &str,
|
||||
json: &mut Raw<AnyStateEventContent>,
|
||||
) -> Result {
|
||||
match event_type {
|
||||
| StateEventType::RoomCreate => {
|
||||
return Err!(Request(BadJson(debug_warn!(
|
||||
%room_id,
|
||||
"You cannot update m.room.create after a room has been created."
|
||||
))));
|
||||
},
|
||||
| StateEventType::RoomServerAcl =>
|
||||
self.assert_allowed_to_send_room_server_acl_event(room_id, json)
|
||||
.await?,
|
||||
| StateEventType::RoomEncryption =>
|
||||
// Forbid m.room.encryption if encryption is disabled
|
||||
if !self.services.config.allow_encryption {
|
||||
return Err!(Request(Forbidden(
|
||||
"Encryption is disabled on this homeserver."
|
||||
)));
|
||||
},
|
||||
| StateEventType::RoomJoinRules =>
|
||||
self.assert_allowed_to_send_room_join_rules_event(room_id, json)
|
||||
.await?,
|
||||
| StateEventType::RoomHistoryVisibility =>
|
||||
self.assert_allowed_to_send_room_history_visibility_event(room_id, json)
|
||||
.await?,
|
||||
| StateEventType::RoomCanonicalAlias =>
|
||||
self.assert_allowed_to_send_room_canonical_alias_event(room_id, json)
|
||||
.await?,
|
||||
| StateEventType::RoomMember =>
|
||||
self.assert_allowed_to_send_room_member_event(room_id, state_key, json)
|
||||
.await?,
|
||||
| _ => (),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn assert_allowed_to_send_room_server_acl_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
json: &Raw<AnyStateEventContent>,
|
||||
) -> Result {
|
||||
// prevents common ACL paw-guns as ACL management is difficult and prone to
|
||||
// irreversible mistakes
|
||||
|
||||
let acl_content = json
|
||||
.deserialize_as_unchecked::<RoomServerAclEventContent>()
|
||||
.map_err(|e| {
|
||||
err!(Request(BadJson(debug_warn!("Room server ACL event is invalid: {e}"))))
|
||||
})?;
|
||||
|
||||
let allow_has_wildcard = acl_content.allow.iter().any(|entry| entry == "*");
|
||||
let deny_has_wildcard = acl_content.deny.iter().any(|entry| entry == "*");
|
||||
let allow_has_server = acl_content
|
||||
.allow
|
||||
.iter()
|
||||
.any(|entry| entry == self.services.globals.server_name().as_str());
|
||||
|
||||
if acl_content.allow.is_empty() {
|
||||
return Err!(Request(BadJson(debug_warn!(
|
||||
%room_id,
|
||||
"Sending an ACL event with an empty allow key will permanently \
|
||||
brick the room for non-conduwuit's as this equates to no servers \
|
||||
being allowed to participate in this room."
|
||||
))));
|
||||
}
|
||||
|
||||
if allow_has_wildcard && deny_has_wildcard {
|
||||
return Err!(Request(BadJson(debug_warn!(
|
||||
%room_id,
|
||||
"Sending an ACL event with a deny and allow key value of \"*\" will \
|
||||
permanently brick the room for non-conduwuit's as this equates to \
|
||||
no servers being allowed to participate in this room."
|
||||
))));
|
||||
}
|
||||
|
||||
if deny_has_wildcard
|
||||
&& !acl_content.is_allowed(self.services.globals.server_name())
|
||||
&& !allow_has_server
|
||||
{
|
||||
return Err!(Request(BadJson(debug_warn!(
|
||||
%room_id,
|
||||
"Sending an ACL event with a deny key value of \"*\" and without \
|
||||
your own server name in the allow key will result in you being \
|
||||
unable to participate in this room."
|
||||
))));
|
||||
}
|
||||
|
||||
if !allow_has_wildcard
|
||||
&& !acl_content.is_allowed(self.services.globals.server_name())
|
||||
&& !allow_has_server
|
||||
{
|
||||
return Err!(Request(BadJson(debug_warn!(
|
||||
%room_id,
|
||||
"Sending an ACL event for an allow key without \"*\" and without \
|
||||
your own server name in the allow key will result in you being \
|
||||
unable to participate in this room."
|
||||
))));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn assert_allowed_to_send_room_join_rules_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
json: &Raw<AnyStateEventContent>,
|
||||
) -> Result {
|
||||
// admin room is a sensitive room, it should not ever be made public
|
||||
if let Ok(admin_room_id) = self.services.admin.get_admin_room().await
|
||||
&& admin_room_id == room_id
|
||||
{
|
||||
let join_rule = json
|
||||
.deserialize_as_unchecked::<RoomJoinRulesEventContent>()
|
||||
.map_err(|e| {
|
||||
err!(Request(BadJson(debug_warn!("Room join rules event is invalid: {e}"))))
|
||||
})?;
|
||||
|
||||
if join_rule.join_rule == JoinRule::Public {
|
||||
return Err!(Request(Forbidden(
|
||||
"Admin room is a sensitive room, it cannot be made public"
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn assert_allowed_to_send_room_history_visibility_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
json: &Raw<AnyStateEventContent>,
|
||||
) -> Result {
|
||||
// admin room is a sensitive room, it should not ever be made world readable
|
||||
|
||||
if let Ok(admin_room_id) = self.services.admin.get_admin_room().await
|
||||
&& admin_room_id == room_id
|
||||
{
|
||||
let visibility_content = json
|
||||
.deserialize_as_unchecked::<RoomHistoryVisibilityEventContent>()
|
||||
.map_err(|e| {
|
||||
err!(Request(BadJson(debug_warn!(
|
||||
"Room history visibility event is invalid: {e}"
|
||||
))))
|
||||
})?;
|
||||
|
||||
if visibility_content.history_visibility == HistoryVisibility::WorldReadable {
|
||||
return Err!(Request(Forbidden(
|
||||
"Admin room is a sensitive room, it cannot be made public"
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn assert_allowed_to_send_room_canonical_alias_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
json: &Raw<AnyStateEventContent>,
|
||||
) -> Result {
|
||||
let canonical_alias_content = json
|
||||
.deserialize_as_unchecked::<RoomCanonicalAliasEventContent>()
|
||||
.map_err(|e| {
|
||||
err!(Request(BadJson(debug_warn!("Room canonical alias event is invalid: {e}"))))
|
||||
})?;
|
||||
|
||||
let mut aliases = canonical_alias_content.alt_aliases.clone();
|
||||
|
||||
if let Some(alias) = canonical_alias_content.alias {
|
||||
aliases.push(alias);
|
||||
}
|
||||
|
||||
for alias in aliases {
|
||||
let (alias_room_id, _) = self
|
||||
.services
|
||||
.alias
|
||||
.resolve_alias(&alias)
|
||||
.await
|
||||
.map_err(|e| err!(Request(Unknown("Failed resolving alias \"{alias}\": {e}"))))?;
|
||||
|
||||
if alias_room_id != room_id {
|
||||
return Err!(Request(BadAlias(
|
||||
"Room alias {alias} does not belong to room {room_id}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn assert_allowed_to_send_room_member_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
state_key: &str,
|
||||
json: &mut Raw<AnyStateEventContent>,
|
||||
) -> Result {
|
||||
let mut membership_content = json
|
||||
.deserialize_as_unchecked::<RoomMemberEventContent>()
|
||||
.map_err(|e| {
|
||||
err!(Request(BadJson(debug_warn!("Room member event is invalid: {e}"))))
|
||||
})?;
|
||||
|
||||
let Ok(state_key) = UserId::parse(state_key) else {
|
||||
return Err!(Request(BadJson(
|
||||
"Membership event has invalid or non-existent state key"
|
||||
)));
|
||||
};
|
||||
|
||||
let Some(authorising_user) = membership_content.join_authorized_via_users_server else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// join_authorized_via_users_server must be thrown away, if user is
|
||||
// already a member of the room.
|
||||
if self
|
||||
.services
|
||||
.state_cache
|
||||
.is_joined(&state_key, room_id)
|
||||
.await
|
||||
{
|
||||
membership_content.join_authorized_via_users_server = None;
|
||||
*json = Raw::<AnyStateEventContent>::from_json_string(serde_json::to_string(
|
||||
&membership_content,
|
||||
)?)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if membership_content.membership != MembershipState::Join {
|
||||
return Err!(Request(BadJson(
|
||||
"join_authorised_via_users_server is only for member joins"
|
||||
)));
|
||||
}
|
||||
|
||||
if !self.services.globals.user_is_local(&authorising_user) {
|
||||
return Err!(Request(InvalidParam(
|
||||
"Authorising user {authorising_user} does not belong to this homeserver"
|
||||
)));
|
||||
}
|
||||
|
||||
if !self
|
||||
.services
|
||||
.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."
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ mod backfill;
|
||||
mod build;
|
||||
mod create;
|
||||
mod data;
|
||||
mod helpers;
|
||||
mod redact;
|
||||
|
||||
use std::{fmt::Write, sync::Arc};
|
||||
@@ -28,8 +29,8 @@ use serde::Deserialize;
|
||||
use self::data::Data;
|
||||
pub use self::{create::pdu_fits, data::PdusIterItem};
|
||||
use crate::{
|
||||
Dep, account_data, admin, appservice, globals, pusher, rooms, sending, server_keys, sync,
|
||||
users,
|
||||
Dep, account_data, admin, appservice, config, globals, pusher, rooms, sending, server_keys,
|
||||
sync, users,
|
||||
};
|
||||
|
||||
// Update Relationships
|
||||
@@ -67,6 +68,7 @@ struct Services {
|
||||
admin: Dep<admin::Service>,
|
||||
alias: Dep<rooms::alias::Service>,
|
||||
event_handler: Dep<rooms::event_handler::Service>,
|
||||
config: Dep<config::Service>,
|
||||
globals: Dep<globals::Service>,
|
||||
pdu_metadata: Dep<rooms::pdu_metadata::Service>,
|
||||
pusher: Dep<pusher::Service>,
|
||||
@@ -98,6 +100,7 @@ impl crate::Service for Service {
|
||||
appservice: args.depend::<appservice::Service>("appservice"),
|
||||
admin: args.depend::<admin::Service>("admin"),
|
||||
alias: args.depend::<rooms::alias::Service>("rooms::alias"),
|
||||
config: args.depend::<config::Service>("config"),
|
||||
event_handler: args
|
||||
.depend::<rooms::event_handler::Service>("rooms::event_handler"),
|
||||
globals: args.depend::<globals::Service>("globals"),
|
||||
|
||||
Reference in new issue
Block a user