Files
continuwuity/src/api/client/membership.rs
T

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

1682 lines
46 KiB
Rust
Raw Normal View History

use std::{
collections::{BTreeMap, HashMap, HashSet},
net::IpAddr,
sync::Arc,
2024-03-05 19:48:54 -05:00
};
2024-07-16 08:05:25 +00:00
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
2024-07-07 03:39:35 +00:00
use conduit::{
debug, debug_info, debug_warn, err, error, info, pdu,
2024-07-22 07:43:51 +00:00
pdu::{gen_event_id_canonical_json, PduBuilder},
result::FlatOk,
2024-07-22 07:43:51 +00:00
trace, utils,
utils::{shuffle, IterStream, ReadyExt},
2024-08-01 10:58:27 +00:00
warn, Err, Error, PduEvent, Result,
2024-07-07 03:39:35 +00:00
};
2024-08-08 17:18:30 +00:00
use futures::{FutureExt, StreamExt};
2020-07-30 18:14:47 +02:00
use ruma::{
api::{
client::{
error::ErrorKind,
2022-02-18 15:33:14 +01:00
membership::{
2020-08-20 12:12:02 -04:00
ban_user, forget_room, get_member_events, invite_user, join_room_by_id, join_room_by_id_or_alias,
joined_members::{self, v3::RoomMember},
joined_rooms, kick_user, leave_room, unban_user, ThirdPartySigned,
2022-12-14 13:09:10 +01:00
},
},
2021-04-25 14:10:07 +02:00
federation::{self, membership::create_invite},
},
2022-10-09 17:25:06 +02:00
canonical_json::to_canonical_value,
2021-04-25 14:10:07 +02:00
events::{
room::{
join_rules::{AllowRule, JoinRule, RoomJoinRulesEventContent},
member::{MembershipState, RoomMemberEventContent},
message::RoomMessageEventContent,
},
StateEventType,
2020-07-30 18:14:47 +02:00
},
state_res, CanonicalJsonObject, CanonicalJsonValue, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId,
RoomVersionId, ServerName, UserId,
2020-07-30 18:14:47 +02:00
};
use service::{
appservice::RegistrationInfo,
rooms::{state::RoomMutexGuard, state_compressor::HashSetCompressStateEvent},
Services,
};
2020-07-30 18:14:47 +02:00
use crate::{client::full_user_deactivate, Ruma};
2022-09-06 23:15:09 +02:00
/// Checks if the room is banned in any way possible and the sender user is not
/// an admin.
///
/// Performs automatic deactivation if `auto_deactivate_banned_room_attempts` is
/// enabled
2024-07-16 08:05:25 +00:00
#[tracing::instrument(skip(services))]
async fn banned_room_check(
2024-07-16 08:05:25 +00:00
services: &Services, user_id: &UserId, room_id: Option<&RoomId>, server_name: Option<&ServerName>,
client_ip: IpAddr,
) -> Result<()> {
2024-08-08 17:18:30 +00:00
if !services.users.is_admin(user_id).await {
if let Some(room_id) = room_id {
2024-08-08 17:18:30 +00:00
if services.rooms.metadata.is_banned(room_id).await
2024-07-16 08:05:25 +00:00
|| services
.globals
.config
.forbidden_remote_server_names
.contains(&room_id.server_name().unwrap().to_owned())
{
warn!(
"User {user_id} who is not an admin attempted to send an invite for or attempted to join a banned \
room or banned room server name: {room_id}"
);
2024-07-16 08:05:25 +00:00
if services.globals.config.auto_deactivate_banned_room_attempts {
warn!("Automatically deactivating user {user_id} due to attempted banned room join");
if services.globals.config.admin_room_notices {
services
.admin
.send_message(RoomMessageEventContent::text_plain(format!(
"Automatically deactivating user {user_id} due to attempted banned room join from IP \
{client_ip}"
)))
2024-08-08 17:18:30 +00:00
.await
.ok();
}
2024-07-16 08:05:25 +00:00
let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms
.state_cache
.rooms_joined(user_id)
2024-08-08 17:18:30 +00:00
.map(Into::into)
.collect()
.await;
2024-08-08 17:18:30 +00:00
full_user_deactivate(services, user_id, &all_joined_rooms).await?;
}
2024-08-08 17:18:30 +00:00
return Err!(Request(Forbidden("This room is banned on this homeserver.")));
}
} else if let Some(server_name) = server_name {
2024-07-16 08:05:25 +00:00
if services
.globals
.config
.forbidden_remote_server_names
.contains(&server_name.to_owned())
{
warn!(
"User {user_id} who is not an admin tried joining a room which has the server name {server_name} \
that is globally forbidden. Rejecting.",
);
2024-07-16 08:05:25 +00:00
if services.globals.config.auto_deactivate_banned_room_attempts {
warn!("Automatically deactivating user {user_id} due to attempted banned room join");
if services.globals.config.admin_room_notices {
services
.admin
.send_message(RoomMessageEventContent::text_plain(format!(
"Automatically deactivating user {user_id} due to attempted banned room join from IP \
{client_ip}"
)))
2024-08-08 17:18:30 +00:00
.await
.ok();
}
2024-07-16 08:05:25 +00:00
let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms
.state_cache
.rooms_joined(user_id)
2024-08-08 17:18:30 +00:00
.map(Into::into)
.collect()
.await;
2024-08-08 17:18:30 +00:00
full_user_deactivate(services, user_id, &all_joined_rooms).await?;
}
2024-08-08 17:18:30 +00:00
return Err!(Request(Forbidden("This remote server is banned on this homeserver.")));
}
}
}
Ok(())
}
2021-08-31 19:14:37 +02:00
/// # `POST /_matrix/client/r0/rooms/{roomId}/join`
///
/// Tries to join the sender user into a room.
///
/// - If the server knowns about this room: creates the join event and does auth
/// rules locally
/// - If the server does not know about the room: asks other servers over
/// federation
2024-06-17 04:12:11 +00:00
#[tracing::instrument(skip_all, fields(%client_ip), name = "join")]
2024-04-22 23:48:57 -04:00
pub(crate) async fn join_room_by_id_route(
2024-07-16 08:05:25 +00:00
State(services): State<crate::State>, InsecureClientIp(client_ip): InsecureClientIp,
body: Ruma<join_room_by_id::v3::Request>,
2024-04-22 23:48:57 -04:00
) -> Result<join_room_by_id::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
2024-03-05 19:48:54 -05:00
2024-07-16 08:05:25 +00:00
banned_room_check(
2024-07-27 07:17:07 +00:00
&services,
2024-07-16 08:05:25 +00:00
sender_user,
Some(&body.room_id),
body.room_id.server_name(),
client_ip,
)
.await?;
2024-04-07 23:24:38 -04:00
// There is no body.server_name for /roomId/join
2024-10-01 02:47:39 +00:00
let mut servers: Vec<_> = services
2024-04-07 23:24:38 -04:00
.rooms
.state_cache
2024-06-10 14:53:26 -04:00
.servers_invite_via(&body.room_id)
2024-08-08 17:18:30 +00:00
.map(ToOwned::to_owned)
2024-10-01 02:47:39 +00:00
.collect()
2024-08-08 17:18:30 +00:00
.await;
2024-06-10 14:53:26 -04:00
servers.extend(
2024-07-16 08:05:25 +00:00
services
2024-06-10 14:53:26 -04:00
.rooms
.state_cache
2024-08-08 17:18:30 +00:00
.invite_state(sender_user, &body.room_id)
.await
2024-06-10 14:53:26 -04:00
.unwrap_or_default()
.iter()
.filter_map(|event| event.get_field("sender").ok().flatten())
.filter_map(|sender: &str| UserId::parse(sender).ok())
2024-06-10 14:53:26 -04:00
.map(|user| user.server_name().to_owned()),
);
2024-03-05 19:48:54 -05:00
2024-02-23 19:37:48 -05:00
if let Some(server) = body.room_id.server_name() {
servers.push(server.into());
}
2024-03-05 19:48:54 -05:00
servers.sort_unstable();
servers.dedup();
shuffle(&mut servers);
2022-10-10 14:09:11 +02:00
join_room_by_id_helper(
2024-07-27 07:17:07 +00:00
&services,
sender_user,
&body.room_id,
body.reason.clone(),
&servers,
body.third_party_signed.as_ref(),
&body.appservice_info,
)
2024-08-08 17:18:30 +00:00
.boxed()
2022-10-10 14:09:11 +02:00
.await
2020-07-30 18:14:47 +02:00
}
2021-08-31 19:14:37 +02:00
/// # `POST /_matrix/client/r0/join/{roomIdOrAlias}`
///
/// Tries to join the sender user into a room.
///
/// - If the server knowns about this room: creates the join event and does auth
/// rules locally
/// - If the server does not know about the room: use the server name query
/// param if specified. if not specified, asks other servers over federation
/// via room alias server name and room ID server name
2024-06-17 04:12:11 +00:00
#[tracing::instrument(skip_all, fields(%client), name = "join")]
2024-04-22 23:48:57 -04:00
pub(crate) async fn join_room_by_id_or_alias_route(
2024-07-16 08:05:25 +00:00
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
body: Ruma<join_room_by_id_or_alias::v3::Request>,
2022-02-18 15:33:14 +01:00
) -> Result<join_room_by_id_or_alias::v3::Response> {
2021-12-15 13:58:25 +01:00
let sender_user = body.sender_user.as_deref().expect("user is authenticated");
let appservice_info = &body.appservice_info;
2021-12-15 13:58:25 +01:00
let body = body.body;
2024-03-05 19:48:54 -05:00
2022-10-09 17:25:06 +02:00
let (servers, room_id) = match OwnedRoomId::try_from(body.room_id_or_alias) {
Ok(room_id) => {
2024-07-27 07:17:07 +00:00
banned_room_check(&services, sender_user, Some(&room_id), room_id.server_name(), client).await?;
let mut servers = body.via.clone();
2022-06-18 16:38:41 +02:00
servers.extend(
2024-07-16 08:05:25 +00:00
services
2022-10-05 20:34:31 +02:00
.rooms
.state_cache
2024-06-10 14:53:26 -04:00
.servers_invite_via(&room_id)
2024-08-08 17:18:30 +00:00
.map(ToOwned::to_owned)
.collect::<Vec<_>>()
.await,
2024-06-10 14:53:26 -04:00
);
servers.extend(
2024-07-16 08:05:25 +00:00
services
2024-06-10 14:53:26 -04:00
.rooms
.state_cache
2024-08-08 17:18:30 +00:00
.invite_state(sender_user, &room_id)
.await
2024-06-10 14:53:26 -04:00
.unwrap_or_default()
.iter()
.filter_map(|event| event.get_field("sender").ok().flatten())
.filter_map(|sender: &str| UserId::parse(sender).ok())
2024-06-10 14:53:26 -04:00
.map(|user| user.server_name().to_owned()),
2022-06-18 16:38:41 +02:00
);
2024-03-05 19:48:54 -05:00
2024-02-23 19:37:48 -05:00
if let Some(server) = room_id.server_name() {
servers.push(server.to_owned());
2024-02-23 19:37:48 -05:00
}
2024-03-05 19:48:54 -05:00
servers.sort_unstable();
servers.dedup();
shuffle(&mut servers);
(servers, room_id)
},
Err(room_alias) => {
let (room_id, mut servers) = services
.rooms
.alias
.resolve_alias(&room_alias, Some(body.via.clone()))
.await?;
2024-03-05 19:48:54 -05:00
2024-07-27 07:17:07 +00:00
banned_room_check(&services, sender_user, Some(&room_id), Some(room_alias.server_name()), client).await?;
let addl_via_servers = services
.rooms
.state_cache
.servers_invite_via(&room_id)
.map(ToOwned::to_owned);
2024-06-10 14:53:26 -04:00
let addl_state_servers = services
.rooms
.state_cache
.invite_state(sender_user, &room_id)
.await
.unwrap_or_default();
let mut addl_servers: Vec<_> = addl_state_servers
.iter()
.map(|event| event.get_field("sender"))
.filter_map(FlatOk::flat_ok)
.map(|user: &UserId| user.server_name().to_owned())
.stream()
.chain(addl_via_servers)
.collect()
.await;
addl_servers.sort_unstable();
addl_servers.dedup();
shuffle(&mut addl_servers);
servers.append(&mut addl_servers);
(servers, room_id)
},
};
2024-03-05 19:48:54 -05:00
let join_room_response = join_room_by_id_helper(
2024-07-27 07:17:07 +00:00
&services,
sender_user,
&room_id,
body.reason.clone(),
&servers,
body.third_party_signed.as_ref(),
appservice_info,
)
2024-08-08 17:18:30 +00:00
.boxed()
.await?;
2024-03-05 19:48:54 -05:00
2022-02-18 15:33:14 +01:00
Ok(join_room_by_id_or_alias::v3::Response {
2022-01-22 16:58:32 +01:00
room_id: join_room_response.room_id,
})
2020-07-30 18:14:47 +02:00
}
/// # `POST /_matrix/client/v3/rooms/{roomId}/leave`
2021-08-31 19:14:37 +02:00
///
/// Tries to leave the sender user from a room.
///
/// - This should always work if the user is currently joined.
2024-07-16 08:05:25 +00:00
pub(crate) async fn leave_room_route(
State(services): State<crate::State>, body: Ruma<leave_room::v3::Request>,
) -> Result<leave_room::v3::Response> {
2020-10-18 20:33:12 +02:00
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
2020-07-30 18:14:47 +02:00
2024-07-27 07:17:07 +00:00
leave_room(&services, sender_user, &body.room_id, body.reason.clone()).await?;
2022-02-18 15:33:14 +01:00
Ok(leave_room::v3::Response::new())
2020-07-30 18:14:47 +02:00
}
2021-08-31 19:14:37 +02:00
/// # `POST /_matrix/client/r0/rooms/{roomId}/invite`
///
/// Tries to send an invite event into the room.
2024-06-17 04:12:11 +00:00
#[tracing::instrument(skip_all, fields(%client), name = "invite")]
pub(crate) async fn invite_user_route(
2024-07-16 08:05:25 +00:00
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
body: Ruma<invite_user::v3::Request>,
) -> Result<invite_user::v3::Response> {
2020-10-18 20:33:12 +02:00
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
2024-03-05 19:48:54 -05:00
2024-08-08 17:18:30 +00:00
if !services.users.is_admin(sender_user).await && services.globals.block_non_admin_invites() {
info!(
"User {sender_user} is not an admin and attempted to send an invite to room {}",
&body.room_id
);
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"Invites are not allowed on this server.",
));
}
2024-03-05 19:48:54 -05:00
2024-07-27 07:17:07 +00:00
banned_room_check(&services, sender_user, Some(&body.room_id), body.room_id.server_name(), client).await?;
2022-12-14 13:09:10 +01:00
if let invite_user::v3::InvitationRecipient::UserId {
user_id,
} = &body.recipient
{
if services.users.user_is_ignored(sender_user, user_id).await {
return Err!(Request(Forbidden("You cannot invite users you have ignored to rooms.")));
} else if services.users.user_is_ignored(user_id, sender_user).await {
// silently drop the invite to the recipient if they've been ignored by the
// sender, pretend it worked
return Ok(invite_user::v3::Response {});
}
invite_helper(&services, sender_user, user_id, &body.room_id, body.reason.clone(), false)
.boxed()
.await?;
2022-02-18 15:33:14 +01:00
Ok(invite_user::v3::Response {})
2020-07-30 18:14:47 +02:00
} else {
Err(Error::BadRequest(ErrorKind::NotFound, "User not found."))
}
}
2021-08-31 19:14:37 +02:00
/// # `POST /_matrix/client/r0/rooms/{roomId}/kick`
///
/// Tries to send a kick event into the room.
2024-07-16 08:05:25 +00:00
pub(crate) async fn kick_user_route(
State(services): State<crate::State>, body: Ruma<kick_user::v3::Request>,
) -> Result<kick_user::v3::Response> {
2020-10-18 20:33:12 +02:00
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
2024-03-05 19:48:54 -05:00
2024-07-16 08:05:25 +00:00
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
2024-07-09 06:04:05 +00:00
let event: RoomMemberEventContent = services
.rooms
.state_accessor
.room_state_get_content(&body.room_id, &StateEventType::RoomMember, body.user_id.as_ref())
.await
.map_err(|_| err!(Request(BadState("Cannot kick member that's not in the room."))))?;
2024-03-05 19:48:54 -05:00
2024-07-16 08:05:25 +00:00
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(
body.user_id.to_string(),
&RoomMemberEventContent {
membership: MembershipState::Leave,
reason: body.reason.clone(),
..event
},
),
sender_user,
&body.room_id,
&state_lock,
)
.await?;
2024-03-05 19:48:54 -05:00
2021-08-03 11:10:58 +02:00
drop(state_lock);
2024-03-05 19:48:54 -05:00
2022-02-18 15:33:14 +01:00
Ok(kick_user::v3::Response::new())
2020-07-30 18:14:47 +02:00
}
2021-08-31 19:14:37 +02:00
/// # `POST /_matrix/client/r0/rooms/{roomId}/ban`
///
/// Tries to send a ban event into the room.
2024-07-16 08:05:25 +00:00
pub(crate) async fn ban_user_route(
State(services): State<crate::State>, body: Ruma<ban_user::v3::Request>,
) -> Result<ban_user::v3::Response> {
2020-10-18 20:33:12 +02:00
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
2024-03-05 19:48:54 -05:00
2024-07-16 08:05:25 +00:00
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
2024-07-09 06:04:05 +00:00
2024-08-08 17:18:30 +00:00
let blurhash = services.users.blurhash(&body.user_id).await.ok();
2024-07-16 08:05:25 +00:00
let event = services
2020-07-30 18:14:47 +02:00
.rooms
2022-09-07 13:25:51 +02:00
.state_accessor
.room_state_get_content(&body.room_id, &StateEventType::RoomMember, body.user_id.as_ref())
2024-08-08 17:18:30 +00:00
.await
.map_or_else(
|_| RoomMemberEventContent {
2024-08-08 17:18:30 +00:00
blurhash: blurhash.clone(),
reason: body.reason.clone(),
..RoomMemberEventContent::new(MembershipState::Ban)
2020-07-30 18:14:47 +02:00
},
|event| RoomMemberEventContent {
membership: MembershipState::Ban,
displayname: None,
avatar_url: None,
blurhash: blurhash.clone(),
reason: body.reason.clone(),
join_authorized_via_users_server: None,
..event
},
);
2024-03-05 19:48:54 -05:00
2024-07-16 08:05:25 +00:00
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(body.user_id.to_string(), &event),
sender_user,
&body.room_id,
&state_lock,
)
.await?;
2024-03-05 19:48:54 -05:00
2021-08-03 11:10:58 +02:00
drop(state_lock);
2024-03-05 19:48:54 -05:00
2022-02-18 15:33:14 +01:00
Ok(ban_user::v3::Response::new())
2020-07-30 18:14:47 +02:00
}
2021-08-31 19:14:37 +02:00
/// # `POST /_matrix/client/r0/rooms/{roomId}/unban`
///
/// Tries to send an unban event into the room.
2024-07-16 08:05:25 +00:00
pub(crate) async fn unban_user_route(
State(services): State<crate::State>, body: Ruma<unban_user::v3::Request>,
) -> Result<unban_user::v3::Response> {
2020-10-18 20:33:12 +02:00
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
2024-03-05 19:48:54 -05:00
2024-07-16 08:05:25 +00:00
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
2024-07-09 06:04:05 +00:00
let event: RoomMemberEventContent = services
.rooms
.state_accessor
.room_state_get_content(&body.room_id, &StateEventType::RoomMember, body.user_id.as_ref())
.await
.map_err(|_| err!(Request(BadState("Cannot unban a user who is not banned."))))?;
2024-03-05 19:48:54 -05:00
2024-07-16 08:05:25 +00:00
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(
body.user_id.to_string(),
&RoomMemberEventContent {
membership: MembershipState::Leave,
reason: body.reason.clone(),
join_authorized_via_users_server: None,
..event
},
),
sender_user,
&body.room_id,
&state_lock,
)
.await?;
2024-03-05 19:48:54 -05:00
2021-08-03 11:10:58 +02:00
drop(state_lock);
2024-03-05 19:48:54 -05:00
2022-02-18 15:33:14 +01:00
Ok(unban_user::v3::Response::new())
2020-07-30 18:14:47 +02:00
}
/// # `POST /_matrix/client/v3/rooms/{roomId}/forget`
2021-08-31 19:14:37 +02:00
///
/// Forgets about a room.
///
/// - If the sender user currently left the room: Stops sender user from
/// receiving information about the room
///
/// Note: Other devices of the user have no way of knowing the room was
/// forgotten, so this has to be called from every device
2024-07-16 08:05:25 +00:00
pub(crate) async fn forget_room_route(
State(services): State<crate::State>, body: Ruma<forget_room::v3::Request>,
) -> Result<forget_room::v3::Response> {
2020-10-18 20:33:12 +02:00
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
2024-03-05 19:48:54 -05:00
2024-07-16 08:05:25 +00:00
if services
.rooms
.state_cache
2024-08-08 17:18:30 +00:00
.is_joined(sender_user, &body.room_id)
.await
{
2024-08-08 17:18:30 +00:00
return Err!(Request(Unknown("You must leave the room before forgetting it")));
}
2024-07-16 08:05:25 +00:00
services
2024-03-25 17:05:11 -04:00
.rooms
.state_cache
2024-08-08 17:18:30 +00:00
.forget(&body.room_id, sender_user);
2024-03-05 19:48:54 -05:00
2022-02-18 15:33:14 +01:00
Ok(forget_room::v3::Response::new())
2020-07-30 18:14:47 +02:00
}
2021-08-31 19:14:37 +02:00
/// # `POST /_matrix/client/r0/joined_rooms`
///
/// Lists all rooms the user has joined.
2024-07-16 08:05:25 +00:00
pub(crate) async fn joined_rooms_route(
State(services): State<crate::State>, body: Ruma<joined_rooms::v3::Request>,
) -> Result<joined_rooms::v3::Response> {
2020-10-18 20:33:12 +02:00
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
2024-03-05 19:48:54 -05:00
2022-02-18 15:33:14 +01:00
Ok(joined_rooms::v3::Response {
2024-07-16 08:05:25 +00:00
joined_rooms: services
2024-03-25 17:05:11 -04:00
.rooms
.state_cache
.rooms_joined(sender_user)
2024-08-08 17:18:30 +00:00
.map(ToOwned::to_owned)
.collect()
.await,
2022-01-22 16:58:32 +01:00
})
2020-07-30 18:14:47 +02:00
}
2021-08-31 19:14:37 +02:00
/// # `POST /_matrix/client/r0/rooms/{roomId}/members`
///
/// Lists all joined users in a room (TODO: at a specific point in time, with a
/// specific membership).
///
/// - Only works if the user is currently joined
2024-04-22 23:48:57 -04:00
pub(crate) async fn get_member_events_route(
2024-07-16 08:05:25 +00:00
State(services): State<crate::State>, body: Ruma<get_member_events::v3::Request>,
2022-02-18 15:33:14 +01:00
) -> Result<get_member_events::v3::Response> {
2020-10-18 20:33:12 +02:00
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
2024-03-05 19:48:54 -05:00
2024-07-16 08:05:25 +00:00
if !services
2024-03-25 17:05:11 -04:00
.rooms
.state_accessor
2024-08-08 17:18:30 +00:00
.user_can_see_state_events(sender_user, &body.room_id)
.await
2024-03-25 17:05:11 -04:00
{
2024-08-08 17:18:30 +00:00
return Err!(Request(Forbidden("You don't have permission to view this room.")));
2020-07-30 18:14:47 +02:00
}
2024-03-05 19:48:54 -05:00
2022-02-18 15:33:14 +01:00
Ok(get_member_events::v3::Response {
2024-07-16 08:05:25 +00:00
chunk: services
2020-07-30 18:14:47 +02:00
.rooms
2022-09-07 13:25:51 +02:00
.state_accessor
2022-06-18 16:38:41 +02:00
.room_state_full(&body.room_id)
.await?
2020-12-19 16:00:11 +01:00
.iter()
2022-04-06 21:31:29 +02:00
.filter(|(key, _)| key.0 == StateEventType::RoomMember)
2022-10-10 14:09:11 +02:00
.map(|(_, pdu)| pdu.to_member_event())
2020-07-30 18:14:47 +02:00
.collect(),
2022-01-22 16:58:32 +01:00
})
2020-07-30 18:14:47 +02:00
}
2021-08-31 19:14:37 +02:00
/// # `POST /_matrix/client/r0/rooms/{roomId}/joined_members`
///
/// Lists all members of a room.
///
/// - The sender user must be in the room
/// - TODO: An appservice just needs a puppet joined
2024-04-22 23:48:57 -04:00
pub(crate) async fn joined_members_route(
2024-07-16 08:05:25 +00:00
State(services): State<crate::State>, body: Ruma<joined_members::v3::Request>,
2024-04-22 23:48:57 -04:00
) -> Result<joined_members::v3::Response> {
2020-10-18 20:33:12 +02:00
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
2024-03-05 19:48:54 -05:00
2024-07-16 08:05:25 +00:00
if !services
2024-03-25 17:05:11 -04:00
.rooms
.state_accessor
2024-08-08 17:18:30 +00:00
.user_can_see_state_events(sender_user, &body.room_id)
.await
2024-03-25 17:05:11 -04:00
{
2024-08-08 17:18:30 +00:00
return Err!(Request(Forbidden("You don't have permission to view this room.")));
2020-07-30 18:14:47 +02:00
}
2024-03-05 19:48:54 -05:00
let joined: BTreeMap<OwnedUserId, RoomMember> = services
2024-03-25 17:05:11 -04:00
.rooms
.state_cache
.room_members(&body.room_id)
2024-10-01 02:47:39 +00:00
.map(ToOwned::to_owned)
2024-08-08 17:18:30 +00:00
.then(|user| async move {
(
2024-10-01 02:47:39 +00:00
user.clone(),
RoomMember {
2024-10-01 02:47:39 +00:00
display_name: services.users.displayname(&user).await.ok(),
avatar_url: services.users.avatar_url(&user).await.ok(),
},
2024-08-08 17:18:30 +00:00
)
})
2024-08-08 17:18:30 +00:00
.collect()
.await;
2024-03-05 19:48:54 -05:00
2022-02-18 15:33:14 +01:00
Ok(joined_members::v3::Response {
joined,
})
2020-07-30 18:14:47 +02:00
}
2024-05-09 15:59:08 -07:00
pub async fn join_room_by_id_helper(
2024-07-16 08:05:25 +00:00
services: &Services, sender_user: &UserId, room_id: &RoomId, reason: Option<String>, servers: &[OwnedServerName],
third_party_signed: Option<&ThirdPartySigned>, appservice_info: &Option<RegistrationInfo>,
2022-02-18 15:33:14 +01:00
) -> Result<join_room_by_id::v3::Response> {
2024-07-16 08:05:25 +00:00
let state_lock = services.rooms.state.mutex.lock(room_id).await;
2024-07-09 06:04:05 +00:00
2024-08-08 17:18:30 +00:00
let user_is_guest = services
.users
.is_deactivated(sender_user)
.await
.unwrap_or(false)
&& appservice_info.is_none();
2024-08-09 16:28:11 -04:00
2024-08-08 17:18:30 +00:00
if user_is_guest && !services.rooms.state_accessor.guest_can_join(room_id).await {
2024-08-09 16:28:11 -04:00
return Err!(Request(Forbidden("Guests are not allowed to join this room")));
}
2024-08-08 17:18:30 +00:00
if services
.rooms
.state_cache
.is_joined(sender_user, room_id)
.await
{
2024-07-09 06:08:40 +00:00
debug_warn!("{sender_user} is already joined in {room_id}");
return Ok(join_room_by_id::v3::Response {
room_id: room_id.into(),
});
}
let server_in_room = services
2024-03-25 17:05:11 -04:00
.rooms
.state_cache
2024-08-08 17:18:30 +00:00
.server_in_room(services.globals.server_name(), room_id)
.await;
let local_join =
server_in_room || servers.is_empty() || (servers.len() == 1 && services.globals.server_is_ours(&servers[0]));
if local_join {
2024-07-16 08:05:25 +00:00
join_room_by_id_helper_local(services, sender_user, room_id, reason, servers, third_party_signed, state_lock)
2024-08-08 17:18:30 +00:00
.boxed()
.await?;
} else {
// Ask a remote server if we are not participating in this room
2024-07-16 08:05:25 +00:00
join_room_by_id_helper_remote(services, sender_user, room_id, reason, servers, third_party_signed, state_lock)
2024-08-08 17:18:30 +00:00
.boxed()
.await?;
}
Ok(join_room_by_id::v3::Response::new(room_id.to_owned()))
}
2024-03-05 19:48:54 -05:00
#[tracing::instrument(skip_all, fields(%sender_user, %room_id), name = "join_remote")]
async fn join_room_by_id_helper_remote(
2024-07-16 08:05:25 +00:00
services: &Services, sender_user: &UserId, room_id: &RoomId, reason: Option<String>, servers: &[OwnedServerName],
2024-07-09 20:04:43 +00:00
_third_party_signed: Option<&ThirdPartySigned>, state_lock: RoomMutexGuard,
) -> Result {
info!("Joining {room_id} over federation.");
2024-07-16 08:05:25 +00:00
let (make_join_response, remote_server) = make_join_request(services, sender_user, room_id, servers).await?;
info!("make_join finished");
let room_version_id = match make_join_response.room_version {
Some(room_version)
2024-07-16 08:05:25 +00:00
if services
.globals
.supported_room_versions()
.contains(&room_version) =>
{
room_version
},
2024-08-01 10:58:27 +00:00
_ => return Err!(BadServerResponse("Room version is not supported")),
};
let mut join_event_stub: CanonicalJsonObject = serde_json::from_str(make_join_response.event.get())
2024-08-01 10:58:27 +00:00
.map_err(|e| err!(BadServerResponse("Invalid make_join event json received from server: {e:?}")))?;
let join_authorized_via_users_server = join_event_stub
.get("content")
.map(|s| {
s.as_object()?
.get("join_authorised_via_users_server")?
.as_str()
})
.and_then(|s| OwnedUserId::try_from(s.unwrap_or_default()).ok());
// TODO: Is origin needed?
join_event_stub.insert(
"origin".to_owned(),
2024-07-16 08:05:25 +00:00
CanonicalJsonValue::String(services.globals.server_name().as_str().to_owned()),
);
join_event_stub.insert(
"origin_server_ts".to_owned(),
CanonicalJsonValue::Integer(
utils::millis_since_unix_epoch()
.try_into()
.expect("Timestamp is valid js_int value"),
),
);
join_event_stub.insert(
"content".to_owned(),
to_canonical_value(RoomMemberEventContent {
2024-08-08 17:18:30 +00:00
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
blurhash: services.users.blurhash(sender_user).await.ok(),
reason,
join_authorized_via_users_server: join_authorized_via_users_server.clone(),
..RoomMemberEventContent::new(MembershipState::Join)
})
.expect("event is valid, we just created it"),
);
// We keep the "event_id" in the pdu only in v1 or
// v2 rooms
match room_version_id {
RoomVersionId::V1 | RoomVersionId::V2 => {},
_ => {
join_event_stub.remove("event_id");
},
};
// In order to create a compatible ref hash (EventID) the `hashes` field needs
// to be present
services
.server_keys
.hash_and_sign_event(&mut join_event_stub, &room_version_id)?;
// Generate event id
let event_id = pdu::gen_event_id(&join_event_stub, &room_version_id)?;
// Add event_id back
join_event_stub.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.clone().into()));
// It has enough fields to be called a proper event now
let mut join_event = join_event_stub;
info!("Asking {remote_server} for send_join in room {room_id}");
let send_join_request = federation::membership::create_join_event::v2::Request {
room_id: room_id.to_owned(),
event_id: event_id.clone(),
omit_members: false,
pdu: services
.sending
.convert_to_outgoing_federation_event(join_event.clone())
.await,
};
2024-07-16 08:05:25 +00:00
let send_join_response = services
.sending
.send_synapse_request(&remote_server, send_join_request)
.await?;
info!("send_join finished");
if join_authorized_via_users_server.is_some() {
2024-07-12 01:08:53 +00:00
use RoomVersionId::*;
match &room_version_id {
2024-07-12 01:08:53 +00:00
V1 | V2 | V3 | V4 | V5 | V6 | V7 => {
warn!(
"Found `join_authorised_via_users_server` but room {} is version {}. Ignoring.",
room_id, &room_version_id
);
},
// only room versions 8 and above using `join_authorized_via_users_server` (restricted joins) need to
// validate and send signatures
_ => {
if let Some(signed_raw) = &send_join_response.room_state.event {
debug_info!(
"There is a signed event. This room is probably using restricted joins. Adding signature to \
our event"
);
let Ok((signed_event_id, signed_value)) = gen_event_id_canonical_json(signed_raw, &room_version_id)
else {
// Event could not be converted to canonical json
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Could not convert event to canonical json.",
));
};
if signed_event_id != event_id {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Server sent event with wrong event id",
));
}
match signed_value["signatures"]
.as_object()
.ok_or(Error::BadRequest(
ErrorKind::InvalidParam,
"Server sent invalid signatures type",
))
.and_then(|e| {
e.get(remote_server.as_str())
.ok_or(Error::BadRequest(ErrorKind::InvalidParam, "Server did not send its signature"))
}) {
Ok(signature) => {
join_event
.get_mut("signatures")
.expect("we created a valid pdu")
.as_object_mut()
.expect("we created a valid pdu")
.insert(remote_server.to_string(), signature.clone());
},
Err(e) => {
warn!(
"Server {remote_server} sent invalid signature in sendjoin signatures for event \
{signed_value:?}: {e:?}",
);
},
}
}
},
}
}
2024-08-08 17:18:30 +00:00
services
.rooms
.short
.get_or_create_shortroomid(room_id)
.await;
info!("Parsing join event");
let parsed_join_pdu = PduEvent::from_id_val(&event_id, join_event.clone())
2024-08-01 10:58:27 +00:00
.map_err(|e| err!(BadServerResponse("Invalid join event PDU: {e:?}")))?;
info!("Acquiring server signing keys for response events");
let resp_events = &send_join_response.room_state;
let resp_state = &resp_events.state;
let resp_auth = &resp_events.auth_chain;
2024-07-16 08:05:25 +00:00
services
.server_keys
.acquire_events_pubkeys(resp_auth.iter().chain(resp_state.iter()))
.await;
info!("Going through send_join response room_state");
2024-11-09 02:42:09 +00:00
let cork = services.db.cork_and_flush();
let state = send_join_response
.room_state
.state
.iter()
.stream()
.then(|pdu| {
services
.server_keys
.validate_and_add_event_id_no_fetch(pdu, &room_version_id)
})
.ready_filter_map(Result::ok)
.fold(HashMap::new(), |mut state, (event_id, value)| async move {
let pdu = match PduEvent::from_id_val(&event_id, value.clone()) {
Ok(pdu) => pdu,
Err(e) => {
debug_warn!("Invalid PDU in send_join response: {e:?}: {value:#?}");
return state;
},
};
services.rooms.outlier.add_pdu_outlier(&event_id, &value);
if let Some(state_key) = &pdu.state_key {
let shortstatekey = services
.rooms
.short
.get_or_create_shortstatekey(&pdu.kind.to_string().into(), state_key)
.await;
state.insert(shortstatekey, pdu.event_id.clone());
}
state
})
.await;
2024-11-09 02:42:09 +00:00
drop(cork);
info!("Going through send_join response auth_chain");
2024-11-09 02:42:09 +00:00
let cork = services.db.cork_and_flush();
send_join_response
.room_state
.auth_chain
.iter()
.stream()
.then(|pdu| {
services
.server_keys
.validate_and_add_event_id_no_fetch(pdu, &room_version_id)
})
.ready_filter_map(Result::ok)
.ready_for_each(|(event_id, value)| services.rooms.outlier.add_pdu_outlier(&event_id, &value))
.await;
2024-11-09 02:42:09 +00:00
drop(cork);
debug!("Running send_join auth check");
2024-08-08 17:18:30 +00:00
let fetch_state = &state;
let state_fetch = |k: &'static StateEventType, s: String| async move {
let shortstatekey = services.rooms.short.get_shortstatekey(k, &s).await.ok()?;
let event_id = fetch_state.get(&shortstatekey)?;
services.rooms.timeline.get_pdu(event_id).await.ok()
};
let auth_check = state_res::event_auth::auth_check(
&state_res::RoomVersion::new(&room_version_id).expect("room version is supported"),
&parsed_join_pdu,
2024-08-08 17:18:30 +00:00
None, // TODO: third party invite
|k, s| state_fetch(k, s.to_owned()),
)
2024-08-08 17:18:30 +00:00
.await
.map_err(|e| err!(Request(Forbidden(warn!("Auth check failed: {e:?}")))))?;
if !auth_check {
2024-08-08 17:18:30 +00:00
return Err!(Request(Forbidden("Auth check failed")));
}
info!("Compressing state from send_join");
let compressed = state
.iter()
.stream()
.then(|(&k, id)| services.rooms.state_compressor.compress_state_event(k, id))
.collect()
.await;
debug!("Saving compressed state");
let HashSetCompressStateEvent {
shortstatehash: statehash_before_join,
added,
removed,
} = services
2024-08-08 17:18:30 +00:00
.rooms
.state_compressor
.save_state(room_id, Arc::new(compressed))
2024-08-08 17:18:30 +00:00
.await?;
debug!("Forcing state for new room");
2024-07-16 08:05:25 +00:00
services
.rooms
.state
.force_state(room_id, statehash_before_join, added, removed, &state_lock)
.await?;
info!("Updating joined counts for new room");
2024-08-08 17:18:30 +00:00
services
.rooms
.state_cache
.update_joined_count(room_id)
.await;
// 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
// fail.
2024-08-08 17:18:30 +00:00
let statehash_after_join = services
.rooms
.state
.append_to_state(&parsed_join_pdu)
.await?;
info!("Appending new room join event");
2024-07-16 08:05:25 +00:00
services
.rooms
.timeline
.append_pdu(
&parsed_join_pdu,
join_event,
vec![(*parsed_join_pdu.event_id).to_owned()],
&state_lock,
)
.await?;
info!("Setting final room state for new room");
// We set the room state after inserting the pdu, so that we never have a moment
// in time where events in the current room state do not exist
2024-07-16 08:05:25 +00:00
services
.rooms
.state
2024-08-08 17:18:30 +00:00
.set_room_state(room_id, statehash_after_join, &state_lock);
Ok(())
}
#[tracing::instrument(skip_all, fields(%sender_user, %room_id), name = "join_local")]
async fn join_room_by_id_helper_local(
2024-07-16 08:05:25 +00:00
services: &Services, sender_user: &UserId, room_id: &RoomId, reason: Option<String>, servers: &[OwnedServerName],
2024-07-09 20:04:43 +00:00
_third_party_signed: Option<&ThirdPartySigned>, state_lock: RoomMutexGuard,
) -> Result {
2024-07-09 06:08:40 +00:00
debug!("We can join locally");
2024-08-08 17:18:30 +00:00
let join_rules_event_content = services
2024-07-16 08:05:25 +00:00
.rooms
.state_accessor
2024-08-08 17:18:30 +00:00
.room_state_get_content(room_id, &StateEventType::RoomJoinRules, "")
.await
.map(|content: RoomJoinRulesEventContent| content);
let restriction_rooms = match join_rules_event_content {
2024-08-08 17:18:30 +00:00
Ok(RoomJoinRulesEventContent {
join_rule: JoinRule::Restricted(restricted) | JoinRule::KnockRestricted(restricted),
}) => restricted
.allow
.into_iter()
.filter_map(|a| match a {
AllowRule::RoomMembership(r) => Some(r.room_id),
_ => None,
})
.collect(),
_ => Vec::new(),
};
2024-08-08 17:18:30 +00:00
let local_members: Vec<_> = services
.rooms
.state_cache
.room_members(room_id)
2024-08-08 17:18:30 +00:00
.ready_filter(|user| services.globals.user_is_local(user))
.map(ToOwned::to_owned)
.collect()
.await;
let mut join_authorized_via_users_server: Option<OwnedUserId> = None;
2024-08-08 17:18:30 +00:00
if restriction_rooms
.iter()
.stream()
.any(|restriction_room_id| {
services
.rooms
.state_cache
.is_joined(sender_user, restriction_room_id)
})
.await
{
for user in local_members {
2024-07-16 08:05:25 +00:00
if services
.rooms
.state_accessor
.user_can_invite(room_id, &user, sender_user, &state_lock)
2024-08-08 17:18:30 +00:00
.await
{
join_authorized_via_users_server = Some(user);
break;
}
}
}
let content = RoomMemberEventContent {
2024-08-08 17:18:30 +00:00
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
blurhash: services.users.blurhash(sender_user).await.ok(),
reason: reason.clone(),
join_authorized_via_users_server,
..RoomMemberEventContent::new(MembershipState::Join)
};
// Try normal join first
2024-07-16 08:05:25 +00:00
let error = match services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(sender_user.to_string(), &content),
sender_user,
room_id,
&state_lock,
)
.await
{
Ok(_) => return Ok(()),
Err(e) => e,
};
2024-03-05 19:48:54 -05:00
if !restriction_rooms.is_empty()
&& servers
.iter()
2024-07-22 07:43:51 +00:00
.any(|server_name| !services.globals.server_is_ours(server_name))
{
2024-07-09 06:08:40 +00:00
warn!("We couldn't do the join locally, maybe federation can help to satisfy the restricted join requirements");
2024-07-16 08:05:25 +00:00
let (make_join_response, remote_server) = make_join_request(services, sender_user, room_id, servers).await?;
2024-03-05 19:48:54 -05:00
let room_version_id = match make_join_response.room_version {
Some(room_version_id)
2024-07-16 08:05:25 +00:00
if services
2024-03-25 17:05:11 -04:00
.globals
.supported_room_versions()
.contains(&room_version_id) =>
2024-03-25 17:05:11 -04:00
{
room_version_id
2024-03-25 17:05:11 -04:00
},
2024-08-01 10:58:27 +00:00
_ => return Err!(BadServerResponse("Room version is not supported")),
2024-03-05 19:48:54 -05:00
};
2022-10-05 20:34:31 +02:00
let mut join_event_stub: CanonicalJsonObject = serde_json::from_str(make_join_response.event.get())
2024-08-01 10:58:27 +00:00
.map_err(|e| err!(BadServerResponse("Invalid make_join event json received from server: {e:?}")))?;
2021-03-26 11:10:45 +01:00
let join_authorized_via_users_server = join_event_stub
.get("content")
2024-03-25 17:05:11 -04:00
.map(|s| {
s.as_object()?
.get("join_authorised_via_users_server")?
.as_str()
})
2021-10-13 11:51:30 +02:00
.and_then(|s| OwnedUserId::try_from(s.unwrap_or_default()).ok());
2021-04-13 15:00:45 +02:00
// TODO: Is origin needed?
2021-10-13 11:51:30 +02:00
join_event_stub.insert(
"origin".to_owned(),
2024-07-16 08:05:25 +00:00
CanonicalJsonValue::String(services.globals.server_name().as_str().to_owned()),
2024-03-05 19:48:54 -05:00
);
2021-10-13 11:51:30 +02:00
join_event_stub.insert(
"origin_server_ts".to_owned(),
2021-04-26 18:20:20 +02:00
CanonicalJsonValue::Integer(
2024-03-25 17:05:11 -04:00
utils::millis_since_unix_epoch()
.try_into()
.expect("Timestamp is valid js_int value"),
2024-03-05 19:48:54 -05:00
),
2021-10-13 11:51:30 +02:00
);
join_event_stub.insert(
"content".to_owned(),
to_canonical_value(RoomMemberEventContent {
2024-08-08 17:18:30 +00:00
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
blurhash: services.users.blurhash(sender_user).await.ok(),
reason,
join_authorized_via_users_server,
..RoomMemberEventContent::new(MembershipState::Join)
})
2021-04-26 18:20:20 +02:00
.expect("event is valid, we just created it"),
);
2024-03-05 19:48:54 -05:00
// We keep the "event_id" in the pdu only in v1 or
2022-09-06 23:15:09 +02:00
// v2 rooms
match room_version_id {
RoomVersionId::V1 | RoomVersionId::V2 => {},
_ => {
join_event_stub.remove("event_id");
},
};
2024-03-05 19:48:54 -05:00
2021-10-13 10:16:45 +02:00
// In order to create a compatible ref hash (EventID) the `hashes` field needs
// to be present
services
.server_keys
.hash_and_sign_event(&mut join_event_stub, &room_version_id)?;
2024-03-05 19:48:54 -05:00
// Generate event id
let event_id = pdu::gen_event_id(&join_event_stub, &room_version_id)?;
2024-03-05 19:48:54 -05:00
// Add event_id back
join_event_stub.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.clone().into()));
2024-03-05 19:48:54 -05:00
// It has enough fields to be called a proper event now
let join_event = join_event_stub;
2024-03-05 19:48:54 -05:00
2024-07-16 08:05:25 +00:00
let send_join_response = services
2024-03-05 19:48:54 -05:00
.sending
.send_synapse_request(
&remote_server,
federation::membership::create_join_event::v2::Request {
2021-11-27 00:30:28 +01:00
room_id: room_id.to_owned(),
event_id: event_id.clone(),
2024-08-08 17:18:30 +00:00
omit_members: false,
2024-07-18 06:37:47 +00:00
pdu: services
.sending
2024-08-08 17:18:30 +00:00
.convert_to_outgoing_federation_event(join_event.clone())
.await,
2024-03-05 19:48:54 -05:00
},
)
.await?;
2024-03-05 19:48:54 -05:00
if let Some(signed_raw) = send_join_response.room_state.event {
let Ok((signed_event_id, signed_value)) = gen_event_id_canonical_json(&signed_raw, &room_version_id) else {
// Event could not be converted to canonical json
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Could not convert event to canonical json.",
));
2021-04-13 18:17:51 +02:00
};
2024-03-05 19:48:54 -05:00
if signed_event_id != event_id {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Server sent event with wrong event id",
));
2021-03-13 16:30:12 +01:00
}
2024-03-05 19:48:54 -05:00
drop(state_lock);
2024-07-16 08:05:25 +00:00
services
.rooms
.event_handler
.handle_incoming_pdu(&remote_server, room_id, &signed_event_id, signed_value, true)
.await?;
} else {
return Err(error);
}
} else {
return Err(error);
}
2024-03-05 19:48:54 -05:00
Ok(())
}
2021-04-13 18:17:51 +02:00
async fn make_join_request(
2024-07-16 08:05:25 +00:00
services: &Services, sender_user: &UserId, room_id: &RoomId, servers: &[OwnedServerName],
) -> Result<(federation::membership::prepare_join_event::v1::Response, OwnedServerName)> {
2024-08-01 10:58:27 +00:00
let mut make_join_response_and_server = Err!(BadServerResponse("No server available to assist in joining."));
2024-03-05 19:48:54 -05:00
let mut make_join_counter: u16 = 0;
let mut incompatible_room_version_count: u8 = 0;
for remote_server in servers {
2024-07-22 07:43:51 +00:00
if services.globals.server_is_ours(remote_server) {
continue;
}
info!("Asking {remote_server} for make_join ({make_join_counter})");
2024-07-16 08:05:25 +00:00
let make_join_response = services
.sending
.send_federation_request(
remote_server,
federation::membership::prepare_join_event::v1::Request {
2022-12-14 13:09:10 +01:00
room_id: room_id.to_owned(),
user_id: sender_user.to_owned(),
2024-07-16 08:05:25 +00:00
ver: services.globals.supported_room_versions(),
},
)
.await;
2024-03-05 19:48:54 -05:00
trace!("make_join response: {:?}", make_join_response);
make_join_counter = make_join_counter.saturating_add(1);
if let Err(ref e) = make_join_response {
2024-07-13 02:15:15 +00:00
trace!("make_join ErrorKind string: {:?}", e.kind().to_string());
// converting to a string is necessary (i think) because ruma is forcing us to
// fill in the struct for M_INCOMPATIBLE_ROOM_VERSION
2024-07-13 02:15:15 +00:00
if e.kind().to_string().contains("M_INCOMPATIBLE_ROOM_VERSION")
|| e.kind().to_string().contains("M_UNSUPPORTED_ROOM_VERSION")
{
incompatible_room_version_count = incompatible_room_version_count.saturating_add(1);
}
if incompatible_room_version_count > 15 {
info!(
"15 servers have responded with M_INCOMPATIBLE_ROOM_VERSION or M_UNSUPPORTED_ROOM_VERSION, \
assuming that Conduwuit does not support the room {room_id}: {e}"
);
2024-08-01 10:58:27 +00:00
make_join_response_and_server = Err!(BadServerResponse("Room version is not supported by Conduwuit"));
return make_join_response_and_server;
}
if make_join_counter > 50 {
warn!(
"50 servers failed to provide valid make_join response, assuming no server can assist in joining."
);
2024-08-01 10:58:27 +00:00
make_join_response_and_server = Err!(BadServerResponse("No server available to assist in joining."));
return make_join_response_and_server;
}
}
make_join_response_and_server = make_join_response.map(|r| (r, remote_server.clone()));
2024-03-05 19:48:54 -05:00
if make_join_response_and_server.is_ok() {
break;
2024-03-05 19:48:54 -05:00
}
}
2024-03-05 19:48:54 -05:00
make_join_response_and_server
}
2024-01-14 22:39:08 -05:00
pub(crate) async fn invite_helper(
2024-07-16 08:05:25 +00:00
services: &Services, sender_user: &UserId, user_id: &UserId, room_id: &RoomId, reason: Option<String>,
is_direct: bool,
2021-04-25 14:10:07 +02:00
) -> Result<()> {
if !services.users.is_admin(sender_user).await && services.globals.block_non_admin_invites() {
info!("User {sender_user} is not an admin and attempted to send an invite to room {room_id}");
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"Invites are not allowed on this server.",
));
}
2024-03-05 19:48:54 -05:00
2024-07-22 07:43:51 +00:00
if !services.globals.user_is_local(user_id) {
2022-11-09 21:14:17 +01:00
let (pdu, pdu_json, invite_room_state) = {
2024-07-16 08:05:25 +00:00
let state_lock = services.rooms.state.mutex.lock(room_id).await;
let content = RoomMemberEventContent {
2024-08-08 17:18:30 +00:00
avatar_url: services.users.avatar_url(user_id).await.ok(),
is_direct: Some(is_direct),
reason,
..RoomMemberEventContent::new(MembershipState::Invite)
};
2024-03-05 19:48:54 -05:00
2024-08-08 17:18:30 +00:00
let (pdu, pdu_json) = services
.rooms
.timeline
.create_hash_and_sign_event(
PduBuilder::state(user_id.to_string(), &content),
2024-08-08 17:18:30 +00:00
sender_user,
room_id,
&state_lock,
)
.await?;
2024-03-05 19:48:54 -05:00
2024-10-04 03:40:00 +00:00
let invite_room_state = services.rooms.state.summary_stripped(&pdu).await;
2024-03-05 19:48:54 -05:00
2021-08-03 11:10:58 +02:00
drop(state_lock);
2024-03-05 19:48:54 -05:00
2022-11-09 21:14:17 +01:00
(pdu, pdu_json, invite_room_state)
};
2024-03-05 19:48:54 -05:00
2024-08-08 17:18:30 +00:00
let room_version_id = services.rooms.state.get_room_version(room_id).await?;
2024-03-05 19:48:54 -05:00
2024-07-16 08:05:25 +00:00
let response = services
2021-04-25 14:10:07 +02:00
.sending
.send_federation_request(
user_id.server_name(),
create_invite::v2::Request {
2022-12-14 13:09:10 +01:00
room_id: room_id.to_owned(),
2022-12-21 10:42:12 +01:00
event_id: (*pdu.event_id).to_owned(),
2022-12-14 13:09:10 +01:00
room_version: room_version_id.clone(),
2024-07-18 06:37:47 +00:00
event: services
.sending
2024-08-08 17:18:30 +00:00
.convert_to_outgoing_federation_event(pdu_json.clone())
.await,
2022-12-14 13:09:10 +01:00
invite_room_state,
2024-08-08 17:18:30 +00:00
via: services
.rooms
.state_cache
.servers_route_via(room_id)
.await
.ok(),
2021-04-25 14:10:07 +02:00
},
)
.await?;
2024-03-05 19:48:54 -05:00
2021-04-25 14:10:07 +02:00
// We do not add the event_id field to the pdu here because of signature and
// hashes checks
let Ok((event_id, value)) = gen_event_id_canonical_json(&response.event, &room_version_id) else {
// Event could not be converted to canonical json
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Could not convert event to canonical json.",
));
2021-04-25 14:10:07 +02:00
};
2024-03-05 19:48:54 -05:00
2022-12-14 13:09:10 +01:00
if *pdu.event_id != *event_id {
2021-08-30 10:56:41 +02:00
warn!(
2024-10-03 10:03:31 +00:00
"Server {} changed invite event, that's not allowed in the spec: ours: {pdu_json:?}, theirs: {value:?}",
2021-08-30 10:56:41 +02:00
user_id.server_name(),
);
}
2024-03-05 19:48:54 -05:00
2022-10-09 17:25:06 +02:00
let origin: OwnedServerName = serde_json::from_value(
2021-04-25 14:10:07 +02:00
serde_json::to_value(
value
.get("origin")
.ok_or(Error::BadRequest(ErrorKind::InvalidParam, "Event needs an origin field."))?,
)
.expect("CanonicalJson is valid json value"),
)
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Origin field is invalid."))?;
2024-03-05 19:48:54 -05:00
2024-11-02 06:12:54 +00:00
let pdu_id = services
2022-10-05 20:34:31 +02:00
.rooms
.event_handler
.handle_incoming_pdu(&origin, room_id, &event_id, value, true)
2022-10-05 20:34:31 +02:00
.await?
2024-11-02 06:12:54 +00:00
.ok_or_else(|| err!(Request(InvalidParam("Could not accept incoming PDU as timeline event."))))?;
2024-03-05 19:48:54 -05:00
2024-08-08 17:18:30 +00:00
services.sending.send_pdu_room(room_id, &pdu_id).await?;
2021-04-25 14:10:07 +02:00
return Ok(());
}
2024-03-05 19:48:54 -05:00
2024-08-08 17:18:30 +00:00
if !services
.rooms
.state_cache
.is_joined(sender_user, room_id)
.await
{
2022-06-18 16:38:41 +02:00
return Err(Error::BadRequest(
ErrorKind::forbidden(),
2022-06-18 16:38:41 +02:00
"You don't have permission to view this room.",
));
}
2024-03-05 19:48:54 -05:00
2024-07-16 08:05:25 +00:00
let state_lock = services.rooms.state.mutex.lock(room_id).await;
2024-03-05 19:48:54 -05:00
let content = RoomMemberEventContent {
displayname: services.users.displayname(user_id).await.ok(),
avatar_url: services.users.avatar_url(user_id).await.ok(),
blurhash: services.users.blurhash(user_id).await.ok(),
is_direct: Some(is_direct),
reason,
..RoomMemberEventContent::new(MembershipState::Invite)
};
2024-07-16 08:05:25 +00:00
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(user_id.to_string(), &content),
sender_user,
room_id,
&state_lock,
)
.await?;
2024-03-05 19:48:54 -05:00
2021-08-03 11:10:58 +02:00
drop(state_lock);
2024-03-05 19:48:54 -05:00
2021-04-25 14:10:07 +02:00
Ok(())
}
2022-06-19 22:56:14 +02:00
// Make a user leave all their joined rooms, forgets all rooms, and ignores
// errors
2024-07-16 08:05:25 +00:00
pub async fn leave_all_rooms(services: &Services, user_id: &UserId) {
2024-10-03 10:03:31 +00:00
let rooms_joined = services
2022-09-06 23:15:09 +02:00
.rooms
2022-10-05 09:34:25 +02:00
.state_cache
2022-09-06 23:15:09 +02:00
.rooms_joined(user_id)
2024-10-03 10:03:31 +00:00
.map(ToOwned::to_owned);
let rooms_invited = services
.rooms
.state_cache
.rooms_invited(user_id)
.map(|(r, _)| r);
let all_rooms: Vec<_> = rooms_joined.chain(rooms_invited).collect().await;
2024-03-05 19:48:54 -05:00
2022-09-06 23:15:09 +02:00
for room_id in all_rooms {
2024-03-23 14:38:15 -04:00
// ignore errors
2024-07-16 08:05:25 +00:00
if let Err(e) = leave_room(services, user_id, &room_id, None).await {
warn!(%room_id, %user_id, %e, "Failed to leave room");
}
2024-08-08 17:18:30 +00:00
services.rooms.state_cache.forget(&room_id, user_id);
2022-06-19 22:56:14 +02:00
}
2022-09-06 23:15:09 +02:00
}
2022-06-19 22:56:14 +02:00
2024-07-16 08:05:25 +00:00
pub async fn leave_room(services: &Services, user_id: &UserId, room_id: &RoomId, reason: Option<String>) -> Result<()> {
2024-08-08 17:18:30 +00:00
//use conduit::utils::stream::OptionStream;
use futures::TryFutureExt;
2022-09-06 23:15:09 +02:00
// Ask a remote server if we don't have this room
2024-07-16 08:05:25 +00:00
if !services
.rooms
.state_cache
2024-08-08 17:18:30 +00:00
.server_in_room(services.globals.server_name(), room_id)
.await
{
2024-07-16 08:05:25 +00:00
if let Err(e) = remote_leave_room(services, user_id, room_id).await {
2024-10-03 10:03:31 +00:00
warn!("Failed to leave room {user_id} remotely: {e}");
2022-09-06 23:15:09 +02:00
// Don't tell the client about this error
}
2024-03-05 19:48:54 -05:00
2024-07-16 08:05:25 +00:00
let last_state = services
2022-10-05 20:34:31 +02:00
.rooms
.state_cache
2024-08-08 17:18:30 +00:00
.invite_state(user_id, room_id)
.map_err(|_| services.rooms.state_cache.left_state(user_id, room_id))
.await
.ok();
2024-03-05 19:48:54 -05:00
2022-09-06 23:15:09 +02:00
// We always drop the invite, we can't rely on other servers
2024-08-08 17:18:30 +00:00
services
.rooms
.state_cache
.update_membership(
room_id,
user_id,
RoomMemberEventContent::new(MembershipState::Leave),
user_id,
last_state,
None,
true,
)
.await?;
2022-09-06 23:15:09 +02:00
} else {
2024-07-16 08:05:25 +00:00
let state_lock = services.rooms.state.mutex.lock(room_id).await;
2024-03-05 19:48:54 -05:00
let Ok(event) = services
2024-08-08 17:18:30 +00:00
.rooms
.state_accessor
.room_state_get_content::<RoomMemberEventContent>(room_id, &StateEventType::RoomMember, user_id.as_str())
.await
else {
// Fix for broken rooms
2024-08-08 17:18:30 +00:00
error!("Trying to leave a room you are not a member of.");
2024-03-05 19:48:54 -05:00
2024-08-08 17:18:30 +00:00
services
.rooms
.state_cache
.update_membership(
2024-04-11 19:39:17 -04:00
room_id,
user_id,
RoomMemberEventContent::new(MembershipState::Leave),
user_id,
None,
None,
true,
2024-08-08 17:18:30 +00:00
)
.await?;
return Ok(());
2022-11-19 12:52:47 +01:00
};
2024-03-05 19:48:54 -05:00
2024-07-16 08:05:25 +00:00
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(
user_id.to_string(),
&RoomMemberEventContent {
membership: MembershipState::Leave,
reason,
..event
},
),
user_id,
room_id,
&state_lock,
)
.await?;
2022-06-19 22:56:14 +02:00
}
2024-03-05 19:48:54 -05:00
2022-09-06 23:15:09 +02:00
Ok(())
}
2022-06-19 22:56:14 +02:00
2024-07-16 08:05:25 +00:00
async fn remote_leave_room(services: &Services, user_id: &UserId, room_id: &RoomId) -> Result<()> {
2024-08-01 10:58:27 +00:00
let mut make_leave_response_and_server = Err!(BadServerResponse("No server available to assist in leaving."));
2024-03-05 19:48:54 -05:00
2024-07-16 08:05:25 +00:00
let invite_state = services
2022-09-06 23:15:09 +02:00
.rooms
2022-10-05 09:34:25 +02:00
.state_cache
2024-08-08 17:18:30 +00:00
.invite_state(user_id, room_id)
.await
.map_err(|_| err!(Request(BadState("User is not invited."))))?;
2024-03-05 19:48:54 -05:00
2024-07-16 08:05:25 +00:00
let mut servers: HashSet<OwnedServerName> = services
2024-04-11 19:39:17 -04:00
.rooms
.state_cache
2024-06-10 14:53:26 -04:00
.servers_invite_via(room_id)
2024-08-08 17:18:30 +00:00
.map(ToOwned::to_owned)
.collect()
.await;
2024-06-10 14:53:26 -04:00
servers.extend(
invite_state
.iter()
.filter_map(|event| event.get_field("sender").ok().flatten())
.filter_map(|sender: &str| UserId::parse(sender).ok())
2024-07-07 07:39:18 +00:00
.map(|user| user.server_name().to_owned()),
2024-06-10 14:53:26 -04:00
);
2024-03-05 19:48:54 -05:00
debug!("servers in remote_leave_room: {servers:?}");
2022-09-06 23:15:09 +02:00
for remote_server in servers {
2024-07-16 08:05:25 +00:00
let make_leave_response = services
2022-09-06 23:15:09 +02:00
.sending
.send_federation_request(
&remote_server,
2022-12-14 13:09:10 +01:00
federation::membership::prepare_leave_event::v1::Request {
room_id: room_id.to_owned(),
user_id: user_id.to_owned(),
},
2022-09-06 23:15:09 +02:00
)
.await;
2024-03-05 19:48:54 -05:00
2022-09-06 23:15:09 +02:00
make_leave_response_and_server = make_leave_response.map(|r| (r, remote_server));
2024-03-05 19:48:54 -05:00
2022-09-06 23:15:09 +02:00
if make_leave_response_and_server.is_ok() {
break;
}
2024-03-05 19:48:54 -05:00
}
2022-09-06 23:15:09 +02:00
let (make_leave_response, remote_server) = make_leave_response_and_server?;
2024-03-05 19:48:54 -05:00
2022-09-06 23:15:09 +02:00
let room_version_id = match make_leave_response.room_version {
2024-03-25 17:05:11 -04:00
Some(version)
2024-07-16 08:05:25 +00:00
if services
2024-03-25 17:05:11 -04:00
.globals
.supported_room_versions()
.contains(&version) =>
{
version
},
2024-08-01 10:58:27 +00:00
_ => return Err!(BadServerResponse("Room version is not supported")),
2022-09-06 23:15:09 +02:00
};
2024-03-05 19:48:54 -05:00
2022-10-05 20:34:31 +02:00
let mut leave_event_stub = serde_json::from_str::<CanonicalJsonObject>(make_leave_response.event.get())
2024-08-01 10:58:27 +00:00
.map_err(|e| err!(BadServerResponse("Invalid make_leave event json received from server: {e:?}")))?;
2024-03-05 19:48:54 -05:00
2022-09-06 23:15:09 +02:00
// TODO: Is origin needed?
leave_event_stub.insert(
"origin".to_owned(),
2024-07-16 08:05:25 +00:00
CanonicalJsonValue::String(services.globals.server_name().as_str().to_owned()),
2022-09-06 23:15:09 +02:00
);
leave_event_stub.insert(
"origin_server_ts".to_owned(),
CanonicalJsonValue::Integer(
2024-03-25 17:05:11 -04:00
utils::millis_since_unix_epoch()
.try_into()
.expect("Timestamp is valid js_int value"),
2022-09-06 23:15:09 +02:00
),
);
// room v3 and above removed the "event_id" field from remote PDU format
match room_version_id {
RoomVersionId::V1 | RoomVersionId::V2 => {},
_ => {
leave_event_stub.remove("event_id");
},
};
2024-03-05 19:48:54 -05:00
2022-09-06 23:15:09 +02:00
// In order to create a compatible ref hash (EventID) the `hashes` field needs
// to be present
services
.server_keys
.hash_and_sign_event(&mut leave_event_stub, &room_version_id)?;
2024-03-05 19:48:54 -05:00
2022-09-06 23:15:09 +02:00
// Generate event id
let event_id = pdu::gen_event_id(&leave_event_stub, &room_version_id)?;
2024-03-05 19:48:54 -05:00
2022-09-06 23:15:09 +02:00
// Add event_id back
leave_event_stub.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.clone().into()));
2024-03-05 19:48:54 -05:00
2022-09-06 23:15:09 +02:00
// It has enough fields to be called a proper event now
let leave_event = leave_event_stub;
2024-03-05 19:48:54 -05:00
2024-07-16 08:05:25 +00:00
services
2022-10-05 20:34:31 +02:00
.sending
2022-09-06 23:15:09 +02:00
.send_federation_request(
&remote_server,
federation::membership::create_leave_event::v2::Request {
2022-12-14 13:09:10 +01:00
room_id: room_id.to_owned(),
event_id,
2024-07-18 06:37:47 +00:00
pdu: services
.sending
2024-08-08 17:18:30 +00:00
.convert_to_outgoing_federation_event(leave_event.clone())
.await,
2022-09-06 23:15:09 +02:00
},
)
.await?;
2024-03-05 19:48:54 -05:00
2022-09-06 23:15:09 +02:00
Ok(())
}