fix: Return accurate errors in make_join for restricted rooms

This commit is contained in:
nexy7574
2026-02-15 16:11:18 +00:00
committed by Jade Ellis
parent 3da7fa24db
commit c22b17fb29
+54 -22
View File
@@ -16,6 +16,7 @@ use ruma::{
}, },
}; };
use serde_json::value::to_raw_value; use serde_json::value::to_raw_value;
use tokio::join;
use crate::Ruma; use crate::Ruma;
@@ -85,16 +86,24 @@ pub(crate) async fn create_join_event_template_route(
} }
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await; let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
let is_invited = services let (is_invited, is_joined) = join!(
.rooms services
.state_cache .rooms
.is_invited(&body.user_id, &body.room_id) .state_cache
.await; .is_invited(&body.user_id, &body.room_id),
services
.rooms
.state_cache
.is_joined(&body.user_id, &body.room_id)
);
let join_authorized_via_users_server: Option<OwnedUserId> = { let join_authorized_via_users_server: Option<OwnedUserId> = {
use RoomVersionId::*; use RoomVersionId::*;
if matches!(room_version_id, V1 | V2 | V3 | V4 | V5 | V6 | V7) || is_invited { if is_joined || is_invited {
// room version does not support restricted join rules, or the user is currently // User is already joined or invited and consequently does not need an
// already invited // authorising user
None
} else if matches!(room_version_id, V1 | V2 | V3 | V4 | V5 | V6 | V7) {
// room version does not support restricted join rules
None None
} else if user_can_perform_restricted_join( } else if user_can_perform_restricted_join(
&services, &services,
@@ -159,9 +168,7 @@ pub(crate) async fn create_join_event_template_route(
) )
.await?; .await?;
drop(state_lock); drop(state_lock);
pdu_json.remove("event_id");
// room v3 and above removed the "event_id" field from remote PDU format
maybe_strip_event_id(&mut pdu_json, &room_version_id)?;
Ok(prepare_join_event::v1::Response { Ok(prepare_join_event::v1::Response {
room_version: Some(room_version_id), room_version: Some(room_version_id),
@@ -180,12 +187,9 @@ pub(crate) async fn user_can_perform_restricted_join(
// restricted rooms are not supported on <=v7 // restricted rooms are not supported on <=v7
if matches!(room_version_id, V1 | V2 | V3 | V4 | V5 | V6 | V7) { if matches!(room_version_id, V1 | V2 | V3 | V4 | V5 | V6 | V7) {
return Ok(false); // This should be impossible as it was checked earlier on, but retain this check
} // for safety.
unreachable!("user_can_perform_restricted_join got incompatible room version");
if services.rooms.state_cache.is_joined(user_id, room_id).await {
// joining user is already joined, there is nothing we need to do
return Ok(false);
} }
let Ok(join_rules_event_content) = services let Ok(join_rules_event_content) = services
@@ -205,17 +209,31 @@ pub(crate) async fn user_can_perform_restricted_join(
let (JoinRule::Restricted(r) | JoinRule::KnockRestricted(r)) = let (JoinRule::Restricted(r) | JoinRule::KnockRestricted(r)) =
join_rules_event_content.join_rule join_rules_event_content.join_rule
else { else {
// This is not a restricted room
return Ok(false); return Ok(false);
}; };
if r.allow.is_empty() { if r.allow.is_empty() {
debug_info!("{room_id} is restricted but the allow key is empty"); // This will never be authorisable, return forbidden.
return Ok(false); return Err!(Request(Forbidden("You are not invited to this room.")));
} }
let mut could_satisfy = true;
for allow_rule in &r.allow { for allow_rule in &r.allow {
match allow_rule { match allow_rule {
| AllowRule::RoomMembership(membership) => { | AllowRule::RoomMembership(membership) => {
if !services
.rooms
.state_cache
.server_in_room(services.globals.server_name(), &membership.room_id)
.await
{
// Since we can't check this room, mark could_satisfy as false
// so that we can return M_UNABLE_TO_AUTHORIZE_JOIN later.
could_satisfy = false;
continue;
}
if services if services
.rooms .rooms
.state_cache .state_cache
@@ -239,6 +257,8 @@ pub(crate) async fn user_can_perform_restricted_join(
| Err(_) => Err!(Request(Forbidden("Antispam rejected join request."))), | Err(_) => Err!(Request(Forbidden("Antispam rejected join request."))),
}, },
| _ => { | _ => {
// We don't recognise this join rule, so we cannot satisfy the request.
could_satisfy = false;
debug_info!( debug_info!(
"Unsupported allow rule in restricted join for room {}: {:?}", "Unsupported allow rule in restricted join for room {}: {:?}",
room_id, room_id,
@@ -248,9 +268,21 @@ pub(crate) async fn user_can_perform_restricted_join(
} }
} }
Err!(Request(UnableToAuthorizeJoin( if could_satisfy {
"Joining user is not known to be in any required room." // We were able to check all the restrictions and can be certain that the
))) // prospective member is not permitted to join.
Err!(Request(Forbidden(
"You do not belong to any of the required rooms to join this one."
)))
} else {
// We were unable to check all the restrictions. This usually means we aren't in
// one of the rooms this one is restricted to, ergo can't check its state for
// the user's membership, and consequently the user *might* be able to join if
// they ask another server.
Err!(Request(UnableToAuthorizeJoin(
"Joining user is not known to be in any required room."
)))
}
} }
pub(crate) fn maybe_strip_event_id( pub(crate) fn maybe_strip_event_id(