Files
continuwuity/src/api/server/invite.rs
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

333 lines
9.6 KiB
Rust
Raw Normal View History

2026-07-11 06:09:42 +01:00
use std::collections::{HashMap, hash_map::Entry};
2024-07-16 08:05:25 +00:00
use axum::extract::State;
2026-04-23 20:02:48 +01:00
use axum_client_ip::ClientIp;
use base64::{Engine as _, engine::general_purpose};
use conduwuit::{
2026-07-11 06:09:42 +01:00
Err, Error, EventTypeExt, PduEvent, Result, err, error,
matrix::{Event, StateKey, event::gen_event_id},
utils::{self, hash::sha256},
warn,
};
2024-06-05 04:32:58 +00:00
use ruma::{
2026-07-11 06:09:42 +01:00
CanonicalJsonValue, OwnedEventId, UserId,
api::{
error::{ErrorKind, IncompatibleRoomVersionErrorData},
federation::membership::{RawStrippedState, create_invite},
},
2026-07-11 06:09:42 +01:00
events::{
StateEventType,
room::member::{MembershipState, RoomMemberEventContent},
},
2024-06-05 04:32:58 +00:00
serde::JsonObject,
};
2024-07-03 21:05:24 +00:00
use crate::Ruma;
2024-06-05 04:32:58 +00:00
/// # `PUT /_matrix/federation/v2/invite/{roomId}/{eventId}`
///
/// Invites a remote user to a room.
2026-01-04 03:04:37 +00:00
#[tracing::instrument(skip_all, fields(%client), name = "invite", level = "info")]
pub(crate) async fn create_invite_route(
State(services): State<crate::State>,
2026-04-23 20:02:48 +01:00
ClientIp(client): ClientIp,
2024-07-16 08:05:25 +00:00
body: Ruma<create_invite::v2::Request>,
) -> 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
.acl_check(&body.identity, &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(
ErrorKind::IncompatibleRoomVersion(IncompatibleRoomVersionErrorData::new(
body.room_version.clone(),
)),
2024-06-05 04:32:58 +00:00
"Server does not support this room version.",
));
}
let room_version_rules = body.room_version.rules().unwrap();
2024-06-05 04:32:58 +00:00
if let Some(server) = body.room_id.server_name() {
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
.moderation
.is_remote_server_forbidden(&body.identity)
2024-06-05 04:32:58 +00:00
{
warn!(
"Received federated/remote invite from banned server {} for room ID {}. Rejecting.",
body.identity, body.room_id
2024-06-05 04:32:58 +00:00
);
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)
.map_err(|_| err!(Request(InvalidParam("Invite event is invalid."))))?;
2024-06-05 04:32:58 +00:00
2026-07-11 06:09:42 +01:00
services
.server_keys
.verify_event(&signed_event, &room_version_rules)
.await
.map_err(|e| {
err!(Request(InvalidParam("Signature verification failed on invite event: {e}")))
})?;
2025-12-21 08:41:56 +00:00
// Ensure this is a membership event
if signed_event
.get("type")
.expect("event must have a type")
.as_str()
.expect("type must be a string")
!= "m.room.member"
{
return Err!(Request(BadJson(
"Not allowed to send non-membership event to invite endpoint."
)));
}
let content: RoomMemberEventContent = serde_json::from_value(
signed_event
.get("content")
.ok_or_else(|| err!(Request(BadJson("Event missing content property"))))?
.clone()
.into(),
)
.map_err(|e| err!(Request(BadJson(warn!("Event content is empty or invalid: {e}")))))?;
// Ensure this is an invite membership event
if content.membership != MembershipState::Invite {
return Err!(Request(BadJson(
"Not allowed to send a non-invite membership event to invite endpoint."
)));
}
2025-12-21 10:58:50 +00:00
// Ensure the sending user isn't a lying bozo
let sender_user = signed_event
2025-12-21 10:58:50 +00:00
.get("sender")
.and_then(|v| v.as_str())
.map(UserId::parse)
.and_then(Result::ok)
.ok_or_else(|| err!(Request(InvalidParam("Invalid sender property"))))?;
if sender_user.server_name() != body.identity {
2025-12-21 10:58:50 +00:00
return Err!(Request(Forbidden("Sender's server does not match the origin server.",)));
}
2025-12-21 08:41:56 +00:00
// Ensure the target user belongs to this server
let recipient_user = signed_event
.get("state_key")
.and_then(|v| v.as_str())
.map(UserId::parse)
.and_then(Result::ok)
.ok_or_else(|| err!(Request(InvalidParam("Invalid state_key property"))))?;
2024-06-05 04:32:58 +00:00
2025-09-21 17:03:40 +00:00
if !services
.globals
.server_is_ours(recipient_user.server_name())
{
2024-10-03 10:03:31 +00:00
return Err!(Request(InvalidParam("User does not belong to this homeserver.")));
2024-06-05 04:32:58 +00:00
}
2026-07-11 06:09:42 +01:00
// Validate the invite room state.
let mut invite_state_map: HashMap<(StateEventType, StateKey), _> =
HashMap::with_capacity(body.invite_room_state.len());
let mut create_event_id: Option<OwnedEventId> = None;
for (idx, invite_state_event) in body.invite_room_state.iter().cloned().enumerate() {
// Stripped state hasn't been sent over federation since v1.16.
let RawStrippedState::Pdu(raw_pdu) = invite_state_event else {
return Err!(Request(InvalidParam(
"PDU in invite state (index {idx}) violates the room event format"
)));
};
let (state_event_room_id, state_event_id, state_event_json) = services
.rooms
.event_handler
.parse_incoming_pdu(&raw_pdu)
.await
.map_err(|e| err!(Request(InvalidParam("Invalid PDU in invite state: {e}"))))?;
2024-06-05 04:32:58 +00:00
2026-07-11 06:09:42 +01:00
if state_event_room_id != body.room_id {
return Err!(Request(InvalidParam(
"PDU in invite state ({state_event_id}) belongs to the wrong room"
)));
}
2024-06-05 04:32:58 +00:00
2026-07-11 06:09:42 +01:00
services
.server_keys
.verify_event(&signed_event, &room_version_rules)
.await
.map_err(|e| {
err!(Request(InvalidParam("Signature verification failed on invite event: {e}")))
})?;
2024-06-05 04:32:58 +00:00
2026-07-11 06:09:42 +01:00
let Some(state_key) = state_event_json.get("state_key").and_then(|k| k.as_str()) else {
return Err!(Request(InvalidParam(
"PDU in invite state ({state_event_id}) is not a state event"
)));
};
let Some(event_type) = state_event_json.get("event_type").and_then(|k| k.as_str()) else {
return Err!(Request(InvalidParam(
"PDU in invite state ({state_event_id}) is not an event?"
)));
};
let key = StateEventType::from(event_type).with_state_key(state_key);
match invite_state_map.entry(key) {
| Entry::Occupied(entry) =>
return Err!(Request(InvalidParam(
"Duplicate state events in invite state for state key: {:?}",
entry.key(),
))),
| Entry::Vacant(entry) => {
if entry.key().0 == StateEventType::RoomCreate {
let event_id = gen_event_id(&state_event_json, &room_version_rules)
.expect("must be able to generate room ID");
create_event_id = Some(event_id);
}
entry.insert(state_event_json);
},
}
}
let Some(create_event_id) = create_event_id else {
return Err!(Request(InvalidParam(
"Invite state does not contain the m.room.create event"
)));
};
invite_state_map.iter().try_for_each(|(key, event_json)| {
service::rooms::event_handler::Service::pdu_format_check_1(
event_json,
&room_version_rules,
&create_event_id,
)
.map_err(|e| {
err!(Request(InvalidParam(
"PDU in invite state for {key:?} violates the room event format: {e}"
)))
})
})?;
2024-06-05 04:32:58 +00:00
if services.rooms.metadata.is_banned(&body.room_id).await
2025-09-21 17:03:40 +00:00
&& !services.users.is_admin(&recipient_user).await
{
2024-10-03 10:03:31 +00:00
return Err!(Request(Forbidden("This room is banned on this homeserver.")));
2024-06-05 04:32:58 +00:00
}
2025-09-21 17:03:40 +00:00
if services.config.block_non_admin_invites && !services.users.is_admin(&recipient_user).await
{
2024-10-03 10:03:31 +00:00
return Err!(Request(Forbidden("This server does not allow room invites.")));
2024-06-05 04:32:58 +00:00
}
if let Err(e) = services
.antispam
2026-04-13 18:31:16 -04:00
.user_may_invite(sender_user.clone(), recipient_user.clone(), body.room_id.clone())
.await
{
warn!("Antispam rejected invite: {e:?}");
return Err!(Request(Forbidden("Invite rejected by antispam service.")));
}
2026-07-11 06:09:42 +01:00
// Make sure we're not ACL'ed from their room.
services
.rooms
.event_handler
.acl_check(recipient_user.server_name(), &body.room_id)
.await?;
services
.server_keys
.hash_and_sign_event(&mut signed_event, &room_version_rules)
.map_err(|e| err!(Request(InvalidParam("Failed to sign event: {e}"))))?;
// Generate event id
let event_id = gen_event_id(&signed_event, &room_version_rules)?;
// Add event_id back
signed_event.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.to_string()));
2024-06-05 04:32:58 +00:00
let mut invite_state = body.invite_room_state.clone();
let mut event: JsonObject = serde_json::from_str(body.event.get())
2025-01-11 18:43:54 -05:00
.map_err(|e| err!(Request(BadJson("Invalid invite event PDU: {e}"))))?;
2024-06-05 04:32:58 +00:00
event.insert("event_id".to_owned(), "$placeholder".into());
let pdu: PduEvent = serde_json::from_value(event.into())
2025-01-11 18:43:54 -05:00
.map_err(|e| err!(Request(BadJson("Invalid invite event PDU: {e}"))))?;
2024-06-05 04:32:58 +00:00
invite_state.push(RawStrippedState::Pdu(
serde_json::value::to_raw_value(&pdu).expect("PDU was just created, it must be valid"),
));
2024-06-05 04:32:58 +00: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,
&sender_user,
invite_state,
2024-08-08 17:18:30 +00:00
body.via.clone(),
)
.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
services.sync.wake(&recipient_user).await;
for appservice in services.appservice.read().await.values() {
2025-09-21 17:03:40 +00:00
if appservice.is_user_match(&recipient_user) {
let transaction_id = general_purpose::URL_SAFE_NO_PAD
.encode(sha256::hash(pdu.event_id.as_bytes()))
.into();
let request = ruma::api::appservice::event::push_events::v1::Request::new(
transaction_id,
vec![pdu.to_format()],
);
services
.sending
.send_appservice_request(appservice.registration.clone(), request)
.await
.map_err(|e| {
error!(
"failed to notify appservice {} about incoming invite: {e}",
appservice.registration.id
);
err!(BadServerResponse(
"Failed to notify appservice about incoming invite."
))
})?;
}
}
}
Ok(create_invite::v2::Response::new(
services
2024-07-18 06:37:47 +00:00
.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
}