fixup! feat(federation): support /make_join and /send_join for restricted rooms

move out checking restricted room rules into separate function
This commit is contained in:
Matthias Ahouansou 2024-07-01 17:16:39 +01:00
parent a6da294c55
commit 3a8ed99246
No known key found for this signature in database

View file

@ -49,7 +49,7 @@ use ruma::{
to_device::DeviceIdOrAllDevices, to_device::DeviceIdOrAllDevices,
uint, user_id, CanonicalJsonObject, CanonicalJsonValue, EventId, MilliSecondsSinceUnixEpoch, uint, user_id, CanonicalJsonObject, CanonicalJsonValue, EventId, MilliSecondsSinceUnixEpoch,
OwnedEventId, OwnedRoomId, OwnedServerName, OwnedServerSigningKeyId, OwnedUserId, RoomId, OwnedEventId, OwnedRoomId, OwnedServerName, OwnedServerSigningKeyId, OwnedUserId, RoomId,
RoomVersionId, ServerName, RoomVersionId, ServerName, UserId,
}; };
use serde_json::value::{to_raw_value, RawValue as RawJsonValue}; use serde_json::value::{to_raw_value, RawValue as RawJsonValue};
use std::{ use std::{
@ -1517,100 +1517,46 @@ pub async fn create_join_event_template_route(
); );
let state_lock = mutex_state.lock().await; let state_lock = mutex_state.lock().await;
let join_rules_event = services().rooms.state_accessor.room_state_get( let room_version_id = services().rooms.state.get_room_version(&body.room_id)?;
&body.room_id,
&StateEventType::RoomJoinRules,
"",
)?;
let join_rules_event_content: Option<RoomJoinRulesEventContent> = join_rules_event let join_authorized_via_users_server = if (services()
.as_ref() .rooms
.map(|join_rules_event| { .state_cache
serde_json::from_str(join_rules_event.content.get()).map_err(|e| { .is_left(&body.user_id, &body.room_id)
warn!("Invalid join rules event: {}", e); .unwrap_or(true)
Error::bad_database("Invalid join rules event in db.") || services()
})
})
.transpose()?;
let join_authorized_via_users_server = || {
let join_rules_event_content = join_rules_event_content?;
if !services()
.rooms .rooms
.state_cache .state_cache
.is_left(&body.user_id, &body.room_id) .is_knocked(&body.user_id, &body.room_id)
.unwrap_or(true) .unwrap_or(false))
&& !services() && user_can_perform_restricted_join(&body.user_id, &body.room_id, &room_version_id)?
.rooms {
.state_cache let auth_user = services()
.is_knocked(&body.user_id, &body.room_id) .rooms
.unwrap_or(false) .state_cache
{ .room_members(&body.room_id)
// If the user has any state other than leave or knock, either: .filter_map(Result::ok)
// - the auth_check will deny them (ban) .filter(|user| user.server_name() == services().globals.server_name())
// - they are able to join via other methods (invite) .find(|user| {
// - they are already in the room (join)
return None;
};
let (JoinRule::Restricted(r) | JoinRule::KnockRestricted(r)) =
join_rules_event_content.join_rule
else {
// Room is not restricted
return None;
};
if r.allow
.iter()
.filter_map(|rule| {
if let AllowRule::RoomMembership(membership) = rule {
Some(membership)
} else {
None
}
})
.any(|m| {
services() services()
.rooms .rooms
.state_cache .state_accessor
.is_joined(&body.user_id, &m.room_id) .user_can_invite(&body.room_id, user, &body.user_id, &state_lock)
.unwrap_or(false) .unwrap_or(false)
}) });
{
let auth_user = services()
.rooms
.state_cache
.room_members(&body.room_id)
.filter_map(Result::ok)
.filter(|user| user.server_name() == services().globals.server_name())
.find(|user| {
services()
.rooms
.state_accessor
.user_can_invite(&body.room_id, user, &body.user_id, &state_lock)
.unwrap_or(false)
});
if auth_user.is_some() { if auth_user.is_some() {
Ok(auth_user).transpose() auth_user
} else {
Some(Err(Error::BadRequest(
ErrorKind::UnableToGrantJoin,
"No user on this server is able to assist in joining.",
)))
}
} else { } else {
Some(Err(Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::UnableToAuthorizeJoin, ErrorKind::UnableToGrantJoin,
"User is not known to be in any required room.", "No user on this server is able to assist in joining.",
))) ));
} }
} else {
None
}; };
let join_authorized_via_users_server = join_authorized_via_users_server().transpose()?;
let room_version_id = services().rooms.state.get_room_version(&body.room_id)?;
if !body.ver.contains(&room_version_id) { if !body.ver.contains(&room_version_id) {
return Err(Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::IncompatibleRoomVersion { ErrorKind::IncompatibleRoomVersion {
@ -1763,16 +1709,7 @@ async fn create_join_event(
.join_authorized_via_users_server .join_authorized_via_users_server
.map(|user| user.server_name() == services().globals.server_name()) .map(|user| user.server_name() == services().globals.server_name())
.unwrap_or_default() .unwrap_or_default()
&& !matches!( && user_can_perform_restricted_join(&sender, room_id, &room_version_id).unwrap_or_default()
room_version_id,
RoomVersionId::V1
| RoomVersionId::V2
| RoomVersionId::V3
| RoomVersionId::V4
| RoomVersionId::V5
| RoomVersionId::V6
| RoomVersionId::V7
)
{ {
ruma::signatures::hash_and_sign_event( ruma::signatures::hash_and_sign_event(
services().globals.server_name().as_str(), services().globals.server_name().as_str(),
@ -1901,6 +1838,79 @@ pub async fn create_join_event_v2_route(
Ok(create_join_event::v2::Response { room_state }) Ok(create_join_event::v2::Response { room_state })
} }
/// Checks whether the given user can join the given room via a restricted join.
/// This doesn't check the current user's membership. This should be done externally,
/// either by using the state cache or attempting to authorize the event.
fn user_can_perform_restricted_join(
user_id: &UserId,
room_id: &RoomId,
room_version_id: &RoomVersionId,
) -> Result<bool> {
let join_rules_event = services().rooms.state_accessor.room_state_get(
room_id,
&StateEventType::RoomJoinRules,
"",
)?;
let Some(join_rules_event_content) = join_rules_event
.as_ref()
.map(|join_rules_event| {
serde_json::from_str::<RoomJoinRulesEventContent>(join_rules_event.content.get())
.map_err(|e| {
warn!("Invalid join rules event: {}", e);
Error::bad_database("Invalid join rules event in db.")
})
})
.transpose()?
else {
return Ok(false);
};
if matches!(
room_version_id,
RoomVersionId::V1
| RoomVersionId::V2
| RoomVersionId::V3
| RoomVersionId::V4
| RoomVersionId::V5
| RoomVersionId::V6
| RoomVersionId::V7
) {
return Ok(false);
}
let (JoinRule::Restricted(r) | JoinRule::KnockRestricted(r)) =
join_rules_event_content.join_rule
else {
return Ok(false);
};
if r.allow
.iter()
.filter_map(|rule| {
if let AllowRule::RoomMembership(membership) = rule {
Some(membership)
} else {
None
}
})
.any(|m| {
services()
.rooms
.state_cache
.is_joined(user_id, &m.room_id)
.unwrap_or(false)
})
{
Ok(true)
} else {
Err(Error::BadRequest(
ErrorKind::UnableToAuthorizeJoin,
"User is not known to be in any required room.",
))
}
}
/// # `PUT /_matrix/federation/v2/invite/{roomId}/{eventId}` /// # `PUT /_matrix/federation/v2/invite/{roomId}/{eventId}`
/// ///
/// Invites a remote user to a room. /// Invites a remote user to a room.