sync upstream spaces/hierarchy federation MR
also had to fix a million clippy lints fix(spaces): deal with hierarchy recursion fix(spaces): properly handle max_depth refactor(spaces): token scheme to prevent clients from modifying max_depth and suggested_only perf(spaces): use tokens to skip to room to start populating results at feat(spaces): request hierarchy from servers in via field of child event Co-authored-by: Matthias Ahouansou <matthias@ahouansou.cz> Signed-off-by: strawberry <strawberry@puppygock.gay>
This commit is contained in:
parent
9115901c66
commit
28ac3790c2
5 changed files with 433 additions and 784 deletions
|
@ -2,10 +2,10 @@ use std::str::FromStr;
|
||||||
|
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::client::{error::ErrorKind, space::get_hierarchy},
|
api::client::{error::ErrorKind, space::get_hierarchy},
|
||||||
uint, UInt,
|
UInt,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{service::rooms::spaces::PagnationToken, services, Error, Result, Ruma};
|
use crate::{service::rooms::spaces::PaginationToken, services, Error, Result, Ruma};
|
||||||
|
|
||||||
/// # `GET /_matrix/client/v1/rooms/{room_id}/hierarchy`
|
/// # `GET /_matrix/client/v1/rooms/{room_id}/hierarchy`
|
||||||
///
|
///
|
||||||
|
@ -14,12 +14,10 @@ use crate::{service::rooms::spaces::PagnationToken, services, Error, Result, Rum
|
||||||
pub(crate) async fn get_hierarchy_route(body: Ruma<get_hierarchy::v1::Request>) -> Result<get_hierarchy::v1::Response> {
|
pub(crate) async fn get_hierarchy_route(body: Ruma<get_hierarchy::v1::Request>) -> Result<get_hierarchy::v1::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
let limit: usize = body
|
let limit = body
|
||||||
.limit
|
.limit
|
||||||
.unwrap_or_else(|| uint!(10))
|
.unwrap_or_else(|| UInt::from(10_u32))
|
||||||
.try_into()
|
.min(UInt::from(100_u32));
|
||||||
.unwrap_or(10)
|
|
||||||
.min(100);
|
|
||||||
|
|
||||||
let max_depth = body
|
let max_depth = body
|
||||||
.max_depth
|
.max_depth
|
||||||
|
@ -29,7 +27,7 @@ pub(crate) async fn get_hierarchy_route(body: Ruma<get_hierarchy::v1::Request>)
|
||||||
let key = body
|
let key = body
|
||||||
.from
|
.from
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|s| PagnationToken::from_str(s).ok());
|
.and_then(|s| PaginationToken::from_str(s).ok());
|
||||||
|
|
||||||
// Should prevent unexpeded behaviour in (bad) clients
|
// Should prevent unexpeded behaviour in (bad) clients
|
||||||
if let Some(ref token) = key {
|
if let Some(ref token) = key {
|
||||||
|
@ -47,8 +45,8 @@ pub(crate) async fn get_hierarchy_route(body: Ruma<get_hierarchy::v1::Request>)
|
||||||
.get_client_hierarchy(
|
.get_client_hierarchy(
|
||||||
sender_user,
|
sender_user,
|
||||||
&body.room_id,
|
&body.room_id,
|
||||||
limit,
|
limit.try_into().unwrap_or(10),
|
||||||
key.map_or(0, |token| token.skip.try_into().unwrap_or(0)),
|
key.map_or(vec![], |token| token.short_room_ids),
|
||||||
max_depth.try_into().unwrap_or(3),
|
max_depth.try_into().unwrap_or(3),
|
||||||
body.suggested_only,
|
body.suggested_only,
|
||||||
)
|
)
|
||||||
|
|
File diff suppressed because it is too large
Load diff
145
src/service/rooms/spaces/tests.rs
Normal file
145
src/service/rooms/spaces/tests.rs
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
#![cfg(test)]
|
||||||
|
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use ruma::{
|
||||||
|
api::federation::space::{SpaceHierarchyParentSummary, SpaceHierarchyParentSummaryInit},
|
||||||
|
owned_room_id, owned_server_name,
|
||||||
|
space::SpaceRoomJoinRule,
|
||||||
|
UInt,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::rooms::spaces::{get_parent_children_via, PaginationToken};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_summary_children() {
|
||||||
|
let summary: SpaceHierarchyParentSummary = SpaceHierarchyParentSummaryInit {
|
||||||
|
num_joined_members: UInt::from(1_u32),
|
||||||
|
room_id: owned_room_id!("!root:example.org"),
|
||||||
|
world_readable: true,
|
||||||
|
guest_can_join: true,
|
||||||
|
join_rule: SpaceRoomJoinRule::Public,
|
||||||
|
children_state: vec![
|
||||||
|
serde_json::from_str(
|
||||||
|
r#"{
|
||||||
|
"content": {
|
||||||
|
"via": [
|
||||||
|
"example.org"
|
||||||
|
],
|
||||||
|
"suggested": false
|
||||||
|
},
|
||||||
|
"origin_server_ts": 1629413349153,
|
||||||
|
"sender": "@alice:example.org",
|
||||||
|
"state_key": "!foo:example.org",
|
||||||
|
"type": "m.space.child"
|
||||||
|
}"#,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
serde_json::from_str(
|
||||||
|
r#"{
|
||||||
|
"content": {
|
||||||
|
"via": [
|
||||||
|
"example.org"
|
||||||
|
],
|
||||||
|
"suggested": true
|
||||||
|
},
|
||||||
|
"origin_server_ts": 1629413349157,
|
||||||
|
"sender": "@alice:example.org",
|
||||||
|
"state_key": "!bar:example.org",
|
||||||
|
"type": "m.space.child"
|
||||||
|
}"#,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
serde_json::from_str(
|
||||||
|
r#"{
|
||||||
|
"content": {
|
||||||
|
"via": [
|
||||||
|
"example.org"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"origin_server_ts": 1629413349160,
|
||||||
|
"sender": "@alice:example.org",
|
||||||
|
"state_key": "!baz:example.org",
|
||||||
|
"type": "m.space.child"
|
||||||
|
}"#,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
],
|
||||||
|
allowed_room_ids: vec![],
|
||||||
|
}
|
||||||
|
.into();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
get_parent_children_via(&summary, false),
|
||||||
|
vec![
|
||||||
|
(owned_room_id!("!foo:example.org"), vec![owned_server_name!("example.org")]),
|
||||||
|
(owned_room_id!("!bar:example.org"), vec![owned_server_name!("example.org")]),
|
||||||
|
(owned_room_id!("!baz:example.org"), vec![owned_server_name!("example.org")])
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
get_parent_children_via(&summary, true),
|
||||||
|
vec![(owned_room_id!("!bar:example.org"), vec![owned_server_name!("example.org")])]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_pagination_tokens() {
|
||||||
|
fn token_is_err(token: &str) { PaginationToken::from_str(token).unwrap_err(); }
|
||||||
|
|
||||||
|
token_is_err("231_2_noabool");
|
||||||
|
token_is_err("");
|
||||||
|
token_is_err("111_3_");
|
||||||
|
token_is_err("foo_not_int");
|
||||||
|
token_is_err("11_4_true_");
|
||||||
|
token_is_err("___");
|
||||||
|
token_is_err("__false");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn valid_pagination_tokens() {
|
||||||
|
assert_eq!(
|
||||||
|
PaginationToken {
|
||||||
|
short_room_ids: vec![5383, 42934, 283, 423],
|
||||||
|
limit: UInt::from(20_u32),
|
||||||
|
max_depth: UInt::from(1_u32),
|
||||||
|
suggested_only: true
|
||||||
|
},
|
||||||
|
PaginationToken::from_str("5383,42934,283,423_20_1_true").unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
PaginationToken {
|
||||||
|
short_room_ids: vec![740],
|
||||||
|
limit: UInt::from(97_u32),
|
||||||
|
max_depth: UInt::from(10539_u32),
|
||||||
|
suggested_only: false
|
||||||
|
},
|
||||||
|
PaginationToken::from_str("740_97_10539_false").unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pagination_token_to_string() {
|
||||||
|
assert_eq!(
|
||||||
|
PaginationToken {
|
||||||
|
short_room_ids: vec![740],
|
||||||
|
limit: UInt::from(97_u32),
|
||||||
|
max_depth: UInt::from(10539_u32),
|
||||||
|
suggested_only: false
|
||||||
|
}
|
||||||
|
.to_string(),
|
||||||
|
"740_97_10539_false"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
PaginationToken {
|
||||||
|
short_room_ids: vec![9, 34],
|
||||||
|
limit: UInt::from(3_u32),
|
||||||
|
max_depth: UInt::from(1_u32),
|
||||||
|
suggested_only: true
|
||||||
|
}
|
||||||
|
.to_string(),
|
||||||
|
"9,34_3_1_true"
|
||||||
|
);
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ use ruma::{
|
||||||
canonical_alias::RoomCanonicalAliasEventContent,
|
canonical_alias::RoomCanonicalAliasEventContent,
|
||||||
guest_access::{GuestAccess, RoomGuestAccessEventContent},
|
guest_access::{GuestAccess, RoomGuestAccessEventContent},
|
||||||
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
|
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
|
||||||
|
join_rules::{AllowRule, JoinRule, RoomJoinRulesEventContent, RoomMembership},
|
||||||
member::{MembershipState, RoomMemberEventContent},
|
member::{MembershipState, RoomMemberEventContent},
|
||||||
name::RoomNameEventContent,
|
name::RoomNameEventContent,
|
||||||
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
|
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
|
||||||
|
@ -23,7 +24,8 @@ use ruma::{
|
||||||
},
|
},
|
||||||
StateEventType,
|
StateEventType,
|
||||||
},
|
},
|
||||||
EventId, OwnedRoomAliasId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId,
|
space::SpaceRoomJoinRule,
|
||||||
|
EventId, OwnedRoomAliasId, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId,
|
||||||
};
|
};
|
||||||
use serde_json::value::to_raw_value;
|
use serde_json::value::to_raw_value;
|
||||||
|
|
||||||
|
@ -415,4 +417,38 @@ impl Service {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the join rule for a given room
|
||||||
|
pub fn get_join_rule(&self, current_room: &RoomId) -> Result<(SpaceRoomJoinRule, Vec<OwnedRoomId>), Error> {
|
||||||
|
Ok(self
|
||||||
|
.room_state_get(current_room, &StateEventType::RoomJoinRules, "")?
|
||||||
|
.map(|s| {
|
||||||
|
serde_json::from_str(s.content.get())
|
||||||
|
.map(|c: RoomJoinRulesEventContent| {
|
||||||
|
(c.join_rule.clone().into(), self.allowed_room_ids(c.join_rule))
|
||||||
|
})
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("Invalid room join rule event in database: {e}");
|
||||||
|
Error::BadDatabase("Invalid room join rule event in database.")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.transpose()?
|
||||||
|
.unwrap_or((SpaceRoomJoinRule::Invite, vec![])))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an empty vec if not a restricted room
|
||||||
|
pub fn allowed_room_ids(&self, join_rule: JoinRule) -> Vec<OwnedRoomId> {
|
||||||
|
let mut room_ids = vec![];
|
||||||
|
if let JoinRule::Restricted(r) | JoinRule::KnockRestricted(r) = join_rule {
|
||||||
|
for rule in r.allow {
|
||||||
|
if let AllowRule::RoomMembership(RoomMembership {
|
||||||
|
room_id: membership,
|
||||||
|
}) = rule
|
||||||
|
{
|
||||||
|
room_ids.push(membership.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
room_ids
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -293,6 +293,7 @@ impl Service {
|
||||||
self.db.room_members(room_id)
|
self.db.room_members(room_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the number of users which are currently in a room
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
pub fn room_joined_count(&self, room_id: &RoomId) -> Result<Option<u64>> { self.db.room_joined_count(room_id) }
|
pub fn room_joined_count(&self, room_id: &RoomId) -> Result<Option<u64>> { self.db.room_joined_count(room_id) }
|
||||||
|
|
||||||
|
@ -310,6 +311,7 @@ impl Service {
|
||||||
self.db.active_local_users_in_room(room_id)
|
self.db.active_local_users_in_room(room_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the number of users which are currently invited to a room
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
pub fn room_invited_count(&self, room_id: &RoomId) -> Result<Option<u64>> { self.db.room_invited_count(room_id) }
|
pub fn room_invited_count(&self, room_id: &RoomId) -> Result<Option<u64>> { self.db.room_invited_count(room_id) }
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue