mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-11-12 19:56:48 +00:00
feat: Add support for MSC4560
This commit is contained in:
14 files changed
+460
-125
No files matched your search
Generated
+11
-11
@@ -5019,7 +5019,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma"
|
||||
version = "0.17.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=f5e5e1c885f598036fd4326c566e9d1928027d5d#f5e5e1c885f598036fd4326c566e9d1928027d5d"
|
||||
source = "git+https://github.com/gingershaped/ruwuma.git?rev=ef98d8d0b111f563fab960c3f696493979fba3a7#ef98d8d0b111f563fab960c3f696493979fba3a7"
|
||||
dependencies = [
|
||||
"assign",
|
||||
"js_int",
|
||||
@@ -5038,7 +5038,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-appservice-api"
|
||||
version = "0.17.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=f5e5e1c885f598036fd4326c566e9d1928027d5d#f5e5e1c885f598036fd4326c566e9d1928027d5d"
|
||||
source = "git+https://github.com/gingershaped/ruwuma.git?rev=ef98d8d0b111f563fab960c3f696493979fba3a7#ef98d8d0b111f563fab960c3f696493979fba3a7"
|
||||
dependencies = [
|
||||
"http",
|
||||
"js_int",
|
||||
@@ -5051,7 +5051,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-client-api"
|
||||
version = "0.25.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=f5e5e1c885f598036fd4326c566e9d1928027d5d#f5e5e1c885f598036fd4326c566e9d1928027d5d"
|
||||
source = "git+https://github.com/gingershaped/ruwuma.git?rev=ef98d8d0b111f563fab960c3f696493979fba3a7#ef98d8d0b111f563fab960c3f696493979fba3a7"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"assign",
|
||||
@@ -5073,7 +5073,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-common"
|
||||
version = "0.20.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=f5e5e1c885f598036fd4326c566e9d1928027d5d#f5e5e1c885f598036fd4326c566e9d1928027d5d"
|
||||
source = "git+https://github.com/gingershaped/ruwuma.git?rev=ef98d8d0b111f563fab960c3f696493979fba3a7#ef98d8d0b111f563fab960c3f696493979fba3a7"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"base64 0.23.1",
|
||||
@@ -5106,7 +5106,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-events"
|
||||
version = "0.35.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=f5e5e1c885f598036fd4326c566e9d1928027d5d#f5e5e1c885f598036fd4326c566e9d1928027d5d"
|
||||
source = "git+https://github.com/gingershaped/ruwuma.git?rev=ef98d8d0b111f563fab960c3f696493979fba3a7#ef98d8d0b111f563fab960c3f696493979fba3a7"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"indexmap 2.14.2",
|
||||
@@ -5127,7 +5127,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-federation-api"
|
||||
version = "0.16.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=f5e5e1c885f598036fd4326c566e9d1928027d5d#f5e5e1c885f598036fd4326c566e9d1928027d5d"
|
||||
source = "git+https://github.com/gingershaped/ruwuma.git?rev=ef98d8d0b111f563fab960c3f696493979fba3a7#ef98d8d0b111f563fab960c3f696493979fba3a7"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
@@ -5149,7 +5149,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-identifiers-validation"
|
||||
version = "0.12.1"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=f5e5e1c885f598036fd4326c566e9d1928027d5d#f5e5e1c885f598036fd4326c566e9d1928027d5d"
|
||||
source = "git+https://github.com/gingershaped/ruwuma.git?rev=ef98d8d0b111f563fab960c3f696493979fba3a7#ef98d8d0b111f563fab960c3f696493979fba3a7"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"thiserror 2.0.21",
|
||||
@@ -5158,7 +5158,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-macros"
|
||||
version = "0.20.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=f5e5e1c885f598036fd4326c566e9d1928027d5d#f5e5e1c885f598036fd4326c566e9d1928027d5d"
|
||||
source = "git+https://github.com/gingershaped/ruwuma.git?rev=ef98d8d0b111f563fab960c3f696493979fba3a7#ef98d8d0b111f563fab960c3f696493979fba3a7"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"cfg-if",
|
||||
@@ -5174,7 +5174,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-push-gateway-api"
|
||||
version = "0.16.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=f5e5e1c885f598036fd4326c566e9d1928027d5d#f5e5e1c885f598036fd4326c566e9d1928027d5d"
|
||||
source = "git+https://github.com/gingershaped/ruwuma.git?rev=ef98d8d0b111f563fab960c3f696493979fba3a7#ef98d8d0b111f563fab960c3f696493979fba3a7"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
@@ -5186,7 +5186,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-signatures"
|
||||
version = "0.22.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=f5e5e1c885f598036fd4326c566e9d1928027d5d#f5e5e1c885f598036fd4326c566e9d1928027d5d"
|
||||
source = "git+https://github.com/gingershaped/ruwuma.git?rev=ef98d8d0b111f563fab960c3f696493979fba3a7#ef98d8d0b111f563fab960c3f696493979fba3a7"
|
||||
dependencies = [
|
||||
"base64 0.23.1",
|
||||
"ed25519-dalek 3.0.0",
|
||||
@@ -5203,7 +5203,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-state-res"
|
||||
version = "0.18.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=f5e5e1c885f598036fd4326c566e9d1928027d5d#f5e5e1c885f598036fd4326c566e9d1928027d5d"
|
||||
source = "git+https://github.com/gingershaped/ruwuma.git?rev=ef98d8d0b111f563fab960c3f696493979fba3a7#ef98d8d0b111f563fab960c3f696493979fba3a7"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
|
||||
+3
-2
@@ -347,8 +347,8 @@ version = "1.1.1"
|
||||
|
||||
# Used for matrix spec type definitions and helpers
|
||||
[workspace.dependencies.ruma]
|
||||
git = "https://github.com/ruma/ruma.git"
|
||||
rev = "f5e5e1c885f598036fd4326c566e9d1928027d5d"
|
||||
git = "https://github.com/gingershaped/ruwuma.git"
|
||||
rev = "ef98d8d0b111f563fab960c3f696493979fba3a7"
|
||||
features = [
|
||||
"appservice-api-c",
|
||||
"client-api",
|
||||
@@ -388,6 +388,7 @@ features = [
|
||||
"unstable-msc4494",
|
||||
"unstable-msc4484",
|
||||
"unstable-msc4540",
|
||||
"unstable-msc4560",
|
||||
"unstable-extensible-events",
|
||||
]
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
Implemented unstable support for [MSC4560] (Stripped state requests over federation). Contributed by @ginger.
|
||||
|
||||
[MSC4560]: https://github.com/matrix-org/matrix-spec-proposals/pull/4560
|
||||
@@ -2,11 +2,12 @@ mod aliases;
|
||||
mod create;
|
||||
mod event;
|
||||
mod initial_sync;
|
||||
mod stripped_state;
|
||||
mod summary;
|
||||
mod upgrade;
|
||||
|
||||
pub(crate) use self::{
|
||||
aliases::get_room_aliases_route, create::create_room_route, event::get_room_event_route,
|
||||
initial_sync::room_initial_sync_route, summary::get_room_summary,
|
||||
upgrade::upgrade_room_route,
|
||||
initial_sync::room_initial_sync_route, stripped_state::get_stripped_state_route,
|
||||
summary::get_room_summary, upgrade::upgrade_room_route,
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
use axum::extract::State;
|
||||
use conduwuit::{Err, Result};
|
||||
use ruma::{
|
||||
api::client::state::get_stripped_state, assign, events::ALLOWED_STRIPPED_STATE_EVENTS,
|
||||
};
|
||||
use service::rooms::summary::{Accessibility, RoomStrippedState};
|
||||
|
||||
use crate::{Ruma, router::ClientIdentity};
|
||||
|
||||
/// # `POST /_matrix/client/unstable/org.continuwuity.msc4560.stripped_state/{room_id_or_alias}` ([MSC4560])
|
||||
///
|
||||
/// Returns a room's stripped state.
|
||||
///
|
||||
/// [MSC4560]: https://github.com/matrix-org/matrix-spec-proposals/pull/4560
|
||||
#[tracing::instrument(skip_all, name = "stripped_state", level = "info")]
|
||||
pub(crate) async fn get_stripped_state_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<get_stripped_state::unstable::Request>,
|
||||
) -> Result<get_stripped_state::unstable::Response> {
|
||||
let user_id = body.identity.as_ref().and_then(ClientIdentity::sender_user);
|
||||
|
||||
if body.via.len() > get_stripped_state::unstable::MAX_VIAS {
|
||||
return Err!(Request(TooLarge("Too many vias, please be less indecisive.")));
|
||||
}
|
||||
|
||||
if let Some(forbidden_type) = body
|
||||
.state
|
||||
.iter()
|
||||
.find(|event_type| !ALLOWED_STRIPPED_STATE_EVENTS.contains(event_type))
|
||||
{
|
||||
return Err!(Request(InvalidParam(
|
||||
"Event type {forbidden_type} may not be requested as stripped state."
|
||||
)));
|
||||
}
|
||||
|
||||
let (room_id, _) = services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_with_servers(&body.room_id_or_alias, Some(body.via.clone()))
|
||||
.await?;
|
||||
|
||||
if services.rooms.metadata.is_banned(&room_id).await {
|
||||
return Err!(Request(Forbidden("This room is banned on this homeserver.")));
|
||||
}
|
||||
|
||||
match services
|
||||
.rooms
|
||||
.summary
|
||||
.get_stripped_state_for_user(
|
||||
user_id,
|
||||
&room_id,
|
||||
&body.via,
|
||||
&body.state,
|
||||
body.num_joined_members,
|
||||
)
|
||||
.await
|
||||
{
|
||||
| Accessibility::Accessible((
|
||||
via,
|
||||
RoomStrippedState { state, num_joined_members, .. },
|
||||
)) => Ok(
|
||||
assign!(get_stripped_state::unstable::Response::new(via), { state, num_joined_members }),
|
||||
),
|
||||
| Accessibility::Inaccessible => {
|
||||
Err!(Request(Forbidden("You may not request this room's stripped state.")))
|
||||
},
|
||||
| Accessibility::NotFound => {
|
||||
Err!(Request(NotFound("This room does not exist.")))
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ pub(crate) async fn get_room_summary(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<get_summary::v1::Request>,
|
||||
) -> Result<get_summary::v1::Response> {
|
||||
let (room_id, servers) = services
|
||||
let (room_id, _) = services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_with_servers(&body.room_id_or_alias, Some(body.via.clone()))
|
||||
@@ -27,12 +27,9 @@ pub(crate) async fn get_room_summary(
|
||||
.rooms
|
||||
.summary
|
||||
.get_room_summary_for_user(
|
||||
body.identity
|
||||
.as_ref()
|
||||
.map(ClientIdentity::expect_sender_user)
|
||||
.transpose()?,
|
||||
body.identity.as_ref().and_then(ClientIdentity::sender_user),
|
||||
&room_id,
|
||||
&servers,
|
||||
&body.via,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -184,6 +184,7 @@ pub fn build(router: Router<State>, state: State) -> Router<State> {
|
||||
.ruma_route(&client::get_mutual_rooms_unstable_route)
|
||||
.ruma_route(&client::get_mutual_rooms_route)
|
||||
.ruma_route(&client::get_room_summary)
|
||||
.ruma_route(&client::get_stripped_state_route)
|
||||
.ruma_route(&client::get_suspended_status)
|
||||
.ruma_route(&client::put_suspended_status)
|
||||
.ruma_route(&client::get_lock_status)
|
||||
@@ -229,6 +230,7 @@ pub fn build(router: Router<State>, state: State) -> Router<State> {
|
||||
.ruma_route(&server::get_keys_route)
|
||||
.ruma_route(&server::claim_keys_route)
|
||||
.ruma_route(&server::get_hierarchy_route)
|
||||
.ruma_route(&server::get_stripped_state_route)
|
||||
.ruma_route(&server::well_known_server)
|
||||
.ruma_route(&server::get_content_route)
|
||||
.ruma_route(&server::get_content_thumbnail_route)
|
||||
|
||||
@@ -13,7 +13,7 @@ use ruma::{
|
||||
},
|
||||
};
|
||||
use serde_json::value::to_raw_value;
|
||||
use service::rooms::membership::JoinConditionResult;
|
||||
use service::rooms::membership::{JoinConditionEntity, JoinConditionResult};
|
||||
|
||||
use crate::Ruma;
|
||||
|
||||
@@ -95,7 +95,11 @@ pub(crate) async fn create_join_event_template_route(
|
||||
match services
|
||||
.rooms
|
||||
.membership
|
||||
.evaluate_join_conditions(&body.room_id, &body.user_id, &conditions)
|
||||
.evaluate_join_conditions(
|
||||
&body.room_id,
|
||||
JoinConditionEntity::User(&body.user_id),
|
||||
&conditions,
|
||||
)
|
||||
.await
|
||||
{
|
||||
| JoinConditionResult::Satisfied => {
|
||||
|
||||
@@ -18,6 +18,7 @@ pub(super) mod send_knock;
|
||||
pub(super) mod send_leave;
|
||||
pub(super) mod state;
|
||||
pub(super) mod state_ids;
|
||||
pub(super) mod stripped_state;
|
||||
pub(super) mod user;
|
||||
pub(super) mod version;
|
||||
pub(super) mod well_known;
|
||||
@@ -42,6 +43,7 @@ pub(super) use send_knock::*;
|
||||
pub(super) use send_leave::*;
|
||||
pub(super) use state::*;
|
||||
pub(super) use state_ids::*;
|
||||
pub(super) use stripped_state::*;
|
||||
pub(super) use user::*;
|
||||
pub(super) use version::*;
|
||||
pub(super) use well_known::*;
|
||||
|
||||
@@ -20,7 +20,7 @@ use ruma::{
|
||||
room_version_rules::RoomVersionRules,
|
||||
};
|
||||
use serde_json::value::to_raw_value;
|
||||
use service::rooms::membership::JoinConditionResult;
|
||||
use service::rooms::membership::{JoinConditionEntity, JoinConditionResult};
|
||||
|
||||
use crate::{Ruma, server::utils::validate_any_membership_event};
|
||||
|
||||
@@ -123,7 +123,11 @@ async fn create_join_event(
|
||||
match services
|
||||
.rooms
|
||||
.membership
|
||||
.evaluate_join_conditions(room_id, &sender, &conditions)
|
||||
.evaluate_join_conditions(
|
||||
room_id,
|
||||
JoinConditionEntity::User(&sender),
|
||||
&conditions,
|
||||
)
|
||||
.await
|
||||
{
|
||||
| JoinConditionResult::Satisfied => {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
use axum::extract::State;
|
||||
use conduwuit::{Err, Result};
|
||||
use ruma::{
|
||||
api::federation::event::get_stripped_state, assign, events::ALLOWED_STRIPPED_STATE_EVENTS,
|
||||
};
|
||||
use service::rooms::summary::{Accessibility, RoomStrippedState};
|
||||
|
||||
use crate::Ruma;
|
||||
|
||||
/// # `POST /_matrix/federation/unstable/org.continuwuity.msc4560.stripped_state/{room_id}` ([MSC4560])
|
||||
///
|
||||
/// Returns a room's stripped state.
|
||||
///
|
||||
/// [MSC4560]: https://github.com/matrix-org/matrix-spec-proposals/pull/4560
|
||||
pub(crate) async fn get_stripped_state_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<get_stripped_state::unstable::Request>,
|
||||
) -> Result<get_stripped_state::unstable::Response> {
|
||||
if let Some(forbidden_type) = body
|
||||
.state
|
||||
.iter()
|
||||
.find(|event_type| !ALLOWED_STRIPPED_STATE_EVENTS.contains(event_type))
|
||||
{
|
||||
return Err!(Request(InvalidParam(
|
||||
"Event type {forbidden_type} may not be requested as stripped state."
|
||||
)));
|
||||
}
|
||||
|
||||
if services.rooms.metadata.is_banned(&body.room_id).await {
|
||||
return Err!(Request(Forbidden("This room is banned on this homeserver.")));
|
||||
}
|
||||
|
||||
match services
|
||||
.rooms
|
||||
.summary
|
||||
.get_local_stripped_state_for_server(
|
||||
&body.identity,
|
||||
&body.room_id,
|
||||
&body.state,
|
||||
body.num_joined_members,
|
||||
)
|
||||
.await
|
||||
{
|
||||
| Accessibility::Accessible(RoomStrippedState { state, num_joined_members, .. }) => Ok(
|
||||
assign!(get_stripped_state::unstable::Response::default(), { state, num_joined_members }),
|
||||
),
|
||||
| Accessibility::Inaccessible => {
|
||||
Err!(Request(Forbidden("You may not access this room's stripped state.")))
|
||||
},
|
||||
| Accessibility::NotFound => {
|
||||
Err!(Request(NotFound("This room is not known to this server.")))
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -50,5 +50,7 @@ pub fn unstable_features() -> BTreeMap<String, bool> {
|
||||
("uk.timedout.msc4494".to_owned(), true),
|
||||
// server administration OAuth scope (https://github.com/matrix-org/matrix-spec-proposals/pull/4484)
|
||||
("org.continuwuity.msc4484.unstable".to_owned(), true),
|
||||
// stripped state over federation (https://github.com/matrix-org/matrix-spec-proposals/pull/4560)
|
||||
("org.continuwuity.msc4560".to_owned(), true),
|
||||
])
|
||||
}
|
||||
@@ -15,7 +15,7 @@ use database::Database;
|
||||
use futures::{FutureExt, StreamExt, TryFutureExt, join};
|
||||
use ruma::{
|
||||
CanonicalJsonObject, CanonicalJsonValue, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId,
|
||||
RoomVersionId, UserId,
|
||||
RoomVersionId, ServerName, UserId,
|
||||
api::{
|
||||
error::{ErrorKind, IncompatibleRoomVersionErrorData},
|
||||
federation,
|
||||
@@ -52,6 +52,20 @@ pub enum JoinConditionResult {
|
||||
UnableToAuthorizeJoin,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum JoinConditionEntity<'id> {
|
||||
User(&'id UserId),
|
||||
Server(&'id ServerName),
|
||||
}
|
||||
|
||||
impl<'id> From<&'id UserId> for JoinConditionEntity<'id> {
|
||||
fn from(value: &'id UserId) -> Self { Self::User(value) }
|
||||
}
|
||||
|
||||
impl<'id> From<&'id ServerName> for JoinConditionEntity<'id> {
|
||||
fn from(value: &'id ServerName) -> Self { Self::Server(value) }
|
||||
}
|
||||
|
||||
pub struct Service {
|
||||
services: Services,
|
||||
}
|
||||
@@ -250,7 +264,7 @@ impl Service {
|
||||
// This is a restricted room, check if we can complete the join
|
||||
// requirements locally.
|
||||
if self
|
||||
.evaluate_join_conditions(room_id, sender_user, conditions)
|
||||
.evaluate_join_conditions(room_id, sender_user.into(), conditions)
|
||||
.await
|
||||
== JoinConditionResult::Satisfied
|
||||
{
|
||||
@@ -834,7 +848,7 @@ impl Service {
|
||||
pub async fn evaluate_join_conditions(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
user_id: &UserId,
|
||||
entity: JoinConditionEntity<'_>,
|
||||
conditions: &Restricted,
|
||||
) -> JoinConditionResult {
|
||||
if conditions.allow.is_empty() {
|
||||
@@ -845,7 +859,7 @@ impl Service {
|
||||
let mut unable_to_authorize = false;
|
||||
|
||||
for rule in &conditions.allow {
|
||||
match self.evaluate_allow_rule(room_id, user_id, rule).await {
|
||||
match self.evaluate_allow_rule(room_id, entity, rule).await {
|
||||
| JoinConditionResult::Satisfied => {
|
||||
// This allow rule is satisfied, the user can join.
|
||||
return JoinConditionResult::Satisfied;
|
||||
@@ -879,7 +893,7 @@ impl Service {
|
||||
async fn evaluate_allow_rule(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
user_id: &UserId,
|
||||
entity: JoinConditionEntity<'_>,
|
||||
rule: &AllowRule,
|
||||
) -> JoinConditionResult {
|
||||
match rule {
|
||||
@@ -890,17 +904,25 @@ impl Service {
|
||||
.server_in_room(self.services.globals.server_name(), condition_room)
|
||||
.await
|
||||
{
|
||||
if self
|
||||
.services
|
||||
.state_cache
|
||||
.is_joined(user_id, condition_room)
|
||||
.await
|
||||
{
|
||||
let is_joined = match entity {
|
||||
| JoinConditionEntity::User(user_id) =>
|
||||
self.services
|
||||
.state_cache
|
||||
.is_joined(user_id, condition_room)
|
||||
.await,
|
||||
| JoinConditionEntity::Server(server) =>
|
||||
self.services
|
||||
.state_cache
|
||||
.server_in_room(server, room_id)
|
||||
.await,
|
||||
};
|
||||
|
||||
if is_joined {
|
||||
debug!(
|
||||
?user_id,
|
||||
?entity,
|
||||
?room_id,
|
||||
?condition_room,
|
||||
"User may join via membership in {condition_room}"
|
||||
"Entity may join via membership"
|
||||
);
|
||||
JoinConditionResult::Satisfied
|
||||
} else {
|
||||
@@ -910,14 +932,22 @@ impl Service {
|
||||
JoinConditionResult::UnableToAuthorizeJoin
|
||||
}
|
||||
},
|
||||
| other if other.rule_type() == "fi.mau.spam_checker" =>
|
||||
self.services
|
||||
.antispam
|
||||
.meowlnir_accept_make_join(room_id.to_owned(), user_id.to_owned())
|
||||
.await,
|
||||
| other if other.rule_type() == "fi.mau.spam_checker" => {
|
||||
if let JoinConditionEntity::User(user_id) = entity {
|
||||
self.services
|
||||
.antispam
|
||||
.meowlnir_accept_make_join(room_id.to_owned(), user_id.to_owned())
|
||||
.await
|
||||
} else {
|
||||
// We can't currently check server names against the spam
|
||||
// checker, so we just assume that they
|
||||
// can't access the room for now.
|
||||
JoinConditionResult::Unsatisfied
|
||||
}
|
||||
},
|
||||
| unknown => {
|
||||
debug!(
|
||||
?user_id,
|
||||
?entity,
|
||||
?room_id,
|
||||
rule_type = unknown.rule_type(),
|
||||
"Could not check unknown allow rule"
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use std::{collections::HashSet, sync::Arc};
|
||||
use std::{
|
||||
collections::{BTreeSet, HashSet},
|
||||
pin::pin,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use conduwuit::{
|
||||
Err, Event, Result, debug, info,
|
||||
@@ -10,18 +14,27 @@ use ruma::{
|
||||
OwnedEventId, OwnedRoomId, OwnedServerName, RoomId, ServerName, UInt, UserId,
|
||||
api::{
|
||||
client::space::SpaceHierarchyRoomsChunk,
|
||||
federation::space::{SpaceHierarchyParentSummary, get_hierarchy},
|
||||
error::ErrorKind,
|
||||
federation::{
|
||||
event::get_stripped_state,
|
||||
space::{SpaceHierarchyParentSummary, get_hierarchy},
|
||||
},
|
||||
},
|
||||
assign,
|
||||
events::{
|
||||
StateEventType,
|
||||
AnyStrippedStateEvent, StateEventType,
|
||||
room::member::MembershipState,
|
||||
space::child::{HierarchySpaceChildEvent, SpaceChildEventContent},
|
||||
},
|
||||
room::{JoinRuleSummary, RestrictedSummary, RoomSummary, RoomType},
|
||||
room::{JoinRule, RoomSummary, RoomType},
|
||||
serde::Raw,
|
||||
};
|
||||
|
||||
use crate::{Dep, rooms, sending};
|
||||
use crate::{
|
||||
Dep, globals,
|
||||
rooms::{self, membership::JoinConditionResult},
|
||||
sending,
|
||||
};
|
||||
|
||||
pub struct Service {
|
||||
services: Services,
|
||||
@@ -29,7 +42,8 @@ pub struct Service {
|
||||
|
||||
struct Services {
|
||||
event_handler: Dep<rooms::event_handler::Service>,
|
||||
metadata: Dep<rooms::metadata::Service>,
|
||||
globals: Dep<globals::Service>,
|
||||
membership: Dep<rooms::membership::Service>,
|
||||
sending: Dep<sending::Service>,
|
||||
state: Dep<rooms::state::Service>,
|
||||
state_accessor: Dep<rooms::state_accessor::Service>,
|
||||
@@ -57,13 +71,21 @@ struct SpaceChild {
|
||||
via: Vec<OwnedServerName>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[non_exhaustive]
|
||||
pub struct RoomStrippedState {
|
||||
pub state: Vec<Raw<AnyStrippedStateEvent>>,
|
||||
pub num_joined_members: Option<UInt>,
|
||||
}
|
||||
|
||||
impl crate::Service for Service {
|
||||
fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
|
||||
Ok(Arc::new(Self {
|
||||
services: Services {
|
||||
event_handler: args
|
||||
.depend::<rooms::event_handler::Service>("rooms::event_handler"),
|
||||
metadata: args.depend::<rooms::metadata::Service>("rooms::metadata"),
|
||||
globals: args.depend::<globals::Service>("globals"),
|
||||
membership: args.depend::<rooms::membership::Service>("rooms::membership"),
|
||||
sending: args.depend::<sending::Service>("sending"),
|
||||
state: args.depend::<rooms::state::Service>("rooms::state"),
|
||||
state_accessor: args
|
||||
@@ -92,7 +114,7 @@ impl Service {
|
||||
|
||||
summary
|
||||
} else if let Some((SpaceHierarchyParentSummary { summary, .. }, _)) =
|
||||
self.fetch_remote_summary(room_id, via, false).await?
|
||||
self.fetch_remote_room_summary(room_id, via, false).await
|
||||
{
|
||||
// A via has this room.
|
||||
|
||||
@@ -105,7 +127,7 @@ impl Service {
|
||||
};
|
||||
|
||||
// Check if the room is visible to the querying user.
|
||||
if !self.user_may_see_summary(querying_user, &summary).await {
|
||||
if !self.user_may_see_summary(querying_user, room_id).await {
|
||||
return Ok(Accessibility::Inaccessible);
|
||||
}
|
||||
|
||||
@@ -138,8 +160,8 @@ impl Service {
|
||||
SpaceHierarchyParentSummary { summary, children_state, .. },
|
||||
inaccessible_children,
|
||||
)) = self
|
||||
.fetch_remote_summary(room_id, via, suggested_only)
|
||||
.await?
|
||||
.fetch_remote_room_summary(room_id, via, suggested_only)
|
||||
.await
|
||||
{
|
||||
// A via has this room.
|
||||
|
||||
@@ -152,10 +174,7 @@ impl Service {
|
||||
};
|
||||
|
||||
// Check if the room is visible to the querying user.
|
||||
if !self
|
||||
.user_may_see_summary(querying_user, &summary.summary)
|
||||
.await
|
||||
{
|
||||
if !self.user_may_see_summary(querying_user, room_id).await {
|
||||
return Ok(Accessibility::Inaccessible);
|
||||
}
|
||||
|
||||
@@ -312,7 +331,7 @@ impl Service {
|
||||
};
|
||||
|
||||
// Check if the server can see the root room's summary
|
||||
if !self.server_may_see_summary(querying_server, &summary).await {
|
||||
if !self.server_may_see_summary(querying_server, room_id).await {
|
||||
return Accessibility::Inaccessible;
|
||||
}
|
||||
|
||||
@@ -331,7 +350,10 @@ impl Service {
|
||||
.broad_then(async |child| {
|
||||
let summary = {
|
||||
if let Some(summary) = self.build_local_room_summary(&child.state_key).await {
|
||||
if self.server_may_see_summary(querying_server, &summary).await {
|
||||
if self
|
||||
.server_may_see_summary(querying_server, &child.state_key)
|
||||
.await
|
||||
{
|
||||
Accessibility::Accessible(summary)
|
||||
} else {
|
||||
Accessibility::Inaccessible
|
||||
@@ -439,20 +461,14 @@ impl Service {
|
||||
}
|
||||
|
||||
/// Query remote servers for the summary of a room.
|
||||
async fn fetch_remote_summary(
|
||||
async fn fetch_remote_room_summary(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
via: &[OwnedServerName],
|
||||
suggested_only: bool,
|
||||
) -> Result<Option<(SpaceHierarchyParentSummary, Vec<OwnedRoomId>)>> {
|
||||
if self.services.metadata.is_disabled(room_id).await {
|
||||
return Err!(Request(Forbidden("This room is blocked by this server.")));
|
||||
}
|
||||
|
||||
) -> Option<(SpaceHierarchyParentSummary, Vec<OwnedRoomId>)> {
|
||||
if via.is_empty() {
|
||||
return Err!(Request(MissingParam(
|
||||
"No servers were provided with which to query this room's summary."
|
||||
)));
|
||||
return None;
|
||||
}
|
||||
|
||||
info!(%room_id, ?via, "Asking for room summary over federation");
|
||||
@@ -477,7 +493,7 @@ impl Service {
|
||||
}
|
||||
|
||||
info!(%room_id, %server, "Got room summary");
|
||||
return Ok(Some((room, inaccessible_children)));
|
||||
return Some((room, inaccessible_children));
|
||||
},
|
||||
| Err(err) => {
|
||||
info!(%room_id, %server, %err, "Server could not provide a summary for this room");
|
||||
@@ -486,7 +502,7 @@ impl Service {
|
||||
}
|
||||
|
||||
info!(%room_id, "No servers queried could provide a summary for this room");
|
||||
Ok(None)
|
||||
None
|
||||
}
|
||||
|
||||
/// Get the stripped m.space.child events of a room.
|
||||
@@ -536,54 +552,207 @@ impl Service {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_stripped_state_for_user(
|
||||
&self,
|
||||
querying_user: Option<&UserId>,
|
||||
room_id: &RoomId,
|
||||
via: &[OwnedServerName],
|
||||
state: &BTreeSet<StateEventType>,
|
||||
num_joined_members: bool,
|
||||
) -> Accessibility<(OwnedServerName, RoomStrippedState)> {
|
||||
if let Some(state) = self
|
||||
.build_local_stripped_state(room_id, state, num_joined_members)
|
||||
.await
|
||||
{
|
||||
if self.user_may_see_summary(querying_user, room_id).await {
|
||||
Accessibility::Accessible((self.services.globals.server_name().to_owned(), state))
|
||||
} else {
|
||||
Accessibility::Inaccessible
|
||||
}
|
||||
} else {
|
||||
self.fetch_remote_stripped_state(room_id, via, state, num_joined_members)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_local_stripped_state_for_server(
|
||||
&self,
|
||||
querying_server: &ServerName,
|
||||
room_id: &RoomId,
|
||||
state: &BTreeSet<StateEventType>,
|
||||
num_joined_members: bool,
|
||||
) -> Accessibility<RoomStrippedState> {
|
||||
if let Some(state) = self
|
||||
.build_local_stripped_state(room_id, state, num_joined_members)
|
||||
.await
|
||||
{
|
||||
if self.server_may_see_summary(querying_server, room_id).await {
|
||||
Accessibility::Accessible(state)
|
||||
} else {
|
||||
Accessibility::Inaccessible
|
||||
}
|
||||
} else {
|
||||
Accessibility::NotFound
|
||||
}
|
||||
}
|
||||
|
||||
async fn build_local_stripped_state(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
state: &BTreeSet<StateEventType>,
|
||||
num_joined_members: bool,
|
||||
) -> Option<RoomStrippedState> {
|
||||
if !self
|
||||
.services
|
||||
.state_cache
|
||||
.server_in_room(self.services.globals.server_name(), room_id)
|
||||
.await
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let current_shortstatehash = self
|
||||
.services
|
||||
.state
|
||||
.get_room_shortstatehash(room_id)
|
||||
.await
|
||||
.expect("room should have state");
|
||||
|
||||
let state = state
|
||||
.iter()
|
||||
.stream()
|
||||
.broad_flat_map(|event_type| {
|
||||
self.services
|
||||
.state_accessor
|
||||
.state_keys_with_ids::<OwnedEventId>(current_shortstatehash, event_type)
|
||||
})
|
||||
.broad_filter_map(async |(_, event_id)| {
|
||||
self.services.timeline.get_pdu(&event_id).ok().await
|
||||
})
|
||||
.ready_filter_map(|pdu| Some(pdu.into_format()))
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
let num_joined_members = if num_joined_members {
|
||||
Some(
|
||||
self.services
|
||||
.state_cache
|
||||
.room_joined_count(room_id)
|
||||
.await
|
||||
.expect("room should have a joined count")
|
||||
.try_into()
|
||||
.expect("joined count should fit into uint"),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Some(RoomStrippedState { state, num_joined_members })
|
||||
}
|
||||
|
||||
async fn fetch_remote_stripped_state(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
via: &[OwnedServerName],
|
||||
state: &BTreeSet<StateEventType>,
|
||||
num_joined_members: bool,
|
||||
) -> Accessibility<(OwnedServerName, RoomStrippedState)> {
|
||||
if via.is_empty() {
|
||||
return Accessibility::NotFound;
|
||||
}
|
||||
|
||||
let request = assign!(get_stripped_state::unstable::Request::new(room_id.to_owned()), {
|
||||
state: state.to_owned(),
|
||||
num_joined_members
|
||||
});
|
||||
|
||||
let mut responses = pin!(via.iter().stream().broad_then(async |server| {
|
||||
match self
|
||||
.services
|
||||
.sending
|
||||
.send_federation_request(server, request.clone())
|
||||
.await
|
||||
{
|
||||
| Ok(response) => Accessibility::Accessible((server.to_owned(), response)),
|
||||
| Err(err) => match err.kind() {
|
||||
| ErrorKind::Forbidden => Accessibility::Inaccessible,
|
||||
| _ => Accessibility::NotFound,
|
||||
},
|
||||
}
|
||||
}));
|
||||
|
||||
let mut got_inaccessible = false;
|
||||
|
||||
while let Some(response) = responses.next().await {
|
||||
match response {
|
||||
| Accessibility::Accessible((server, response)) => {
|
||||
return Accessibility::Accessible((server, RoomStrippedState {
|
||||
state: response.state,
|
||||
num_joined_members: response.num_joined_members,
|
||||
}));
|
||||
},
|
||||
| Accessibility::Inaccessible => {
|
||||
got_inaccessible = true;
|
||||
},
|
||||
| Accessibility::NotFound => {
|
||||
continue;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if got_inaccessible {
|
||||
Accessibility::Inaccessible
|
||||
} else {
|
||||
Accessibility::NotFound
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine if a user (possibly anonymous) may view a room summary.
|
||||
async fn user_may_see_summary(
|
||||
&self,
|
||||
querying_user: Option<&UserId>,
|
||||
summary: &RoomSummary,
|
||||
room_id: &RoomId,
|
||||
) -> bool {
|
||||
// Anyone can view the summary of world-readable rooms.
|
||||
if summary.world_readable {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the user is joined or invited they may always view the summary.
|
||||
if let Some(querying_user) = querying_user
|
||||
&& (self
|
||||
.services
|
||||
.state_cache
|
||||
.is_joined(querying_user, &summary.room_id)
|
||||
.await
|
||||
|| self
|
||||
.services
|
||||
.state_cache
|
||||
.is_invited(querying_user, &summary.room_id)
|
||||
.await)
|
||||
if self
|
||||
.services
|
||||
.state_accessor
|
||||
.is_world_readable(room_id)
|
||||
.await
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the user is joined or invited they may always view the summary.
|
||||
if let Some(querying_user) = querying_user {
|
||||
let membership = self
|
||||
.services
|
||||
.state_accessor
|
||||
.get_member(room_id, querying_user)
|
||||
.await
|
||||
.ok()
|
||||
.map_or_else(|| MembershipState::Leave, |content| content.membership);
|
||||
|
||||
if matches!(membership, MembershipState::Join | MembershipState::Invite) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, visibility depends on the join rule.
|
||||
match (&summary.join_rule, querying_user) {
|
||||
let join_rule = self.services.state_accessor.get_join_rules(room_id).await;
|
||||
|
||||
match (join_rule, querying_user) {
|
||||
// Anyone can view summaries for `public`, `knock`, and `knock_restricted` rooms.
|
||||
| (
|
||||
JoinRuleSummary::Public
|
||||
| JoinRuleSummary::Knock
|
||||
| JoinRuleSummary::KnockRestricted(_),
|
||||
_,
|
||||
) => true,
|
||||
| (JoinRule::Public | JoinRule::Knock | JoinRule::KnockRestricted(_), _) => true,
|
||||
|
||||
// The user may be able to view the summary for a `restricted` room, even if they
|
||||
// aren't invited, provided they're in one of the allowed rooms.
|
||||
| (
|
||||
JoinRuleSummary::Restricted(RestrictedSummary { allowed_room_ids, .. }),
|
||||
Some(querying_user),
|
||||
) =>
|
||||
// aren't invited, provided they fulfill the join rules.
|
||||
| (JoinRule::Restricted(conditions), Some(querying_user)) =>
|
||||
self.services
|
||||
.state_cache
|
||||
.rooms_joined(querying_user)
|
||||
.ready_any(|room_id| allowed_room_ids.contains(&room_id))
|
||||
.await,
|
||||
.membership
|
||||
.evaluate_join_conditions(room_id, querying_user.into(), &conditions)
|
||||
.await
|
||||
== JoinConditionResult::Satisfied,
|
||||
|
||||
// In all other cases, the user may not view the summary.
|
||||
| _ => false,
|
||||
@@ -594,13 +763,13 @@ impl Service {
|
||||
async fn server_may_see_summary(
|
||||
&self,
|
||||
querying_server: &ServerName,
|
||||
summary: &RoomSummary,
|
||||
room_id: &RoomId,
|
||||
) -> bool {
|
||||
// Servers may not see summaries of rooms they're ACLed from.
|
||||
if self
|
||||
.services
|
||||
.event_handler
|
||||
.acl_check(querying_server, &summary.room_id)
|
||||
.acl_check(querying_server, room_id)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
@@ -613,32 +782,27 @@ impl Service {
|
||||
if self
|
||||
.services
|
||||
.state_cache
|
||||
.server_in_room(querying_server, &summary.room_id)
|
||||
.server_in_room(querying_server, room_id)
|
||||
.await
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// For restricted rooms, federation responses must include the summary
|
||||
// when a user on the querying server can satisfy one of the join
|
||||
// conditions.
|
||||
if let JoinRuleSummary::Restricted(RestrictedSummary { allowed_room_ids, .. }) =
|
||||
&summary.join_rule
|
||||
{
|
||||
for room_id in allowed_room_ids {
|
||||
if self
|
||||
.services
|
||||
.state_cache
|
||||
.server_in_room(querying_server, room_id)
|
||||
.await
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
let join_rule = self.services.state_accessor.get_join_rules(room_id).await;
|
||||
|
||||
// Servers may see summaries in restricted rooms if they satisfy the
|
||||
// join conditions.
|
||||
if let JoinRule::Restricted(conditions) = join_rule {
|
||||
return self
|
||||
.services
|
||||
.membership
|
||||
.evaluate_join_conditions(room_id, querying_server.into(), &conditions)
|
||||
.await
|
||||
== JoinConditionResult::Satisfied;
|
||||
}
|
||||
|
||||
// If the server isn't in the room, the same visibility rules apply as
|
||||
// for anonymous summary requests.
|
||||
self.user_may_see_summary(None, summary).await
|
||||
self.user_may_see_summary(None, room_id).await
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user