Skip to content
Snippets Groups Projects
server_server.rs 41.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • 
    	Ok(prepare_join_event::v1::Response {
    		room_version: Some(room_version_id),
    		event: to_raw_value(&pdu_json).expect("CanonicalJson can be serialized to JSON"),
    	})
    
    async fn create_join_event(
    
    	sender_servername: &ServerName, room_id: &RoomId, pdu: &RawJsonValue,
    
    Kévin Commaille's avatar
    Kévin Commaille committed
    ) -> Result<create_join_event::v1::RoomState> {
    
    	if !services().rooms.metadata.exists(room_id)? {
    		return Err(Error::BadRequest(ErrorKind::NotFound, "Room is unknown to this server."));
    	}
    
    
    🥺's avatar
    🥺 committed
    	services()
    		.rooms
    		.event_handler
    		.acl_check(sender_servername, room_id)?;
    
    
    	// TODO: Conduit does not implement restricted join rules yet, we always reject
    	let join_rules_event =
    
    🥺's avatar
    🥺 committed
    		services()
    			.rooms
    			.state_accessor
    			.room_state_get(room_id, &StateEventType::RoomJoinRules, "")?;
    
    
    	let join_rules_event_content: Option<RoomJoinRulesEventContent> = join_rules_event
    		.as_ref()
    		.map(|join_rules_event| {
    			serde_json::from_str(join_rules_event.content.get()).map_err(|e| {
    				warn!("Invalid join rules event: {}", e);
    				Error::bad_database("Invalid join rules event in db.")
    			})
    		})
    		.transpose()?;
    
    	if let Some(join_rules_event_content) = join_rules_event_content {
    		if matches!(
    			join_rules_event_content.join_rule,
    			JoinRule::Restricted { .. } | JoinRule::KnockRestricted { .. }
    		) {
    			return Err(Error::BadRequest(
    				ErrorKind::UnableToAuthorizeJoin,
    				"Conduit does not support restricted rooms yet.",
    			));
    		}
    	}
    
    	// We need to return the state prior to joining, let's keep a reference to that
    	// here
    	let shortstatehash = services()
    		.rooms
    		.state
    		.get_room_shortstatehash(room_id)?
    		.ok_or(Error::BadRequest(ErrorKind::NotFound, "Pdu state not found."))?;
    
    	let pub_key_map = RwLock::new(BTreeMap::new());
    	// let mut auth_cache = EventMap::new();
    
    	// We do not add the event_id field to the pdu here because of signature and
    	// hashes checks
    	let room_version_id = services().rooms.state.get_room_version(room_id)?;
    
    	let Ok((event_id, value)) = gen_event_id_canonical_json(pdu, &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.",
    		));
    
    	};
    
    	let origin: OwnedServerName = serde_json::from_value(
    		serde_json::to_value(
    
    🥺's avatar
    🥺 committed
    			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."))?;
    
    
    🥺's avatar
    🥺 committed
    	services()
    		.rooms
    		.event_handler
    		.fetch_required_signing_keys([&value], &pub_key_map)
    		.await?;
    
    🥺's avatar
    🥺 committed
    	let mutex = Arc::clone(
    		services()
    			.globals
    			.roomid_mutex_federation
    			.write()
    			.await
    			.entry(room_id.to_owned())
    			.or_default(),
    	);
    
    	let mutex_lock = mutex.lock().await;
    	let pdu_id: Vec<u8> = services()
    		.rooms
    		.event_handler
    		.handle_incoming_pdu(&origin, &event_id, room_id, value, true, &pub_key_map)
    		.await?
    		.ok_or(Error::BadRequest(
    			ErrorKind::InvalidParam,
    			"Could not accept incoming PDU as timeline event.",
    		))?;
    	drop(mutex_lock);
    
    
    🥺's avatar
    🥺 committed
    	let state_ids = services()
    		.rooms
    		.state_accessor
    		.state_full_ids(shortstatehash)
    		.await?;
    	let auth_chain_ids = services()
    		.rooms
    		.auth_chain
    		.get_auth_chain(room_id, state_ids.values().cloned().collect())
    		.await?;
    
    	services().sending.send_pdu_room(room_id, &pdu_id)?;
    
    
    	Ok(create_join_event::v1::RoomState {
    		auth_chain: auth_chain_ids
    			.filter_map(|id| services().rooms.timeline.get_pdu_json(&id).ok().flatten())
    			.map(PduEvent::convert_to_outgoing_federation_event)
    			.collect(),
    		state: state_ids
    			.iter()
    			.filter_map(|(_, id)| services().rooms.timeline.get_pdu_json(id).ok().flatten())
    			.map(PduEvent::convert_to_outgoing_federation_event)
    			.collect(),
    		event: None, // TODO: handle restricted joins
    	})
    
    /// # `PUT /_matrix/federation/v1/send_join/{roomId}/{eventId}`
    ///
    /// Submits a signed join event.
    
    pub async fn create_join_event_v1_route(
    
    	body: Ruma<create_join_event::v1::Request>,
    
    ) -> Result<create_join_event::v1::Response> {
    
    🥺's avatar
    🥺 committed
    	let sender_servername = body
    		.sender_servername
    		.as_ref()
    		.expect("server is authenticated");
    
    	let room_state = create_join_event(sender_servername, &body.room_id, &body.pdu).await?;
    
    	Ok(create_join_event::v1::Response {
    		room_state,
    	})
    
    /// # `PUT /_matrix/federation/v2/send_join/{roomId}/{eventId}`
    ///
    /// Submits a signed join event.
    
    pub async fn create_join_event_v2_route(
    
    	body: Ruma<create_join_event::v2::Request>,
    
    ) -> Result<create_join_event::v2::Response> {
    
    🥺's avatar
    🥺 committed
    	let sender_servername = body
    		.sender_servername
    		.as_ref()
    		.expect("server is authenticated");
    
    
    	let create_join_event::v1::RoomState {
    		auth_chain,
    		state,
    		event,
    	} = create_join_event(sender_servername, &body.room_id, &body.pdu).await?;
    	let room_state = create_join_event::v2::RoomState {
    		members_omitted: false,
    		auth_chain,
    		state,
    		event,
    		servers_in_room: None,
    	};
    
    	Ok(create_join_event::v2::Response {
    		room_state,
    	})
    
    /// # `PUT /_matrix/federation/v2/invite/{roomId}/{eventId}`
    ///
    /// Invites a remote user to a room.
    
    pub async fn create_invite_route(body: Ruma<create_invite::v2::Request>) -> Result<create_invite::v2::Response> {
    
    🥺's avatar
    🥺 committed
    	let sender_servername = body
    		.sender_servername
    		.as_ref()
    		.expect("server is authenticated");
    
    🥺's avatar
    🥺 committed
    	services()
    		.rooms
    		.event_handler
    		.acl_check(sender_servername, &body.room_id)?;
    
    🥺's avatar
    🥺 committed
    	if !services()
    		.globals
    		.supported_room_versions()
    		.contains(&body.room_version)
    	{
    
    		return Err(Error::BadRequest(
    			ErrorKind::IncompatibleRoomVersion {
    				room_version: body.room_version.clone(),
    			},
    			"Server does not support this room version.",
    		));
    	}
    
    	let mut signed_event = utils::to_canonical_object(&body.event).map_err(|e| {
    		error!("Failed to convert invite event to canonical JSON: {}", e);
    		Error::BadRequest(ErrorKind::InvalidParam, "Invite event is invalid.")
    	})?;
    
    	ruma::signatures::hash_and_sign_event(
    		services().globals.server_name().as_str(),
    		services().globals.keypair(),
    		&mut signed_event,
    		&body.room_version,
    	)
    	.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Failed to sign event."))?;
    
    	// Generate event id
    	let event_id = EventId::parse(format!(
    		"${}",
    		ruma::signatures::reference_hash(&signed_event, &body.room_version)
    			.expect("ruma can calculate reference hashes")
    	))
    	.expect("ruma's reference hashes are valid event ids");
    
    	// Add event_id back
    	signed_event.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.to_string()));
    
    	let sender: OwnedUserId = serde_json::from_value(
    		signed_event
    			.get("sender")
    			.ok_or(Error::BadRequest(ErrorKind::InvalidParam, "Event had no sender field."))?
    			.clone()
    			.into(),
    	)
    	.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "sender is not a user id."))?;
    
    	let invited_user: Box<_> = serde_json::from_value(
    		signed_event
    			.get("state_key")
    			.ok_or(Error::BadRequest(ErrorKind::InvalidParam, "Event had no state_key field."))?
    			.clone()
    			.into(),
    	)
    	.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "state_key is not a user id."))?;
    
    	if services().rooms.metadata.is_banned(&body.room_id)? && !services().users.is_admin(&invited_user)? {
    		info!(
    			"Received remote invite from server {} for room {} and for user {invited_user}, but room is banned by us.",
    			&sender_servername, &body.room_id
    		);
    		return Err(Error::BadRequest(
    			ErrorKind::Forbidden,
    			"This room is banned on this homeserver.",
    		));
    	}
    
    	if services().globals.block_non_admin_invites() && !services().users.is_admin(&invited_user)? {
    		info!(
    			"Received remote invite from server {} for room {} and for user {invited_user} who is not an admin, but \
    			 \"block_non_admin_invites\" is enabled, rejecting.",
    			&sender_servername, &body.room_id
    		);
    		return Err(Error::BadRequest(
    			ErrorKind::Forbidden,
    			"This server does not allow room invites.",
    		));
    	}
    
    	let mut invite_state = body.invite_room_state.clone();
    
    	let mut event: JsonObject = serde_json::from_str(body.event.get())
    		.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid invite event bytes."))?;
    
    
    	event.insert("event_id".to_owned(), "$placeholder".into());
    
    
    	let pdu: PduEvent = serde_json::from_value(event.into()).map_err(|e| {
    		warn!("Invalid invite event: {}", e);
    		Error::BadRequest(ErrorKind::InvalidParam, "Invalid invite event.")
    	})?;
    
    	invite_state.push(pdu.to_stripped_state_event());
    
    	// If we are active in the room, the remote server will notify us about the join
    	// via /send
    
    🥺's avatar
    🥺 committed
    	if !services()
    		.rooms
    		.state_cache
    		.server_in_room(services().globals.server_name(), &body.room_id)?
    	{
    
    		services()
    			.rooms
    			.state_cache
    			.update_membership(
    				&body.room_id,
    				&invited_user,
    				RoomMemberEventContent::new(MembershipState::Invite),
    				&sender,
    				Some(invite_state),
    				true,
    			)
    			.await?;
    	}
    
    	Ok(create_invite::v2::Response {
    		event: PduEvent::convert_to_outgoing_federation_event(signed_event),
    	})
    
    /// # `GET /_matrix/federation/v1/user/devices/{userId}`
    ///
    /// Gets information on all devices of the user.
    
    pub async fn get_devices_route(body: Ruma<get_devices::v1::Request>) -> Result<get_devices::v1::Response> {
    	if body.user_id.server_name() != services().globals.server_name() {
    		return Err(Error::BadRequest(
    			ErrorKind::InvalidParam,
    			"Tried to access user from other server.",
    		));
    	}
    
    
    🥺's avatar
    🥺 committed
    	let sender_servername = body
    		.sender_servername
    		.as_ref()
    		.expect("server is authenticated");
    
    
    	Ok(get_devices::v1::Response {
    		user_id: body.user_id.clone(),
    		stream_id: services()
    			.users
    			.get_devicelist_version(&body.user_id)?
    			.unwrap_or(0)
    			.try_into()
    			.expect("version will not grow that large"),
    		devices: services()
    			.users
    			.all_devices_metadata(&body.user_id)
    
    			.filter_map(|metadata| {
    				let device_id_string = metadata.device_id.as_str().to_owned();
    
    🥺's avatar
    🥺 committed
    				let device_display_name = if services().globals.allow_device_name_federation() {
    					metadata.display_name
    				} else {
    					Some(device_id_string)
    
    				};
    				Some(UserDevice {
    
    🥺's avatar
    🥺 committed
    					keys: services()
    						.users
    						.get_device_keys(&body.user_id, &metadata.device_id)
    						.ok()??,
    
    					device_id: metadata.device_id,
    					device_display_name,
    				})
    			})
    			.collect(),
    
    🥺's avatar
    🥺 committed
    		master_key: services()
    			.users
    			.get_master_key(None, &body.user_id, &|u| u.server_name() == sender_servername)?,
    
    		self_signing_key: services()
    			.users
    			.get_self_signing_key(None, &body.user_id, &|u| u.server_name() == sender_servername)?,
    	})
    
    /// # `GET /_matrix/federation/v1/query/directory`
    ///
    /// Resolve a room alias to a room id.
    
    Jonas Platte's avatar
    Jonas Platte committed
    pub async fn get_room_information_route(
    
    	body: Ruma<get_room_information::v1::Request>,
    
    ) -> Result<get_room_information::v1::Response> {
    
    	let room_id = services()
    		.rooms
    		.alias
    		.resolve_local_alias(&body.room_alias)?
    		.ok_or(Error::BadRequest(ErrorKind::NotFound, "Room alias not found."))?;
    
    	Ok(get_room_information::v1::Response {
    		room_id,
    		servers: vec![services().globals.server_name().to_owned()],
    	})
    
    /// # `GET /_matrix/federation/v1/query/profile`
    ///
    /// Gets information on a profile.
    
    Jonas Platte's avatar
    Jonas Platte committed
    pub async fn get_profile_information_route(
    
    	body: Ruma<get_profile_information::v1::Request>,
    
    ) -> Result<get_profile_information::v1::Response> {
    
    	if body.user_id.server_name() != services().globals.server_name() {
    		return Err(Error::BadRequest(
    			ErrorKind::InvalidParam,
    			"User does not belong to this server",
    		));
    	}
    
    	let mut displayname = None;
    	let mut avatar_url = None;
    	let mut blurhash = None;
    
    	match &body.field {
    		Some(ProfileField::DisplayName) => {
    			displayname = services().users.displayname(&body.user_id)?;
    		},
    		Some(ProfileField::AvatarUrl) => {
    			avatar_url = services().users.avatar_url(&body.user_id)?;
    			blurhash = services().users.blurhash(&body.user_id)?;
    		},
    		// TODO: what to do with custom
    		Some(_) => {},
    		None => {
    			displayname = services().users.displayname(&body.user_id)?;
    			avatar_url = services().users.avatar_url(&body.user_id)?;
    			blurhash = services().users.blurhash(&body.user_id)?;
    		},
    	}
    
    	Ok(get_profile_information::v1::Response {
    		displayname,
    		avatar_url,
    
    /// # `POST /_matrix/federation/v1/user/keys/query`
    ///
    /// Gets devices and identity keys for the given users.
    
    Timo Kösters's avatar
    Timo Kösters committed
    pub async fn get_keys_route(body: Ruma<get_keys::v1::Request>) -> Result<get_keys::v1::Response> {
    
    🥺's avatar
    🥺 committed
    	if body
    		.device_keys
    		.iter()
    		.any(|(u, _)| u.server_name() != services().globals.server_name())
    	{
    
    		return Err(Error::BadRequest(
    			ErrorKind::InvalidParam,
    			"User does not belong to this server.",
    		));
    	}
    
    	let result = get_keys_helper(
    		None,
    		&body.device_keys,
    		|u| Some(u.server_name()) == body.sender_servername.as_deref(),
    		services().globals.allow_device_name_federation(),
    	)
    	.await?;
    
    	Ok(get_keys::v1::Response {
    		device_keys: result.device_keys,
    		master_keys: result.master_keys,
    		self_signing_keys: result.self_signing_keys,
    	})
    
    /// # `POST /_matrix/federation/v1/user/keys/claim`
    ///
    /// Claims one-time keys.
    
    pub async fn claim_keys_route(body: Ruma<claim_keys::v1::Request>) -> Result<claim_keys::v1::Response> {
    
    🥺's avatar
    🥺 committed
    	if body
    		.one_time_keys
    		.iter()
    		.any(|(u, _)| u.server_name() != services().globals.server_name())
    	{
    
    		return Err(Error::BadRequest(
    			ErrorKind::InvalidParam,
    			"Tried to access user from other server.",
    		));
    	}
    
    	let result = claim_keys_helper(&body.one_time_keys).await?;
    
    	Ok(claim_keys::v1::Response {
    		one_time_keys: result.one_time_keys,
    	})
    
    /// # `GET /.well-known/matrix/server`
    pub async fn well_known_server_route() -> Result<impl IntoResponse> {
    
    	if !services().globals.allow_federation() {
    		return Err(Error::bad_config("Federation is disabled."));
    	}
    
    
    	let server_url = match services().globals.well_known_server() {
    		Some(url) => url.clone(),
    		None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")),
    	};
    
    	Ok(Json(serde_json::json!({
    		"m.server": server_url
    	})))
    
    /// # `GET /_matrix/federation/v1/hierarchy/{roomId}`
    ///
    /// Gets the space tree in a depth-first manner to locate child rooms of a given
    /// space.
    pub async fn get_hierarchy_route(body: Ruma<get_hierarchy::v1::Request>) -> Result<get_hierarchy::v1::Response> {
    
    🥺's avatar
    🥺 committed
    	let sender_servername = body
    		.sender_servername
    		.as_ref()
    		.expect("server is authenticated");
    
    
    	if services().rooms.metadata.exists(&body.room_id)? {
    
    🥺's avatar
    🥺 committed
    		services()
    			.rooms
    			.spaces
    			.get_federation_hierarchy(&body.room_id, sender_servername, body.suggested_only)
    			.await
    
    	} else {
    		Err(Error::BadRequest(ErrorKind::NotFound, "Room does not exist."))
    	}
    }