state-res: Use fetch state closure instead of auth_chain

This commit is contained in:
Devin Ragotzy 2021-08-18 19:17:45 -04:00 committed by Jonas Platte
parent 35a231e20c
commit 886c33eac3
No known key found for this signature in database
GPG Key ID: CC154DE0E30B7C67
3 changed files with 98 additions and 67 deletions

View File

@ -76,19 +76,23 @@ pub fn auth_types_for_event(
/// * check that the events signatures are valid /// * check that the events signatures are valid
/// * then there are checks for specific event types /// * then there are checks for specific event types
/// ///
/// The `auth_events` that are passed to this function should be a state snapshot. /// The `fetch_state` closure should gather state from a state snapshot.
/// We need to know if the event passes auth against some state not a recursive collection /// We need to know if the event passes auth against some state not a recursive
/// of auth_events fields. /// collection of auth_events fields.
/// ///
/// ## Returns /// ## Returns
/// This returns an `Error` only when serialization fails or some other fatal outcome. /// This returns an `Error` only when serialization fails or some other fatal outcome.
pub fn auth_check<E: Event>( pub fn auth_check<E, F>(
room_version: &RoomVersion, room_version: &RoomVersion,
incoming_event: &Arc<E>, incoming_event: &Arc<E>,
prev_event: Option<Arc<E>>, prev_event: Option<Arc<E>>,
auth_events: &StateMap<Arc<E>>,
current_third_party_invite: Option<Arc<E>>, current_third_party_invite: Option<Arc<E>>,
) -> Result<bool> { fetch_state: F,
) -> Result<bool>
where
E: Event,
F: Fn(&EventType, &str) -> Option<Arc<E>>,
{
info!("auth_check beginning for {} ({})", incoming_event.event_id(), incoming_event.kind()); info!("auth_check beginning for {} ({})", incoming_event.event_id(), incoming_event.kind());
// [synapse] check that all the events are in the same room as `incoming_event` // [synapse] check that all the events are in the same room as `incoming_event`
@ -165,7 +169,7 @@ pub fn auth_check<E: Event>(
*/ */
// 3. If event does not have m.room.create in auth_events reject // 3. If event does not have m.room.create in auth_events reject
if auth_events.get(&(EventType::RoomCreate, "".to_owned())).is_none() { if fetch_state(&EventType::RoomCreate, "").is_none() {
warn!("no m.room.create event in auth chain"); warn!("no m.room.create event in auth chain");
return Ok(false); return Ok(false);
@ -213,7 +217,7 @@ pub fn auth_check<E: Event>(
incoming_event.content(), incoming_event.content(),
prev_event, prev_event,
current_third_party_invite, current_third_party_invite,
auth_events, fetch_state,
)? { )? {
return Ok(false); return Ok(false);
} }
@ -223,7 +227,7 @@ pub fn auth_check<E: Event>(
} }
// If the sender's current membership state is not join, reject // If the sender's current membership state is not join, reject
match check_event_sender_in_room(incoming_event.sender(), auth_events) { match check_event_sender_in_room(incoming_event.sender(), &fetch_state) {
Some(true) => {} // sender in room Some(true) => {} // sender in room
Some(false) => { Some(false) => {
warn!("sender's membership is not join"); warn!("sender's membership is not join");
@ -238,7 +242,7 @@ pub fn auth_check<E: Event>(
// Allow if and only if sender's current power level is greater than // Allow if and only if sender's current power level is greater than
// or equal to the invite level // or equal to the invite level
if incoming_event.kind() == EventType::RoomThirdPartyInvite if incoming_event.kind() == EventType::RoomThirdPartyInvite
&& !can_send_invite(incoming_event, auth_events)? && !can_send_invite(incoming_event, &fetch_state)?
{ {
warn!("sender's cannot send invites in this room"); warn!("sender's cannot send invites in this room");
return Ok(false); return Ok(false);
@ -246,7 +250,7 @@ pub fn auth_check<E: Event>(
// If the event type's required power level is greater than the sender's power level, reject // If the event type's required power level is greater than the sender's power level, reject
// If the event has a state_key that starts with an @ and does not match the sender, reject. // If the event has a state_key that starts with an @ and does not match the sender, reject.
if !can_send_event(incoming_event, auth_events) { if !can_send_event(incoming_event, &fetch_state) {
warn!("user cannot send event"); warn!("user cannot send event");
return Ok(false); return Ok(false);
} }
@ -255,7 +259,7 @@ pub fn auth_check<E: Event>(
info!("starting m.room.power_levels check"); info!("starting m.room.power_levels check");
if let Some(required_pwr_lvl) = if let Some(required_pwr_lvl) =
check_power_levels(room_version, incoming_event, auth_events) check_power_levels(room_version, incoming_event, &fetch_state)
{ {
if !required_pwr_lvl { if !required_pwr_lvl {
warn!("power level was not allowed"); warn!("power level was not allowed");
@ -277,7 +281,7 @@ pub fn auth_check<E: Event>(
if room_version.extra_redaction_checks if room_version.extra_redaction_checks
&& incoming_event.kind() == EventType::RoomRedaction && incoming_event.kind() == EventType::RoomRedaction
&& !check_redaction(room_version, incoming_event, auth_events)? && !check_redaction(room_version, incoming_event, &fetch_state)?
{ {
return Ok(false); return Ok(false);
} }
@ -295,14 +299,18 @@ pub fn auth_check<E: Event>(
/// * `auth_events` - The set of auth events that relate to a membership event. /// * `auth_events` - The set of auth events that relate to a membership event.
/// this is generated by calling `auth_types_for_event` with the membership event and /// this is generated by calling `auth_types_for_event` with the membership event and
/// the current State. /// the current State.
pub fn valid_membership_change<E: Event>( pub fn valid_membership_change<E, F>(
state_key: &str, state_key: &str,
user_sender: &UserId, user_sender: &UserId,
content: serde_json::Value, content: serde_json::Value,
prev_event: Option<Arc<E>>, prev_event: Option<Arc<E>>,
current_third_party_invite: Option<Arc<E>>, current_third_party_invite: Option<Arc<E>>,
auth_events: &StateMap<Arc<E>>, fetch_state: F,
) -> Result<bool> { ) -> Result<bool>
where
E: Event,
F: Fn(&EventType, &str) -> Option<Arc<E>>,
{
let target_membership = serde_json::from_value::<MembershipState>( let target_membership = serde_json::from_value::<MembershipState>(
content.get("membership").expect("we test before that this field exists").clone(), content.get("membership").expect("we test before that this field exists").clone(),
)?; )?;
@ -314,16 +322,17 @@ pub fn valid_membership_change<E: Event>(
let target_user_id = let target_user_id =
UserId::try_from(state_key).map_err(|e| Error::InvalidPdu(format!("{}", e)))?; UserId::try_from(state_key).map_err(|e| Error::InvalidPdu(format!("{}", e)))?;
let key = (EventType::RoomMember, user_sender.to_string()); let sender = fetch_state(&EventType::RoomMember, user_sender.as_str());
let sender = auth_events.get(&key); let sender = sender.as_ref();
let sender_membership = sender.map_or(Ok::<_, Error>(MembershipState::Leave), |pdu| { let sender_membership = sender.map_or(Ok::<_, Error>(MembershipState::Leave), |pdu| {
Ok(serde_json::from_value::<MembershipState>( Ok(serde_json::from_value::<MembershipState>(
pdu.content().get("membership").expect("we assume existing events are valid").clone(), pdu.content().get("membership").expect("we assume existing events are valid").clone(),
)?) )?)
})?; })?;
let key = (EventType::RoomMember, target_user_id.to_string()); let current = fetch_state(&EventType::RoomMember, target_user_id.as_str());
let current = auth_events.get(&key); let current = current.as_ref();
let current_membership = current.map_or(Ok::<_, Error>(MembershipState::Leave), |pdu| { let current_membership = current.map_or(Ok::<_, Error>(MembershipState::Leave), |pdu| {
Ok(serde_json::from_value::<MembershipState>( Ok(serde_json::from_value::<MembershipState>(
@ -331,8 +340,9 @@ pub fn valid_membership_change<E: Event>(
)?) )?)
})?; })?;
let key = (EventType::RoomPowerLevels, "".into()); let power_levels_event = fetch_state(&EventType::RoomPowerLevels, "");
let power_levels_event = auth_events.get(&key); let power_levels_event = power_levels_event.as_ref();
let power_levels = power_levels_event.map_or_else( let power_levels = power_levels_event.map_or_else(
|| Ok::<_, Error>(PowerLevelsEventContent::default()), || Ok::<_, Error>(PowerLevelsEventContent::default()),
|power_levels| { |power_levels| {
@ -352,8 +362,8 @@ pub fn valid_membership_change<E: Event>(
Some, Some,
); );
let key = (EventType::RoomJoinRules, "".into()); let join_rules_event = fetch_state(&EventType::RoomJoinRules, "");
let join_rules_event = auth_events.get(&key); let join_rules_event = join_rules_event.as_ref();
let mut join_rules = JoinRule::Invite; let mut join_rules = JoinRule::Invite;
if let Some(jr) = join_rules_event { if let Some(jr) = join_rules_event {
join_rules = serde_json::from_value::<JoinRulesEventContent>(jr.content())?.join_rule; join_rules = serde_json::from_value::<JoinRulesEventContent>(jr.content())?.join_rule;
@ -485,11 +495,12 @@ pub fn valid_membership_change<E: Event>(
} }
/// Is the event's sender in the room that they sent the event to. /// Is the event's sender in the room that they sent the event to.
pub fn check_event_sender_in_room<E: Event>( pub fn check_event_sender_in_room<E, F>(sender: &UserId, fetch_state: F) -> Option<bool>
sender: &UserId, where
auth_events: &StateMap<Arc<E>>, E: Event,
) -> Option<bool> { F: Fn(&EventType, &str) -> Option<Arc<E>>,
let mem = auth_events.get(&(EventType::RoomMember, sender.to_string()))?; {
let mem = fetch_state(&EventType::RoomMember, sender.as_str())?;
let membership = serde_json::from_value::<MembershipState>( let membership = serde_json::from_value::<MembershipState>(
mem.content() mem.content()
@ -504,11 +515,15 @@ pub fn check_event_sender_in_room<E: Event>(
/// Is the user allowed to send a specific event based on the rooms power levels. Does the event /// Is the user allowed to send a specific event based on the rooms power levels. Does the event
/// have the correct userId as it's state_key if it's not the "" state_key. /// have the correct userId as it's state_key if it's not the "" state_key.
pub fn can_send_event<E: Event>(event: &Arc<E>, auth_events: &StateMap<Arc<E>>) -> bool { pub fn can_send_event<E, F>(event: &Arc<E>, fetch_state: F) -> bool
let ple = auth_events.get(&(EventType::RoomPowerLevels, "".into())); where
E: Event,
F: Fn(&EventType, &str) -> Option<Arc<E>>,
{
let ple = fetch_state(&EventType::RoomPowerLevels, "");
let event_type_power_level = get_send_level(&event.kind(), event.state_key(), ple); let event_type_power_level = get_send_level(&event.kind(), event.state_key(), ple.as_ref());
let user_level = get_user_power_level(event.sender(), auth_events); let user_level = get_user_power_level(event.sender(), fetch_state);
debug!("{} ev_type {} usr {}", event.event_id(), event_type_power_level, user_level); debug!("{} ev_type {} usr {}", event.event_id(), event_type_power_level, user_level);
@ -526,19 +541,23 @@ pub fn can_send_event<E: Event>(event: &Arc<E>, auth_events: &StateMap<Arc<E>>)
} }
/// Confirm that the event sender has the required power levels. /// Confirm that the event sender has the required power levels.
pub fn check_power_levels<E: Event>( pub fn check_power_levels<E, F>(
room_version: &RoomVersion, room_version: &RoomVersion,
power_event: &Arc<E>, power_event: &Arc<E>,
auth_events: &StateMap<Arc<E>>, fetch_state: F,
) -> Option<bool> { ) -> Option<bool>
where
E: Event,
F: Fn(&EventType, &str) -> Option<Arc<E>>,
{
let power_event_state_key = power_event.state_key().expect("power events have state keys"); let power_event_state_key = power_event.state_key().expect("power events have state keys");
let key = (power_event.kind(), power_event_state_key); let current_state =
let current_state = if let Some(current_state) = auth_events.get(&key) { if let Some(current_state) = fetch_state(&power_event.kind(), &power_event_state_key) {
current_state current_state
} else { } else {
// If there is no previous m.room.power_levels event in the room, allow // If there is no previous m.room.power_levels event in the room, allow
return Some(true); return Some(true);
}; };
// If users key in content is not a dictionary with keys that are valid user IDs // If users key in content is not a dictionary with keys that are valid user IDs
// with values that are integers (or a string that is an integer), reject. // with values that are integers (or a string that is an integer), reject.
@ -551,7 +570,7 @@ pub fn check_power_levels<E: Event>(
// Validation of users is done in Ruma, synapse for loops validating user_ids and integers here // Validation of users is done in Ruma, synapse for loops validating user_ids and integers here
info!("validation of power event finished"); info!("validation of power event finished");
let user_level = get_user_power_level(power_event.sender(), auth_events); let user_level = get_user_power_level(power_event.sender(), fetch_state);
let mut user_levels_to_check = btreeset![]; let mut user_levels_to_check = btreeset![];
let old_list = &current_content.users; let old_list = &current_content.users;
@ -668,13 +687,17 @@ fn get_deserialize_levels(
} }
/// Does the event redacting come from a user with enough power to redact the given event. /// Does the event redacting come from a user with enough power to redact the given event.
pub fn check_redaction<E: Event>( pub fn check_redaction<E, F>(
_room_version: &RoomVersion, _room_version: &RoomVersion,
redaction_event: &Arc<E>, redaction_event: &Arc<E>,
auth_events: &StateMap<Arc<E>>, fetch_state: F,
) -> Result<bool> { ) -> Result<bool>
let user_level = get_user_power_level(redaction_event.sender(), auth_events); where
let redact_level = get_named_level(auth_events, "redact", 50); E: Event,
F: Fn(&EventType, &str) -> Option<Arc<E>>,
{
let user_level = get_user_power_level(redaction_event.sender(), &fetch_state);
let redact_level = get_named_level(fetch_state, "redact", 50);
if user_level >= redact_level { if user_level >= redact_level {
info!("redaction allowed via power levels"); info!("redaction allowed via power levels");
@ -728,8 +751,12 @@ pub fn can_federate<E: Event>(auth_events: &StateMap<Arc<E>>) -> bool {
/// Helper function to fetch a field, `name`, from a "m.room.power_level" event's content. /// Helper function to fetch a field, `name`, from a "m.room.power_level" event's content.
/// or return `default` if no power level event is found or zero if no field matches `name`. /// or return `default` if no power level event is found or zero if no field matches `name`.
pub fn get_named_level<E: Event>(auth_events: &StateMap<Arc<E>>, name: &str, default: i64) -> i64 { pub fn get_named_level<E, F>(fetch_state: F, name: &str, default: i64) -> i64
let power_level_event = auth_events.get(&(EventType::RoomPowerLevels, "".into())); where
E: Event,
F: Fn(&EventType, &str) -> Option<Arc<E>>,
{
let power_level_event = fetch_state(&EventType::RoomPowerLevels, "");
if let Some(pl) = power_level_event { if let Some(pl) = power_level_event {
// TODO do this the right way and deserialize // TODO do this the right way and deserialize
if let Some(level) = pl.content().get(name) { if let Some(level) = pl.content().get(name) {
@ -744,8 +771,12 @@ pub fn get_named_level<E: Event>(auth_events: &StateMap<Arc<E>>, name: &str, def
/// Helper function to fetch a users default power level from a "m.room.power_level" event's `users` /// Helper function to fetch a users default power level from a "m.room.power_level" event's `users`
/// object. /// object.
pub fn get_user_power_level<E: Event>(user_id: &UserId, auth_events: &StateMap<Arc<E>>) -> i64 { pub fn get_user_power_level<E, F>(user_id: &UserId, fetch_state: F) -> i64
if let Some(pl) = auth_events.get(&(EventType::RoomPowerLevels, "".into())) { where
E: Event,
F: Fn(&EventType, &str) -> Option<Arc<E>>,
{
if let Some(pl) = fetch_state(&EventType::RoomPowerLevels, "") {
if let Ok(content) = serde_json::from_value::<PowerLevelsEventContent>(pl.content()) { if let Ok(content) = serde_json::from_value::<PowerLevelsEventContent>(pl.content()) {
if let Some(level) = content.users.get(user_id) { if let Some(level) = content.users.get(user_id) {
(*level).into() (*level).into()
@ -757,9 +788,7 @@ pub fn get_user_power_level<E: Event>(user_id: &UserId, auth_events: &StateMap<A
} }
} else { } else {
// If no power level event found the creator gets 100 everyone else gets 0 // If no power level event found the creator gets 100 everyone else gets 0
let key = (EventType::RoomCreate, "".into()); fetch_state(&EventType::RoomCreate, "")
auth_events
.get(&key)
.and_then(|create| serde_json::from_value::<CreateEventContent>(create.content()).ok()) .and_then(|create| serde_json::from_value::<CreateEventContent>(create.content()).ok())
.and_then(|create| (create.creator == *user_id).then(|| 100)) .and_then(|create| (create.creator == *user_id).then(|| 100))
.unwrap_or_default() .unwrap_or_default()
@ -792,11 +821,13 @@ pub fn get_send_level<E: Event>(
} }
/// Check user can send invite. /// Check user can send invite.
pub fn can_send_invite<E: Event>(event: &Arc<E>, auth_events: &StateMap<Arc<E>>) -> Result<bool> { pub fn can_send_invite<E, F>(event: &Arc<E>, fetch_state: F) -> Result<bool>
let user_level = get_user_power_level(event.sender(), auth_events); where
let key = (EventType::RoomPowerLevels, "".into()); E: Event,
let invite_level = auth_events F: Fn(&EventType, &str) -> Option<Arc<E>>,
.get(&key) {
let user_level = get_user_power_level(event.sender(), &fetch_state);
let invite_level = fetch_state(&EventType::RoomPowerLevels, "")
.map_or_else( .map_or_else(
|| Ok::<_, Error>(int!(50)), || Ok::<_, Error>(int!(50)),
|power_levels| { |power_levels| {

View File

@ -396,7 +396,7 @@ impl StateResolution {
/// fails the `event_auth::auth_check` will be excluded from the returned `StateMap<EventId>`. /// fails the `event_auth::auth_check` will be excluded from the returned `StateMap<EventId>`.
/// ///
/// For each `events_to_check` event we gather the events needed to auth it from the /// For each `events_to_check` event we gather the events needed to auth it from the
/// `event_map` or `store` and verify each event using the `event_auth::auth_check` /// the `fetch_event` closure and verify each event using the `event_auth::auth_check`
/// function. /// function.
pub fn iterative_auth_check<E, F>( pub fn iterative_auth_check<E, F>(
room_version: &RoomVersion, room_version: &RoomVersion,
@ -424,8 +424,8 @@ impl StateResolution {
let mut auth_events = HashMap::new(); let mut auth_events = HashMap::new();
for aid in &event.auth_events() { for aid in &event.auth_events() {
if let Some(ev) = fetch_event(aid) { if let Some(ev) = fetch_event(aid) {
// TODO synapse check "rejected_reason", I'm guessing this is redacted_because // TODO synapse check "rejected_reason" which is most likely
// in ruma ?? // related to soft-failing
auth_events.insert( auth_events.insert(
( (
ev.kind(), ev.kind(),
@ -472,8 +472,8 @@ impl StateResolution {
room_version, room_version,
&event, &event,
most_recent_prev_event, most_recent_prev_event,
&auth_events,
current_third_party, current_third_party,
|ty, key| auth_events.get(&(ty.clone(), key.to_owned())).cloned(),
)? { )? {
// add event to resolved state map // add event to resolved state map
resolved_state.insert((event.kind(), state_key), event_id.clone()); resolved_state.insert((event.kind(), state_key), event_id.clone());

View File

@ -33,7 +33,7 @@ fn test_ban_pass() {
requester.content(), requester.content(),
prev, prev,
None, None,
&auth_events |ty, key| auth_events.get(&(ty.clone(), key.to_owned())).cloned(),
) )
.unwrap()) .unwrap())
} }
@ -65,7 +65,7 @@ fn test_ban_fail() {
requester.content(), requester.content(),
prev, prev,
None, None,
&auth_events |ty, key| auth_events.get(&(ty.clone(), key.to_owned())).cloned(),
) )
.unwrap()) .unwrap())
} }