Skip to content
Snippets Groups Projects
membership.rs 50.1 KiB
Newer Older
use std::{
	collections::{hash_map::Entry, BTreeMap, HashMap, HashSet},
	time::{Duration, Instant},
};

use axum_client_ip::InsecureClientIp;
Jason Volk's avatar
Jason Volk committed
use conduit::utils::mutex_map;
use ruma::{
	api::{
		client::{
			error::ErrorKind,
			membership::{
				ban_user, forget_room, get_member_events, invite_user, join_room_by_id, join_room_by_id_or_alias,
				joined_members, joined_rooms, kick_user, leave_room, unban_user, ThirdPartySigned,
			},
		},
		federation::{self, membership::create_invite},
	},
	canonical_json::to_canonical_value,
	events::{
		room::{
			join_rules::{AllowRule, JoinRule, RoomJoinRulesEventContent},
			member::{MembershipState, RoomMemberEventContent},
			message::RoomMessageEventContent,
		},
		StateEventType, TimelineEventType,
	},
	serde::Base64,
	state_res, CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId, OwnedServerName,
	OwnedUserId, RoomId, RoomVersionId, ServerName, UserId,
Jonas Platte's avatar
Jonas Platte committed
use serde_json::value::{to_raw_value, RawValue as RawJsonValue};
use service::sending::convert_to_outgoing_federation_event;
Jason Volk's avatar
Jason Volk committed
use tokio::sync::RwLock;
use tracing::{debug, error, info, trace, warn};
Timo Kösters's avatar
Timo Kösters committed
use crate::{
	client::{update_avatar_url, update_displayname},
Jason Volk's avatar
Jason Volk committed
	service::{
		pdu::{gen_event_id_canonical_json, PduBuilder},
		server_is_ours, user_is_local,
	},
	services, utils, Error, PduEvent, Result, Ruma,
Timo Kösters's avatar
Timo Kösters committed
};
/// 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
#[tracing::instrument]
async fn banned_room_check(
	user_id: &UserId, room_id: Option<&RoomId>, server_name: Option<&ServerName>, client_ip: IpAddr,
) -> Result<()> {
	if !services().users.is_admin(user_id)? {
		if let Some(room_id) = room_id {
			if services().rooms.metadata.is_banned(room_id)?
				|| 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}"
				);

				if services()
					.globals
					.config
					.auto_deactivate_banned_room_attempts
				{
					warn!("Automatically deactivating user {user_id} due to attempted banned room join");
					services()
						.admin
						.send_message(RoomMessageEventContent::text_plain(format!(
							"Automatically deactivating user {user_id} due to attempted banned room join from IP \
							 {client_ip}"
					if let Err(e) = services().users.deactivate_account(user_id) {
						warn!(%user_id, %e, "Failed to deactivate account");

					let all_joined_rooms: Vec<OwnedRoomId> = services()
						.rooms
						.state_cache
						.rooms_joined(user_id)
						.filter_map(Result::ok)
						.collect();

					update_displayname(user_id.into(), None, all_joined_rooms.clone()).await?;
					update_avatar_url(user_id.into(), None, None, all_joined_rooms).await?;
					leave_all_rooms(user_id).await;
				}

				return Err(Error::BadRequest(
					ErrorKind::forbidden(),
					"This room is banned on this homeserver.",
				));
			}
		} else if let Some(server_name) = server_name {
			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.",
				);

				if services()
					.globals
					.config
					.auto_deactivate_banned_room_attempts
				{
					warn!("Automatically deactivating user {user_id} due to attempted banned room join");
					services()
						.admin
						.send_message(RoomMessageEventContent::text_plain(format!(
							"Automatically deactivating user {user_id} due to attempted banned room join from IP \
							 {client_ip}"
					if let Err(e) = services().users.deactivate_account(user_id) {
						warn!(%user_id, %e, "Failed to deactivate account");

					let all_joined_rooms: Vec<OwnedRoomId> = services()
						.rooms
						.state_cache
						.rooms_joined(user_id)
						.filter_map(Result::ok)
						.collect();

					update_displayname(user_id.into(), None, all_joined_rooms.clone()).await?;
					update_avatar_url(user_id.into(), None, None, all_joined_rooms).await?;
					leave_all_rooms(user_id).await;
				}

				return Err(Error::BadRequest(
					ErrorKind::forbidden(),
					"This remote server is banned on this homeserver.",
				));
			}
		}
	}

	Ok(())
}

/// # `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
#[tracing::instrument(skip_all, fields(%client_ip), name = "join")]
pub(crate) async fn join_room_by_id_route(
	InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<join_room_by_id::v3::Request>,
) -> Result<join_room_by_id::v3::Response> {
	let sender_user = body.sender_user.as_ref().expect("user is authenticated");

	banned_room_check(sender_user, Some(&body.room_id), body.room_id.server_name(), client_ip).await?;
🥺's avatar
🥺 committed
	// There is no body.server_name for /roomId/join
	let mut servers = services()
		.rooms
		.state_cache
		.servers_invite_via(&body.room_id)
		.filter_map(Result::ok)
		.collect::<Vec<_>>();

	servers.extend(
		services()
			.rooms
			.state_cache
			.invite_state(sender_user, &body.room_id)?
			.unwrap_or_default()
			.iter()
			.filter_map(|event| serde_json::from_str(event.json().get()).ok())
			.filter_map(|event: serde_json::Value| event.get("sender").cloned())
			.filter_map(|sender| sender.as_str().map(ToOwned::to_owned))
			.filter_map(|sender| UserId::parse(sender).ok())
			.map(|user| user.server_name().to_owned()),
	);

	if let Some(server) = body.room_id.server_name() {
		servers.push(server.into());
	}
Loading
Loading full blame...