mirror of
https://gitlab.com/famedly/conduit.git
synced 2024-12-27 04:53:50 +03:00
feat(spaces): hierarchy over federation
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
This commit is contained in:
parent
a9ff97e527
commit
56a51360e0
9 changed files with 973 additions and 389 deletions
|
@ -1,5 +1,10 @@
|
||||||
use crate::{services, Result, Ruma};
|
use std::str::FromStr;
|
||||||
use ruma::api::client::space::get_hierarchy;
|
|
||||||
|
use crate::{service::rooms::spaces::PagnationToken, services, Error, Result, Ruma};
|
||||||
|
use ruma::{
|
||||||
|
api::client::{error::ErrorKind, space::get_hierarchy},
|
||||||
|
UInt,
|
||||||
|
};
|
||||||
|
|
||||||
/// # `GET /_matrix/client/v1/rooms/{room_id}/hierarchy``
|
/// # `GET /_matrix/client/v1/rooms/{room_id}/hierarchy``
|
||||||
///
|
///
|
||||||
|
@ -9,25 +14,42 @@ pub async fn get_hierarchy_route(
|
||||||
) -> Result<get_hierarchy::v1::Response> {
|
) -> 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 skip = body
|
let limit = body
|
||||||
|
.limit
|
||||||
|
.unwrap_or(UInt::from(10_u32))
|
||||||
|
.min(UInt::from(100_u32));
|
||||||
|
let max_depth = body
|
||||||
|
.max_depth
|
||||||
|
.unwrap_or(UInt::from(3_u32))
|
||||||
|
.min(UInt::from(10_u32));
|
||||||
|
|
||||||
|
let key = body
|
||||||
.from
|
.from
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|s| s.parse::<usize>().ok())
|
.and_then(|s| PagnationToken::from_str(s).ok());
|
||||||
.unwrap_or(0);
|
|
||||||
|
|
||||||
let limit = body.limit.map_or(10, u64::from).min(100) as usize;
|
// Should prevent unexpected behaviour in (bad) clients
|
||||||
|
if let Some(token) = &key {
|
||||||
let max_depth = body.max_depth.map_or(3, u64::from).min(10) as usize + 1; // +1 to skip the space room itself
|
if token.suggested_only != body.suggested_only || token.max_depth != max_depth {
|
||||||
|
return Err(Error::BadRequest(
|
||||||
|
ErrorKind::InvalidParam,
|
||||||
|
"suggested_only and max_depth cannot change on paginated requests",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
services()
|
services()
|
||||||
.rooms
|
.rooms
|
||||||
.spaces
|
.spaces
|
||||||
.get_hierarchy(
|
.get_client_hierarchy(
|
||||||
sender_user,
|
sender_user,
|
||||||
&body.room_id,
|
&body.room_id,
|
||||||
limit,
|
usize::try_from(limit)
|
||||||
skip,
|
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Limit is too great"))?,
|
||||||
max_depth,
|
key.map_or(vec![], |token| token.short_room_ids),
|
||||||
|
usize::try_from(max_depth).map_err(|_| {
|
||||||
|
Error::BadRequest(ErrorKind::InvalidParam, "Max depth is too great")
|
||||||
|
})?,
|
||||||
body.suggested_only,
|
body.suggested_only,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -34,6 +34,7 @@ use ruma::{
|
||||||
membership::{create_invite, create_join_event, prepare_join_event},
|
membership::{create_invite, create_join_event, prepare_join_event},
|
||||||
openid::get_openid_userinfo,
|
openid::get_openid_userinfo,
|
||||||
query::{get_profile_information, get_room_information},
|
query::{get_profile_information, get_room_information},
|
||||||
|
space::get_hierarchy,
|
||||||
transactions::{
|
transactions::{
|
||||||
edu::{DeviceListUpdateContent, DirectDeviceContent, Edu, SigningKeyUpdateContent},
|
edu::{DeviceListUpdateContent, DirectDeviceContent, Edu, SigningKeyUpdateContent},
|
||||||
send_transaction_message,
|
send_transaction_message,
|
||||||
|
@ -2162,6 +2163,31 @@ pub async fn get_openid_userinfo_route(
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # `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> {
|
||||||
|
let sender_servername = body
|
||||||
|
.sender_servername
|
||||||
|
.as_ref()
|
||||||
|
.expect("server is authenticated");
|
||||||
|
|
||||||
|
if services().rooms.metadata.exists(&body.room_id)? {
|
||||||
|
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.",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// # `GET /.well-known/matrix/server`
|
/// # `GET /.well-known/matrix/server`
|
||||||
///
|
///
|
||||||
/// Returns the federation server discovery information.
|
/// Returns the federation server discovery information.
|
||||||
|
|
|
@ -451,6 +451,7 @@ fn routes(config: &Config) -> Router {
|
||||||
.ruma_route(server_server::get_keys_route)
|
.ruma_route(server_server::get_keys_route)
|
||||||
.ruma_route(server_server::claim_keys_route)
|
.ruma_route(server_server::claim_keys_route)
|
||||||
.ruma_route(server_server::get_openid_userinfo_route)
|
.ruma_route(server_server::get_openid_userinfo_route)
|
||||||
|
.ruma_route(server_server::get_hierarchy_route)
|
||||||
.ruma_route(server_server::well_known_server)
|
.ruma_route(server_server::well_known_server)
|
||||||
} else {
|
} else {
|
||||||
router
|
router
|
||||||
|
|
|
@ -105,7 +105,7 @@ impl Services {
|
||||||
},
|
},
|
||||||
threads: rooms::threads::Service { db },
|
threads: rooms::threads::Service { db },
|
||||||
spaces: rooms::spaces::Service {
|
spaces: rooms::spaces::Service {
|
||||||
roomid_spacechunk_cache: Mutex::new(LruCache::new(200)),
|
roomid_spacehierarchy_cache: Mutex::new(LruCache::new(200)),
|
||||||
},
|
},
|
||||||
user: rooms::user::Service { db },
|
user: rooms::user::Service { db },
|
||||||
},
|
},
|
||||||
|
@ -154,7 +154,13 @@ impl Services {
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
.len();
|
.len();
|
||||||
let roomid_spacechunk_cache = self.rooms.spaces.roomid_spacechunk_cache.lock().await.len();
|
let roomid_spacehierarchy_cache = self
|
||||||
|
.rooms
|
||||||
|
.spaces
|
||||||
|
.roomid_spacehierarchy_cache
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.len();
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
"\
|
"\
|
||||||
|
@ -163,7 +169,7 @@ server_visibility_cache: {server_visibility_cache}
|
||||||
user_visibility_cache: {user_visibility_cache}
|
user_visibility_cache: {user_visibility_cache}
|
||||||
stateinfo_cache: {stateinfo_cache}
|
stateinfo_cache: {stateinfo_cache}
|
||||||
lasttimelinecount_cache: {lasttimelinecount_cache}
|
lasttimelinecount_cache: {lasttimelinecount_cache}
|
||||||
roomid_spacechunk_cache: {roomid_spacechunk_cache}\
|
roomid_spacechunk_cache: {roomid_spacehierarchy_cache}\
|
||||||
"
|
"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -211,7 +217,7 @@ roomid_spacechunk_cache: {roomid_spacechunk_cache}\
|
||||||
if amount > 5 {
|
if amount > 5 {
|
||||||
self.rooms
|
self.rooms
|
||||||
.spaces
|
.spaces
|
||||||
.roomid_spacechunk_cache
|
.roomid_spacehierarchy_cache
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
.clear();
|
.clear();
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -93,7 +93,7 @@ impl Service {
|
||||||
services()
|
services()
|
||||||
.rooms
|
.rooms
|
||||||
.spaces
|
.spaces
|
||||||
.roomid_spacechunk_cache
|
.roomid_spacehierarchy_cache
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
.remove(&pdu.room_id);
|
.remove(&pdu.room_id);
|
||||||
|
|
|
@ -10,15 +10,18 @@ use ruma::{
|
||||||
events::{
|
events::{
|
||||||
room::{
|
room::{
|
||||||
avatar::RoomAvatarEventContent,
|
avatar::RoomAvatarEventContent,
|
||||||
|
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},
|
||||||
},
|
},
|
||||||
StateEventType,
|
StateEventType,
|
||||||
},
|
},
|
||||||
|
space::SpaceRoomJoinRule,
|
||||||
state_res::Event,
|
state_res::Event,
|
||||||
EventId, JsOption, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId,
|
EventId, JsOption, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId,
|
||||||
};
|
};
|
||||||
use serde_json::value::to_raw_value;
|
use serde_json::value::to_raw_value;
|
||||||
use tokio::sync::MutexGuard;
|
use tokio::sync::MutexGuard;
|
||||||
|
@ -396,4 +399,70 @@ impl Service {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks if guests are able to join a given room
|
||||||
|
pub fn guest_can_join(&self, room_id: &RoomId) -> Result<bool, Error> {
|
||||||
|
self.room_state_get(room_id, &StateEventType::RoomGuestAccess, "")?
|
||||||
|
.map_or(Ok(false), |s| {
|
||||||
|
serde_json::from_str(s.content.get())
|
||||||
|
.map(|c: RoomGuestAccessEventContent| c.guest_access == GuestAccess::CanJoin)
|
||||||
|
.map_err(|_| {
|
||||||
|
Error::bad_database("Invalid room guest access event in database.")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if guests are able to view room content without joining
|
||||||
|
pub fn world_readable(&self, room_id: &RoomId) -> Result<bool, Error> {
|
||||||
|
self.room_state_get(room_id, &StateEventType::RoomHistoryVisibility, "")?
|
||||||
|
.map_or(Ok(false), |s| {
|
||||||
|
serde_json::from_str(s.content.get())
|
||||||
|
.map(|c: RoomHistoryVisibilityEventContent| {
|
||||||
|
c.history_visibility == HistoryVisibility::WorldReadable
|
||||||
|
})
|
||||||
|
.map_err(|_| {
|
||||||
|
Error::bad_database("Invalid room history visibility event in database.")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
room_ids
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -248,11 +248,13 @@ 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>> {
|
pub fn room_joined_count(&self, room_id: &RoomId) -> Result<Option<u64>> {
|
||||||
self.db.room_joined_count(room_id)
|
self.db.room_joined_count(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>> {
|
pub fn room_invited_count(&self, room_id: &RoomId) -> Result<Option<u64>> {
|
||||||
self.db.room_invited_count(room_id)
|
self.db.room_invited_count(room_id)
|
||||||
|
|
|
@ -430,7 +430,7 @@ impl Service {
|
||||||
services()
|
services()
|
||||||
.rooms
|
.rooms
|
||||||
.spaces
|
.spaces
|
||||||
.roomid_spacechunk_cache
|
.roomid_spacehierarchy_cache
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
.remove(&pdu.room_id);
|
.remove(&pdu.room_id);
|
||||||
|
|
Loading…
Reference in a new issue