feat: support /make_join and /send_join for restricted rooms

from https://gitlab.com/famedly/conduit/-/merge_requests/618

Signed-off-by: strawberry <strawberry@puppygock.gay>
This commit is contained in:
Matthias Ahouansou 2024-04-03 14:16:11 -04:00 committed by June
parent ed960f41ac
commit 49eb418786
2 changed files with 75 additions and 46 deletions

View file

@ -34,7 +34,7 @@ use ruma::{
events::{ events::{
receipt::{ReceiptEvent, ReceiptEventContent, ReceiptType}, receipt::{ReceiptEvent, ReceiptEventContent, ReceiptType},
room::{ room::{
join_rules::{JoinRule, RoomJoinRulesEventContent}, join_rules::{AllowRule, JoinRule, RoomJoinRulesEventContent},
member::{MembershipState, RoomMemberEventContent}, member::{MembershipState, RoomMemberEventContent},
}, },
StateEventType, TimelineEventType, StateEventType, TimelineEventType,
@ -910,7 +910,6 @@ pub async fn create_join_event_template_route(
); );
let state_lock = mutex_state.lock().await; let state_lock = mutex_state.lock().await;
// TODO: Conduit does not implement restricted join rules yet, we always reject
let join_rules_event = let join_rules_event =
services() services()
.rooms .rooms
@ -921,23 +920,57 @@ pub async fn create_join_event_template_route(
.as_ref() .as_ref()
.map(|join_rules_event| { .map(|join_rules_event| {
serde_json::from_str(join_rules_event.content.get()).map_err(|e| { serde_json::from_str(join_rules_event.content.get()).map_err(|e| {
warn!("Invalid join rules event: {}", e); warn!("Invalid join rules event: {e}");
Error::bad_database("Invalid join rules event in db.") Error::bad_database("Invalid join rules event in db.")
}) })
}) })
.transpose()?; .transpose()?;
if let Some(join_rules_event_content) = join_rules_event_content { let join_authorized_via_users_server = if let Some(join_rules_event_content) = join_rules_event_content {
if matches!( if let JoinRule::Restricted(r) | JoinRule::KnockRestricted(r) = join_rules_event_content.join_rule {
join_rules_event_content.join_rule, if r.allow
JoinRule::Restricted { .. } | JoinRule::KnockRestricted { .. } .iter()
) { .filter_map(|rule| {
return Err(Error::BadRequest( if let AllowRule::RoomMembership(membership) = rule {
ErrorKind::UnableToAuthorizeJoin, Some(membership)
"Conduit does not support restricted rooms yet.", } else {
)); None
}
})
.any(|m| {
services()
.rooms
.state_cache
.is_joined(&body.user_id, &m.room_id)
.unwrap_or(false)
}) && services()
.rooms
.state_cache
.is_left(&body.user_id, &body.room_id)
.unwrap_or(true)
{
services()
.rooms
.state_cache
.room_members(&body.room_id)
.filter_map(Result::ok)
.find(|user| {
user.server_name() == services().globals.server_name()
&& services()
.rooms
.state_accessor
.user_can_invite(&body.room_id, user, &body.user_id)
.unwrap_or(false)
})
} else {
None
}
} else {
None
} }
} } else {
None
};
let room_version_id = services().rooms.state.get_room_version(&body.room_id)?; 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) {
@ -957,7 +990,7 @@ pub async fn create_join_event_template_route(
membership: MembershipState::Join, membership: MembershipState::Join,
third_party_invite: None, third_party_invite: None,
reason: None, reason: None,
join_authorized_via_users_server: None, join_authorized_via_users_server,
}) })
.expect("member event is valid value"); .expect("member event is valid value");
@ -1017,35 +1050,6 @@ async fn create_join_event(
.event_handler .event_handler
.acl_check(sender_servername, room_id)?; .acl_check(sender_servername, room_id)?;
// TODO: Conduit does not implement restricted join rules yet, we always reject
let join_rules_event =
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 // We need to return the state prior to joining, let's keep a reference to that
// here // here
let shortstatehash = services() let shortstatehash = services()
@ -1060,7 +1064,7 @@ async fn create_join_event(
// We do not add the event_id field to the pdu here because of signature and // We do not add the event_id field to the pdu here because of signature and
// hashes checks // hashes checks
let room_version_id = services().rooms.state.get_room_version(room_id)?; 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 { let Ok((event_id, mut value)) = gen_event_id_canonical_json(pdu, &room_version_id) else {
// Event could not be converted to canonical json // Event could not be converted to canonical json
return Err(Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
@ -1068,6 +1072,14 @@ async fn create_join_event(
)); ));
}; };
ruma::signatures::hash_and_sign_event(
services().globals.server_name().as_str(),
services().globals.keypair(),
&mut value,
&room_version_id,
)
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Failed to sign event."))?;
let origin: OwnedServerName = serde_json::from_value( let origin: OwnedServerName = serde_json::from_value(
serde_json::to_value( serde_json::to_value(
value value
@ -1097,7 +1109,7 @@ async fn create_join_event(
let pdu_id: Vec<u8> = services() let pdu_id: Vec<u8> = services()
.rooms .rooms
.event_handler .event_handler
.handle_incoming_pdu(&origin, &event_id, room_id, value, true, &pub_key_map) .handle_incoming_pdu(&origin, &event_id, room_id, value.clone(), true, &pub_key_map)
.await? .await?
.ok_or(Error::BadRequest( .ok_or(Error::BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
@ -1128,7 +1140,11 @@ async fn create_join_event(
.filter_map(|(_, id)| services().rooms.timeline.get_pdu_json(id).ok().flatten()) .filter_map(|(_, id)| services().rooms.timeline.get_pdu_json(id).ok().flatten())
.map(PduEvent::convert_to_outgoing_federation_event) .map(PduEvent::convert_to_outgoing_federation_event)
.collect(), .collect(),
event: None, // TODO: handle restricted joins // Event field is required if the room version supports restricted join rules.
event: Some(
to_raw_value(&CanonicalJsonValue::Object(value.clone()))
.expect("To raw json should not fail since only change was adding signature"),
),
}) })
} }

View file

@ -13,6 +13,7 @@ use ruma::{
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent}, history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
member::{MembershipState, RoomMemberEventContent}, member::{MembershipState, RoomMemberEventContent},
name::RoomNameEventContent, name::RoomNameEventContent,
power_levels::RoomPowerLevelsEventContent,
}, },
StateEventType, StateEventType,
}, },
@ -139,6 +140,18 @@ impl Service {
Ok(visibility) Ok(visibility)
} }
/// Whether a user's power level is sufficient to invite other users
pub fn user_can_invite(&self, user_id: &UserId, room_id: &RoomId) -> Result<bool> {
self.room_state_get(room_id, &StateEventType::RoomPowerLevels, "")?
.map(|pdu_event| {
serde_json::from_str(pdu_event.content.get()).map(|content: RoomPowerLevelsEventContent| {
content.users.get(user_id).unwrap_or(&content.users_default) >= &content.invite
})
})
.unwrap_or(Ok(false))
.map_err(|_| Error::bad_database("Invalid history visibility event in database."))
}
/// Whether a user is allowed to see an event, based on /// Whether a user is allowed to see an event, based on
/// the room's history_visibility at that event's state. /// the room's history_visibility at that event's state.
#[tracing::instrument(skip(self, user_id, room_id, event_id))] #[tracing::instrument(skip(self, user_id, room_id, event_id))]