2025-12-23 19:50:37 +00:00
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
2024-07-16 08:05:25 +00:00
|
|
|
use axum::extract::State;
|
2024-06-10 21:04:51 -04:00
|
|
|
use axum_client_ip::InsecureClientIp;
|
2025-02-23 01:17:45 -05:00
|
|
|
use base64::{Engine as _, engine::general_purpose};
|
2025-04-04 03:30:13 +00:00
|
|
|
use conduwuit::{
|
2025-12-23 19:50:37 +00:00
|
|
|
Err, Error, EventTypeExt, PduEvent, Result, RoomVersion, err,
|
|
|
|
|
matrix::{Event, StateKey, event::gen_event_id},
|
2025-04-27 02:39:28 +00:00
|
|
|
utils::{self, hash::sha256},
|
|
|
|
|
warn,
|
2025-04-04 03:30:13 +00:00
|
|
|
};
|
2024-06-05 04:32:58 +00:00
|
|
|
use ruma::{
|
2025-12-23 19:50:37 +00:00
|
|
|
CanonicalJsonValue, OwnedUserId, RoomId, RoomVersionId, ServerName, UserId,
|
2024-06-05 04:32:58 +00:00
|
|
|
api::{client::error::ErrorKind, federation::membership::create_invite},
|
2025-12-23 19:50:37 +00:00
|
|
|
events::{
|
|
|
|
|
AnyStateEvent, StateEventType,
|
|
|
|
|
room::{
|
|
|
|
|
create::RoomCreateEventContent,
|
|
|
|
|
member::{MembershipState, RoomMemberEventContent},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
serde::Raw,
|
2024-06-05 04:32:58 +00:00
|
|
|
};
|
2025-12-23 19:50:37 +00:00
|
|
|
use service::{Services, rooms::timeline::pdu_fits};
|
2024-06-05 04:32:58 +00:00
|
|
|
|
2024-07-03 21:05:24 +00:00
|
|
|
use crate::Ruma;
|
2024-06-05 04:32:58 +00:00
|
|
|
|
2025-12-23 19:50:37 +00:00
|
|
|
/// Ensures that the state received from the invite endpoint is sane, correct,
|
|
|
|
|
/// and complies with the room version's requirements.
|
|
|
|
|
async fn check_invite_state(
|
|
|
|
|
services: &Services,
|
|
|
|
|
stripped_state: &Vec<Raw<AnyStateEvent>>,
|
|
|
|
|
room_id: &RoomId,
|
|
|
|
|
room_version_id: &RoomVersionId,
|
|
|
|
|
) -> Result<()> {
|
|
|
|
|
let room_version = RoomVersion::new(room_version_id).map_err(|e| {
|
|
|
|
|
err!(Request(UnsupportedRoomVersion("Invalid room version provided: {e}")))
|
|
|
|
|
})?;
|
|
|
|
|
let mut room_state: HashMap<(StateEventType, StateKey), PduEvent> = HashMap::new();
|
|
|
|
|
|
|
|
|
|
// Build the room state from the provided state events,
|
|
|
|
|
// ensuring that there's no duplicates. We need to check that m.room.create is
|
|
|
|
|
// present and lines up with the other things we've been told, and then verify
|
|
|
|
|
// any signatures present to ensure this isn't forged.
|
|
|
|
|
for raw_event in stripped_state {
|
|
|
|
|
let event = raw_event
|
|
|
|
|
.deserialize_as::<PduEvent>()
|
|
|
|
|
.map_err(|e| err!(Request(InvalidParam("Invalid state event: {e}"))))?;
|
|
|
|
|
if event.state_key().is_none() {
|
|
|
|
|
return Err!(Request(InvalidParam("State event missing event type.")));
|
|
|
|
|
}
|
|
|
|
|
let key = event
|
|
|
|
|
.event_type()
|
|
|
|
|
.with_state_key(event.state_key().unwrap());
|
|
|
|
|
if room_state.contains_key(&key) {
|
|
|
|
|
return Err!(Request(InvalidParam("Duplicate state event found for {key:?}")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// verify the event
|
|
|
|
|
let canonical = utils::to_canonical_object(raw_event)?;
|
|
|
|
|
if !pdu_fits(&mut canonical.clone()) {
|
|
|
|
|
return Err!(Request(InvalidParam("An invite state event PDU is too large")));
|
|
|
|
|
}
|
|
|
|
|
services
|
|
|
|
|
.server_keys
|
|
|
|
|
.verify_event(&canonical, Some(room_version_id))
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| err!(Request(InvalidParam("Signature failed verification: {e}"))))?;
|
|
|
|
|
|
|
|
|
|
// Ensure all events are in the same room
|
|
|
|
|
if event.room_id_or_hash() != room_id {
|
|
|
|
|
return Err!(Request(InvalidParam(
|
|
|
|
|
"State event room ID for {} does not match the expected room ID {}.",
|
|
|
|
|
event.event_id,
|
|
|
|
|
room_id,
|
|
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
room_state.insert(key, event);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// verify m.room.create is present, has a matching room ID, and a matching room
|
|
|
|
|
// version.
|
|
|
|
|
let create_event = room_state
|
|
|
|
|
.get(&(StateEventType::RoomCreate, "".into()))
|
|
|
|
|
.ok_or_else(|| err!(Request(MissingParam("Missing m.room.create in stripped state."))))?;
|
|
|
|
|
let create_event_content: RoomCreateEventContent = create_event
|
|
|
|
|
.get_content()
|
|
|
|
|
.map_err(|e| err!(Request(InvalidParam("Invalid m.room.create content: {e}"))))?;
|
|
|
|
|
// Room v12 removed room IDs over federation, so we'll need to see if the event
|
|
|
|
|
// ID matches the room ID instead.
|
|
|
|
|
if room_version.room_ids_as_hashes {
|
|
|
|
|
let given_room_id = create_event.event_id().as_str().replace('$', "!");
|
|
|
|
|
if given_room_id != room_id.as_str() {
|
|
|
|
|
return Err!(Request(InvalidParam(
|
|
|
|
|
"m.room.create event ID does not match the room ID."
|
|
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
} else if create_event.room_id().unwrap() != room_id {
|
|
|
|
|
return Err!(Request(InvalidParam("m.room.create room ID does not match the room ID.")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Make sure the room version matches
|
|
|
|
|
if &create_event_content.room_version != room_version_id {
|
|
|
|
|
return Err!(Request(InvalidParam(
|
|
|
|
|
"m.room.create room version does not match the given room version."
|
|
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Looks solid
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Ensures that the invite event received from the invite endpoint is sane,
|
|
|
|
|
/// correct, and complies with the room version's requirements.
|
|
|
|
|
/// Returns the invited user ID on success.
|
|
|
|
|
async fn check_invite_event(
|
|
|
|
|
services: &Services,
|
|
|
|
|
invite_event: &PduEvent,
|
|
|
|
|
origin: &ServerName,
|
|
|
|
|
room_id: &RoomId,
|
|
|
|
|
room_version_id: &RoomVersionId,
|
|
|
|
|
) -> Result<OwnedUserId> {
|
|
|
|
|
// Check: The event sender is not a user ID on the origin server.
|
|
|
|
|
if invite_event.sender.server_name() != origin {
|
|
|
|
|
return Err!(Request(InvalidParam(
|
|
|
|
|
"Invite event sender's server does not match the origin server."
|
|
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
// Check: The `state_key` is not a user ID on the receiving server.
|
|
|
|
|
let state_key: &UserId = invite_event
|
|
|
|
|
.state_key()
|
|
|
|
|
.ok_or_else(|| err!(Request(MissingParam("Invite event missing state_key."))))?
|
|
|
|
|
.try_into()
|
|
|
|
|
.map_err(|e| err!(Request(InvalidParam("Invalid state_key property: {e}"))))?;
|
|
|
|
|
if !services.globals.server_is_ours(state_key.server_name()) {
|
|
|
|
|
return Err!(Request(InvalidParam(
|
|
|
|
|
"Invite event state_key does not belong to this homeserver."
|
|
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check: The event's room ID matches the expected room ID.
|
|
|
|
|
if let Some(evt_room_id) = invite_event.room_id() {
|
|
|
|
|
if evt_room_id != room_id {
|
|
|
|
|
return Err!(Request(InvalidParam(
|
|
|
|
|
"Invite event room ID does not match the expected room ID."
|
|
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
return Err!(Request(MissingParam("Invite event missing room ID.")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check: the membership really is "invite"
|
|
|
|
|
let content = invite_event.get_content::<RoomMemberEventContent>()?;
|
|
|
|
|
if content.membership != MembershipState::Invite {
|
|
|
|
|
return Err!(Request(InvalidParam("Invite event is not a membership invite.")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check: signature is valid
|
|
|
|
|
services
|
|
|
|
|
.server_keys
|
|
|
|
|
.verify_event(&utils::to_canonical_object(invite_event)?, Some(room_version_id))
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| {
|
|
|
|
|
err!(Request(InvalidParam("Invite event signature failed verification: {e}")))
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
Ok(state_key.to_owned())
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-05 04:32:58 +00:00
|
|
|
/// # `PUT /_matrix/federation/v2/invite/{roomId}/{eventId}`
|
|
|
|
|
///
|
|
|
|
|
/// Invites a remote user to a room.
|
2024-06-17 04:12:11 +00:00
|
|
|
#[tracing::instrument(skip_all, fields(%client), name = "invite")]
|
2024-06-10 21:04:51 -04:00
|
|
|
pub(crate) async fn create_invite_route(
|
2024-12-15 00:05:47 -05:00
|
|
|
State(services): State<crate::State>,
|
|
|
|
|
InsecureClientIp(client): InsecureClientIp,
|
2024-07-16 08:05:25 +00:00
|
|
|
body: Ruma<create_invite::v2::Request>,
|
2024-06-10 21:04:51 -04:00
|
|
|
) -> Result<create_invite::v2::Response> {
|
2024-06-05 04:32:58 +00:00
|
|
|
// ACL check origin
|
2024-07-16 08:05:25 +00:00
|
|
|
services
|
2024-06-05 04:32:58 +00:00
|
|
|
.rooms
|
|
|
|
|
.event_handler
|
2024-10-24 12:03:56 +00:00
|
|
|
.acl_check(body.origin(), &body.room_id)
|
2024-08-08 17:18:30 +00:00
|
|
|
.await?;
|
2024-06-05 04:32:58 +00:00
|
|
|
|
2024-12-05 07:23:51 +00:00
|
|
|
if !services.server.supported_room_version(&body.room_version) {
|
2024-06-05 04:32:58 +00:00
|
|
|
return Err(Error::BadRequest(
|
2024-12-15 00:05:47 -05:00
|
|
|
ErrorKind::IncompatibleRoomVersion { room_version: body.room_version.clone() },
|
2024-06-05 04:32:58 +00:00
|
|
|
"Server does not support this room version.",
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(server) = body.room_id.server_name() {
|
2025-04-19 23:02:43 +01:00
|
|
|
if services.moderation.is_remote_server_forbidden(server) {
|
2024-10-03 10:03:31 +00:00
|
|
|
return Err!(Request(Forbidden("Server is banned on this homeserver.")));
|
2024-06-05 04:32:58 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-16 08:05:25 +00:00
|
|
|
if services
|
2025-04-19 23:02:43 +01:00
|
|
|
.moderation
|
|
|
|
|
.is_remote_server_forbidden(body.origin())
|
2024-06-05 04:32:58 +00:00
|
|
|
{
|
|
|
|
|
warn!(
|
2024-10-24 12:03:56 +00:00
|
|
|
"Received federated/remote invite from banned server {} for room ID {}. Rejecting.",
|
|
|
|
|
body.origin(),
|
2024-06-05 04:32:58 +00:00
|
|
|
body.room_id
|
|
|
|
|
);
|
2024-10-03 10:03:31 +00:00
|
|
|
|
|
|
|
|
return Err!(Request(Forbidden("Server is banned on this homeserver.")));
|
2024-06-05 04:32:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut signed_event = utils::to_canonical_object(&body.event)
|
2025-04-26 23:50:03 +00:00
|
|
|
.map_err(|_| err!(Request(InvalidParam("Invite event is invalid."))))?;
|
2024-06-05 04:32:58 +00:00
|
|
|
|
2025-12-23 19:50:37 +00:00
|
|
|
// We need to hash and sign the event before we can generate the event ID.
|
|
|
|
|
// It is important that this signed event does not get sent back to the caller
|
|
|
|
|
// until we've verified this isn't incorrect.
|
|
|
|
|
services
|
|
|
|
|
.server_keys
|
|
|
|
|
.hash_and_sign_event(&mut signed_event, &body.room_version)
|
|
|
|
|
.map_err(|e| err!(Request(InvalidParam("Failed to sign event: {e}"))))?;
|
|
|
|
|
let event_id = gen_event_id(&signed_event.clone(), &body.room_version)?;
|
|
|
|
|
if event_id != body.event_id {
|
|
|
|
|
return Err!(Request(InvalidParam(
|
|
|
|
|
"Event ID does not match the generated event ID ({} vs {}).",
|
|
|
|
|
event_id,
|
|
|
|
|
body.event_id,
|
2025-12-21 08:41:56 +00:00
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-23 19:50:37 +00:00
|
|
|
let pdu = PduEvent::from_id_val(&event_id, signed_event.clone())
|
|
|
|
|
.map_err(|e| err!(Request(InvalidParam("Invite event is invalid: {e}"))))?;
|
2025-12-21 08:41:56 +00:00
|
|
|
|
2025-12-23 19:50:37 +00:00
|
|
|
// Check the invite event is valid.
|
|
|
|
|
let recipient_user =
|
|
|
|
|
check_invite_event(&services, &pdu, body.origin(), &body.room_id, &body.room_version)
|
|
|
|
|
.await?;
|
2025-12-21 08:41:56 +00:00
|
|
|
|
2025-12-23 19:50:37 +00:00
|
|
|
// Make sure the room isn't banned and we allow invites
|
|
|
|
|
if services.config.block_non_admin_invites && !services.users.is_admin(&recipient_user).await
|
|
|
|
|
{
|
|
|
|
|
return Err!(Request(Forbidden("This server does not allow room invites.")));
|
2025-12-21 10:58:50 +00:00
|
|
|
}
|
2025-12-23 19:50:37 +00:00
|
|
|
if services.rooms.metadata.is_banned(&body.room_id).await
|
|
|
|
|
&& !services.users.is_admin(&recipient_user).await
|
2025-09-21 17:03:40 +00:00
|
|
|
{
|
2025-12-23 19:50:37 +00:00
|
|
|
return Err!(Request(Forbidden("This room is banned on this homeserver.")));
|
2024-06-05 04:32:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Make sure we're not ACL'ed from their room.
|
2024-07-16 08:05:25 +00:00
|
|
|
services
|
2024-06-05 04:32:58 +00:00
|
|
|
.rooms
|
|
|
|
|
.event_handler
|
2025-09-21 17:03:40 +00:00
|
|
|
.acl_check(recipient_user.server_name(), &body.room_id)
|
2024-08-08 17:18:30 +00:00
|
|
|
.await?;
|
2024-06-05 04:32:58 +00:00
|
|
|
|
2025-12-23 19:50:37 +00:00
|
|
|
// And check the invite state is valid.
|
|
|
|
|
check_invite_state(&services, &body.invite_room_state, &body.room_id, &body.room_version)
|
|
|
|
|
.await?;
|
2024-06-05 04:32:58 +00:00
|
|
|
|
|
|
|
|
// Add event_id back
|
|
|
|
|
signed_event.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.to_string()));
|
|
|
|
|
|
2025-09-21 17:03:40 +00:00
|
|
|
let sender_user: &UserId = signed_event
|
2024-10-03 09:57:43 +00:00
|
|
|
.get("sender")
|
|
|
|
|
.try_into()
|
|
|
|
|
.map_err(|e| err!(Request(InvalidParam("Invalid sender property: {e}"))))?;
|
2024-06-05 04:32:58 +00:00
|
|
|
|
|
|
|
|
let mut invite_state = body.invite_room_state.clone();
|
|
|
|
|
|
2025-04-26 08:24:47 +00:00
|
|
|
invite_state.push(pdu.to_format());
|
2024-06-05 04:32:58 +00:00
|
|
|
|
2024-11-10 21:20:38 -05:00
|
|
|
// If we are active in the room, the remote server will notify us about the
|
|
|
|
|
// join/invite through /send. If we are not in the room, we need to manually
|
|
|
|
|
// record the invited state for client /sync through update_membership(), and
|
|
|
|
|
// send the invite PDU to the relevant appservices.
|
2024-07-16 08:05:25 +00:00
|
|
|
if !services
|
2024-06-05 04:32:58 +00:00
|
|
|
.rooms
|
|
|
|
|
.state_cache
|
2024-08-08 17:18:30 +00:00
|
|
|
.server_in_room(services.globals.server_name(), &body.room_id)
|
|
|
|
|
.await
|
2024-06-05 04:32:58 +00:00
|
|
|
{
|
2024-08-08 17:18:30 +00:00
|
|
|
services
|
|
|
|
|
.rooms
|
|
|
|
|
.state_cache
|
2025-10-27 17:24:02 -04:00
|
|
|
.mark_as_invited(
|
2025-09-21 17:03:40 +00:00
|
|
|
&recipient_user,
|
2025-10-27 17:24:02 -04:00
|
|
|
&body.room_id,
|
2025-10-28 10:06:40 -04:00
|
|
|
sender_user,
|
2024-08-08 17:18:30 +00:00
|
|
|
Some(invite_state),
|
|
|
|
|
body.via.clone(),
|
|
|
|
|
)
|
2025-11-05 14:29:03 -05:00
|
|
|
.await?;
|
2025-10-27 17:24:02 -04:00
|
|
|
|
|
|
|
|
services
|
|
|
|
|
.rooms
|
|
|
|
|
.state_cache
|
|
|
|
|
.update_joined_count(&body.room_id)
|
|
|
|
|
.await;
|
2024-06-05 04:32:58 +00:00
|
|
|
|
2024-12-07 01:07:01 -05:00
|
|
|
for appservice in services.appservice.read().await.values() {
|
2025-09-21 17:03:40 +00:00
|
|
|
if appservice.is_user_match(&recipient_user) {
|
2024-12-07 01:07:01 -05:00
|
|
|
services
|
|
|
|
|
.sending
|
|
|
|
|
.send_appservice_request(
|
|
|
|
|
appservice.registration.clone(),
|
|
|
|
|
ruma::api::appservice::event::push_events::v1::Request {
|
2025-04-26 08:24:47 +00:00
|
|
|
events: vec![pdu.to_format()],
|
2024-12-07 01:07:01 -05:00
|
|
|
txn_id: general_purpose::URL_SAFE_NO_PAD
|
|
|
|
|
.encode(sha256::hash(pdu.event_id.as_bytes()))
|
|
|
|
|
.into(),
|
|
|
|
|
ephemeral: Vec::new(),
|
|
|
|
|
to_device: Vec::new(),
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
}
|
2024-11-10 21:20:38 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-05 04:32:58 +00:00
|
|
|
Ok(create_invite::v2::Response {
|
2024-07-18 06:37:47 +00:00
|
|
|
event: services
|
|
|
|
|
.sending
|
2024-08-08 17:18:30 +00:00
|
|
|
.convert_to_outgoing_federation_event(signed_event)
|
|
|
|
|
.await,
|
2024-06-05 04:32:58 +00:00
|
|
|
})
|
|
|
|
|
}
|