mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-05-26 20:49:55 +00:00
fix: Mark events as rejected in more places, correct soft-fail extremity behaviour
This commit is contained in:
@@ -133,6 +133,8 @@ where
|
|||||||
.filter(|id| !auth_events.contains_key(*id))
|
.filter(|id| !auth_events.contains_key(*id))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
if !still_missing.is_empty() {
|
if !still_missing.is_empty() {
|
||||||
|
// Don't reject: this could be a temporary condition
|
||||||
|
// TODO: use get_missing_events?
|
||||||
return Err!(Request(InvalidParam(
|
return Err!(Request(InvalidParam(
|
||||||
"Could not fetch all auth events for outlier event {event_id}, still missing: \
|
"Could not fetch all auth events for outlier event {event_id}, still missing: \
|
||||||
{still_missing:?}"
|
{still_missing:?}"
|
||||||
@@ -163,6 +165,7 @@ where
|
|||||||
v.insert(auth_event);
|
v.insert(auth_event);
|
||||||
},
|
},
|
||||||
| hash_map::Entry::Occupied(_) => {
|
| hash_map::Entry::Occupied(_) => {
|
||||||
|
self.services.pdu_metadata.mark_event_rejected(event_id);
|
||||||
return Err!(Request(InvalidParam(
|
return Err!(Request(InvalidParam(
|
||||||
"Auth event's type and state_key combination exists multiple times: {}, {}",
|
"Auth event's type and state_key combination exists multiple times: {}, {}",
|
||||||
auth_event.kind,
|
auth_event.kind,
|
||||||
@@ -177,6 +180,7 @@ where
|
|||||||
auth_events_by_key.get(&(StateEventType::RoomCreate, String::new().into())),
|
auth_events_by_key.get(&(StateEventType::RoomCreate, String::new().into())),
|
||||||
Some(_) | None
|
Some(_) | None
|
||||||
) {
|
) {
|
||||||
|
self.services.pdu_metadata.mark_event_rejected(event_id);
|
||||||
return Err!(Request(InvalidParam("Incoming event refers to wrong create event.")));
|
return Err!(Request(InvalidParam("Incoming event refers to wrong create event.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,6 +189,7 @@ where
|
|||||||
ready(auth_events_by_key.get(&key).map(ToOwned::to_owned))
|
ready(auth_events_by_key.get(&key).map(ToOwned::to_owned))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// PDU check: 3
|
||||||
let auth_check = state_res::event_auth::auth_check(
|
let auth_check = state_res::event_auth::auth_check(
|
||||||
&room_version_rules,
|
&room_version_rules,
|
||||||
&pdu_event,
|
&pdu_event,
|
||||||
@@ -196,7 +201,10 @@ where
|
|||||||
.map_err(|e| err!(Request(Forbidden("Auth check failed: {e:?}"))))?;
|
.map_err(|e| err!(Request(Forbidden("Auth check failed: {e:?}"))))?;
|
||||||
|
|
||||||
if !auth_check {
|
if !auth_check {
|
||||||
return Err!(Request(Forbidden("Auth check failed")));
|
self.services.pdu_metadata.mark_event_rejected(event_id);
|
||||||
|
return Err!(Request(Forbidden(
|
||||||
|
"Event authorisation fails based on event's claimed auth events"
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
trace!("Validation successful.");
|
trace!("Validation successful.");
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use conduwuit::{
|
use conduwuit::{
|
||||||
Result, debug, err, implement,
|
Result, debug, err, error, implement,
|
||||||
matrix::{Event, StateMap},
|
matrix::{Event, StateMap},
|
||||||
trace,
|
trace,
|
||||||
utils::stream::{BroadbandExt, IterStream, ReadyExt, TryBroadbandExt, TryWidebandExt},
|
utils::stream::{BroadbandExt, IterStream, ReadyExt, TryBroadbandExt, TryWidebandExt},
|
||||||
@@ -121,6 +121,7 @@ where
|
|||||||
.state_resolution(room_version_rules, fork_states.iter(), &auth_chain_sets)
|
.state_resolution(room_version_rules, fork_states.iter(), &auth_chain_sets)
|
||||||
.boxed()
|
.boxed()
|
||||||
.await
|
.await
|
||||||
|
.inspect_err(|e| error!("State resolution failed: {e:?}"))
|
||||||
else {
|
else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
use std::{borrow::Borrow, collections::BTreeMap, iter::once, sync::Arc, time::Instant};
|
use std::{borrow::Borrow, collections::BTreeMap, sync::Arc, time::Instant};
|
||||||
|
|
||||||
use conduwuit::{
|
use conduwuit::{
|
||||||
Err, Result, debug, debug_info, err, implement, info, is_equal_to,
|
Err, Result, debug, debug_info, err, implement, is_equal_to,
|
||||||
matrix::{Event, EventTypeExt, PduEvent, StateKey, state_res},
|
matrix::{Event, EventTypeExt, PduEvent, StateKey, state_res},
|
||||||
trace,
|
trace,
|
||||||
utils::stream::{BroadbandExt, ReadyExt},
|
utils::{
|
||||||
|
IterStream,
|
||||||
|
stream::{BroadbandExt, ReadyExt},
|
||||||
|
},
|
||||||
warn,
|
warn,
|
||||||
};
|
};
|
||||||
use futures::{FutureExt, StreamExt, future::ready};
|
use futures::{FutureExt, StreamExt, future::ready};
|
||||||
use ruma::{CanonicalJsonValue, RoomId, ServerName, events::StateEventType};
|
use ruma::{CanonicalJsonValue, RoomId, ServerName, events::StateEventType};
|
||||||
|
use tokio::join;
|
||||||
|
|
||||||
use super::get_room_version_rules;
|
use super::get_room_version_rules;
|
||||||
use crate::rooms::{
|
use crate::rooms::{
|
||||||
@@ -38,18 +42,24 @@ where
|
|||||||
return Ok(Some(pduid));
|
return Ok(Some(pduid));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self
|
let (rejected, soft_failed) = join!(
|
||||||
.services
|
self.services
|
||||||
.pdu_metadata
|
.pdu_metadata
|
||||||
.is_event_accepted(incoming_pdu.event_id())
|
.is_event_rejected(incoming_pdu.event_id()),
|
||||||
.await
|
self.services
|
||||||
{
|
.pdu_metadata
|
||||||
|
.is_event_soft_failed(incoming_pdu.event_id())
|
||||||
|
);
|
||||||
|
if rejected || soft_failed {
|
||||||
|
// TODO: debug_warn instead of warn
|
||||||
|
warn!(%rejected, %soft_failed, "Event is not accepted");
|
||||||
return Err!(Request(InvalidParam("Event has been rejected or soft-failed")));
|
return Err!(Request(InvalidParam("Event has been rejected or soft-failed")));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If any of the auth events are rejected, this event is also rejected.
|
// If any of the auth events are rejected, this event is also rejected.
|
||||||
for aid in incoming_pdu.auth_events() {
|
for aid in incoming_pdu.auth_events() {
|
||||||
if self.services.pdu_metadata.is_event_rejected(aid).await {
|
if self.services.pdu_metadata.is_event_rejected(aid).await {
|
||||||
|
// TODO: debug_warn instead of warn
|
||||||
warn!(
|
warn!(
|
||||||
"Rejecting incoming event {} which depends on rejected auth event {aid}",
|
"Rejecting incoming event {} which depends on rejected auth event {aid}",
|
||||||
incoming_pdu.event_id()
|
incoming_pdu.event_id()
|
||||||
@@ -57,7 +67,7 @@ where
|
|||||||
self.services
|
self.services
|
||||||
.pdu_metadata
|
.pdu_metadata
|
||||||
.mark_event_rejected(incoming_pdu.event_id());
|
.mark_event_rejected(incoming_pdu.event_id());
|
||||||
return Err!(Request(InvalidParam("Event has rejected auth events")));
|
return Err!(Request(InvalidParam("Event has rejected auth event: {aid}")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,6 +119,7 @@ where
|
|||||||
event_id = %incoming_pdu.event_id,
|
event_id = %incoming_pdu.event_id,
|
||||||
"Running initial auth check"
|
"Running initial auth check"
|
||||||
);
|
);
|
||||||
|
// PDU check: 5
|
||||||
let auth_check = state_res::event_auth::auth_check(
|
let auth_check = state_res::event_auth::auth_check(
|
||||||
&room_version_rules,
|
&room_version_rules,
|
||||||
&incoming_pdu,
|
&incoming_pdu,
|
||||||
@@ -123,7 +134,9 @@ where
|
|||||||
self.services
|
self.services
|
||||||
.pdu_metadata
|
.pdu_metadata
|
||||||
.mark_event_rejected(incoming_pdu.event_id());
|
.mark_event_rejected(incoming_pdu.event_id());
|
||||||
return Err!(Request(Forbidden("Event has failed auth check with state at the event.")));
|
return Err!(Request(Forbidden(
|
||||||
|
"Event authorisation fails based on the state before the event"
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
@@ -152,6 +165,7 @@ where
|
|||||||
event_id = %incoming_pdu.event_id,
|
event_id = %incoming_pdu.event_id,
|
||||||
"Running auth check with claimed state auth"
|
"Running auth check with claimed state auth"
|
||||||
);
|
);
|
||||||
|
// PDU check: 6
|
||||||
let auth_check = state_res::event_auth::auth_check(
|
let auth_check = state_res::event_auth::auth_check(
|
||||||
&room_version_rules,
|
&room_version_rules,
|
||||||
&incoming_pdu,
|
&incoming_pdu,
|
||||||
@@ -161,6 +175,12 @@ where
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| err!(Request(Forbidden("Auth check failed: {e:?}"))))?;
|
.map_err(|e| err!(Request(Forbidden("Auth check failed: {e:?}"))))?;
|
||||||
|
if !auth_check {
|
||||||
|
warn!(
|
||||||
|
event_id = %incoming_pdu.event_id,
|
||||||
|
"Event authentication fails based on the current state of the room"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Soft fail check before doing state res
|
// Soft fail check before doing state res
|
||||||
debug!(
|
debug!(
|
||||||
@@ -170,16 +190,22 @@ where
|
|||||||
let mut soft_fail = match (auth_check, incoming_pdu.redacts_id(&room_version_rules)) {
|
let mut soft_fail = match (auth_check, incoming_pdu.redacts_id(&room_version_rules)) {
|
||||||
| (false, _) => true,
|
| (false, _) => true,
|
||||||
| (true, None) => false,
|
| (true, None) => false,
|
||||||
| (true, Some(redact_id)) =>
|
| (true, Some(redact_id)) => {
|
||||||
!self
|
if !self
|
||||||
.services
|
.services
|
||||||
.state_accessor
|
.state_accessor
|
||||||
.user_can_redact(&redact_id, incoming_pdu.sender(), room_id, true)
|
.user_can_redact(&redact_id, incoming_pdu.sender(), room_id, true)
|
||||||
.await?,
|
.await?
|
||||||
|
{
|
||||||
|
warn!(redacts = %redact_id, "User is not allowed to redact event");
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// 13. Use state resolution to find new room state
|
// 13. Use state resolution to find new room state
|
||||||
|
|
||||||
// We start looking at current room state now, so lets lock the room
|
// We start looking at current room state now, so lets lock the room
|
||||||
trace!(
|
trace!(
|
||||||
room_id = %room_id,
|
room_id = %room_id,
|
||||||
@@ -187,36 +213,6 @@ where
|
|||||||
);
|
);
|
||||||
let state_lock = self.services.state.mutex.lock(room_id).await;
|
let state_lock = self.services.state.mutex.lock(room_id).await;
|
||||||
|
|
||||||
// Now we calculate the set of extremities this room has after the incoming
|
|
||||||
// event has been applied. We start with the previous extremities (aka leaves)
|
|
||||||
trace!("Calculating extremities");
|
|
||||||
let mut extremities: Vec<_> = self
|
|
||||||
.services
|
|
||||||
.state
|
|
||||||
.get_forward_extremities(room_id)
|
|
||||||
.ready_filter(|event_id| {
|
|
||||||
// Remove any that are referenced by this incoming event's prev_events
|
|
||||||
!incoming_pdu.prev_events().any(is_equal_to!(event_id))
|
|
||||||
})
|
|
||||||
.broad_filter_map(|event_id| async move {
|
|
||||||
// Only keep those extremities were not referenced yet
|
|
||||||
self.services
|
|
||||||
.pdu_metadata
|
|
||||||
.is_event_referenced(room_id, &event_id)
|
|
||||||
.await
|
|
||||||
.eq(&false)
|
|
||||||
.then_some(event_id)
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
.await;
|
|
||||||
extremities.push(incoming_pdu.event_id().to_owned());
|
|
||||||
|
|
||||||
debug!(
|
|
||||||
"Retained {} extremities checked against {} prev_events",
|
|
||||||
extremities.len(),
|
|
||||||
incoming_pdu.prev_events().count()
|
|
||||||
);
|
|
||||||
|
|
||||||
let state_ids_compressed: Arc<CompressedState> = self
|
let state_ids_compressed: Arc<CompressedState> = self
|
||||||
.services
|
.services
|
||||||
.state_compressor
|
.state_compressor
|
||||||
@@ -311,60 +307,57 @@ where
|
|||||||
.is_event_soft_failed(&redact_id)
|
.is_event_soft_failed(&redact_id)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
// TODO: This should avoid pushing the event to the timeline instead of using
|
||||||
|
// soft-fails as a hack
|
||||||
warn!(
|
warn!(
|
||||||
redact_id = %redact_id,
|
redact_id = %redact_id,
|
||||||
"Redaction is for a soft-failed event, soft failing the redaction"
|
"Redaction is for a soft-failed event"
|
||||||
);
|
);
|
||||||
soft_fail = true;
|
soft_fail = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 14. Check if the event passes auth based on the "current state" of the room,
|
|
||||||
// if not soft fail it
|
|
||||||
if soft_fail {
|
|
||||||
info!(
|
|
||||||
event_id = %incoming_pdu.event_id,
|
|
||||||
"Soft failing event"
|
|
||||||
);
|
|
||||||
// assert!(extremities.is_empty(), "soft_fail extremities empty");
|
|
||||||
let extremities = extremities.iter().map(Borrow::borrow);
|
|
||||||
debug_assert!(extremities.clone().count() > 0, "extremities not empty");
|
|
||||||
|
|
||||||
self.services
|
|
||||||
.timeline
|
|
||||||
.append_incoming_pdu(
|
|
||||||
&incoming_pdu,
|
|
||||||
val,
|
|
||||||
extremities,
|
|
||||||
state_ids_compressed,
|
|
||||||
soft_fail,
|
|
||||||
&state_lock,
|
|
||||||
room_id,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Soft fail, we keep the event as an outlier but don't add it to the timeline
|
|
||||||
self.services
|
|
||||||
.pdu_metadata
|
|
||||||
.mark_event_soft_failed(incoming_pdu.event_id());
|
|
||||||
|
|
||||||
warn!(
|
|
||||||
event_id = %incoming_pdu.event_id,
|
|
||||||
"Event was soft failed"
|
|
||||||
);
|
|
||||||
return Err!(Request(InvalidParam("Event has been soft failed")));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now that the event has passed all auth it is added into the timeline.
|
|
||||||
// We use the `state_at_event` instead of `state_after` so we accurately
|
|
||||||
// represent the state for this event.
|
|
||||||
trace!("Appending pdu to timeline");
|
trace!("Appending pdu to timeline");
|
||||||
let extremities = extremities
|
let mut extremities: Vec<_> = self
|
||||||
.iter()
|
.services
|
||||||
.map(Borrow::borrow)
|
.state
|
||||||
.chain(once(incoming_pdu.event_id()));
|
.get_forward_extremities(room_id)
|
||||||
debug_assert!(extremities.clone().count() > 0, "extremities not empty");
|
.collect()
|
||||||
|
.await;
|
||||||
|
if !soft_fail {
|
||||||
|
// Per https://spec.matrix.org/unstable/server-server-api/#soft-failure, soft-failed events
|
||||||
|
// are not added as forward extremities.
|
||||||
|
|
||||||
|
// Now we calculate the set of extremities this room has after the incoming
|
||||||
|
// event has been applied. We start with the previous extremities (aka leaves)
|
||||||
|
trace!("Calculating extremities");
|
||||||
|
extremities = extremities
|
||||||
|
.into_iter()
|
||||||
|
.stream()
|
||||||
|
.ready_filter(|event_id| {
|
||||||
|
// Remove any that are referenced by this incoming event's prev_events
|
||||||
|
!incoming_pdu.prev_events().any(is_equal_to!(event_id))
|
||||||
|
})
|
||||||
|
.broad_filter_map(|event_id| async move {
|
||||||
|
// Only keep those extremities were not referenced yet
|
||||||
|
self.services
|
||||||
|
.pdu_metadata
|
||||||
|
.is_event_referenced(room_id, &event_id)
|
||||||
|
.await
|
||||||
|
.eq(&false)
|
||||||
|
.then_some(event_id)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.await;
|
||||||
|
extremities.push(incoming_pdu.event_id().to_owned());
|
||||||
|
debug!(
|
||||||
|
"Retained {} extremities checked against {} prev_events",
|
||||||
|
extremities.len(),
|
||||||
|
incoming_pdu.prev_events().count()
|
||||||
|
);
|
||||||
|
assert!(!extremities.is_empty(), "extremities must not empty");
|
||||||
|
}
|
||||||
|
|
||||||
let pdu_id = self
|
let pdu_id = self
|
||||||
.services
|
.services
|
||||||
@@ -372,13 +365,19 @@ where
|
|||||||
.append_incoming_pdu(
|
.append_incoming_pdu(
|
||||||
&incoming_pdu,
|
&incoming_pdu,
|
||||||
val,
|
val,
|
||||||
extremities,
|
extremities.iter().map(Borrow::borrow),
|
||||||
state_ids_compressed,
|
state_ids_compressed,
|
||||||
soft_fail,
|
soft_fail,
|
||||||
&state_lock,
|
&state_lock,
|
||||||
room_id,
|
room_id,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
if soft_fail {
|
||||||
|
self.services
|
||||||
|
.pdu_metadata
|
||||||
|
.mark_event_soft_failed(incoming_pdu.event_id());
|
||||||
|
return Err!(Request(InvalidParam("Event has been soft failed")));
|
||||||
|
}
|
||||||
|
|
||||||
// Event has passed all auth/stateres checks
|
// Event has passed all auth/stateres checks
|
||||||
drop(state_lock);
|
drop(state_lock);
|
||||||
|
|||||||
@@ -53,15 +53,7 @@ where
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if soft_fail {
|
if soft_fail {
|
||||||
self.services
|
// Nothing else to do with a soft-failed event.
|
||||||
.pdu_metadata
|
|
||||||
.mark_as_referenced(room_id, pdu.prev_events.iter().map(AsRef::as_ref));
|
|
||||||
|
|
||||||
// self.services
|
|
||||||
// .state
|
|
||||||
// .set_forward_extremities(room_id, new_room_leaves, state_lock)
|
|
||||||
// .await;
|
|
||||||
|
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user