Remove the last few synapse-ism using only spec event auth
This commit is contained in:
parent
1eb89941b7
commit
3cc4ae2bf7
@ -15,7 +15,6 @@ use ruma::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
room_version::RoomVersion,
|
|
||||||
state_event::{Requester, StateEvent},
|
state_event::{Requester, StateEvent},
|
||||||
Error, Result, StateMap,
|
Error, Result, StateMap,
|
||||||
};
|
};
|
||||||
@ -86,54 +85,13 @@ pub fn auth_check(
|
|||||||
incoming_event: &StateEvent,
|
incoming_event: &StateEvent,
|
||||||
prev_event: Option<&StateEvent>,
|
prev_event: Option<&StateEvent>,
|
||||||
auth_events: StateMap<StateEvent>,
|
auth_events: StateMap<StateEvent>,
|
||||||
do_sig_check: bool,
|
current_third_party_invite: Option<&StateEvent>,
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
tracing::info!("auth_check beginning for {}", incoming_event.kind());
|
tracing::info!("auth_check beginning for {}", incoming_event.kind());
|
||||||
|
|
||||||
tracing::debug!(
|
// [synapse] check that all the events are in the same room as `incoming_event`
|
||||||
"{:?}",
|
|
||||||
auth_events
|
|
||||||
.values()
|
|
||||||
.map(|id| id.event_id().to_string())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
);
|
|
||||||
|
|
||||||
// don't let power from other rooms be used
|
// [synapse] do_sig_check check the event has valid signatures for member events
|
||||||
for auth_event in auth_events.values() {
|
|
||||||
if auth_event.room_id() != incoming_event.room_id() {
|
|
||||||
tracing::warn!("found auth event that did not match event's room_id");
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if do_sig_check {
|
|
||||||
let sender_domain = incoming_event.sender().server_name();
|
|
||||||
|
|
||||||
let is_invite_via_3pid = if incoming_event.kind() == EventType::RoomMember {
|
|
||||||
incoming_event
|
|
||||||
.deserialize_content::<room::member::MemberEventContent>()
|
|
||||||
.map(|c| c.membership == MembershipState::Invite && c.third_party_invite.is_some())
|
|
||||||
.unwrap_or_default()
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
// check the event has been signed by the domain of the sender
|
|
||||||
if incoming_event.signatures().get(sender_domain).is_none() && !is_invite_via_3pid {
|
|
||||||
tracing::warn!("event not signed by sender's server");
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if incoming_event.room_version() == RoomVersionId::Version1
|
|
||||||
&& incoming_event
|
|
||||||
.signatures()
|
|
||||||
.get(incoming_event.event_id().server_name().unwrap())
|
|
||||||
.is_none()
|
|
||||||
{
|
|
||||||
tracing::warn!("event not signed by event_id's server");
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO do_size_check is false when called by `iterative_auth_check`
|
// TODO do_size_check is false when called by `iterative_auth_check`
|
||||||
// do_size_check is also mostly accomplished by ruma with the exception of checking event_type,
|
// do_size_check is also mostly accomplished by ruma with the exception of checking event_type,
|
||||||
@ -184,6 +142,23 @@ pub fn auth_check(
|
|||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. Reject if auth_events
|
||||||
|
// a. auth_events cannot have duplicate keys since it's a BTree
|
||||||
|
// b. All entries are valid auth events according to spec
|
||||||
|
let expected_auth = auth_types_for_event(
|
||||||
|
incoming_event.kind(),
|
||||||
|
incoming_event.sender(),
|
||||||
|
incoming_event.state_key(),
|
||||||
|
incoming_event.content().clone(),
|
||||||
|
);
|
||||||
|
for ev_key in auth_events.keys() {
|
||||||
|
// (b)
|
||||||
|
if !expected_auth.contains(ev_key) {
|
||||||
|
tracing::warn!("auth_events contained invalid auth event");
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
if auth_events
|
||||||
.get(&(EventType::RoomCreate, Some("".into())))
|
.get(&(EventType::RoomCreate, Some("".into())))
|
||||||
@ -199,13 +174,13 @@ pub fn auth_check(
|
|||||||
// 4. if type is m.room.aliases
|
// 4. if type is m.room.aliases
|
||||||
if incoming_event.kind() == EventType::RoomAliases {
|
if incoming_event.kind() == EventType::RoomAliases {
|
||||||
tracing::info!("starting m.room.aliases check");
|
tracing::info!("starting m.room.aliases check");
|
||||||
// TODO && room_version "special case aliases auth" ??
|
// [synapse] adds `&& room_version` "special case aliases auth"
|
||||||
if incoming_event.state_key().is_none() {
|
if incoming_event.state_key().is_none() {
|
||||||
tracing::warn!("no state_key field found for event");
|
tracing::warn!("no state_key field found for event");
|
||||||
return Ok(false); // must have state_key
|
return Ok(false); // must have state_key
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO this is not part of the spec
|
// [synapse]
|
||||||
// if event.state_key().unwrap().is_empty() {
|
// if event.state_key().unwrap().is_empty() {
|
||||||
// tracing::warn!("state_key must be non-empty");
|
// tracing::warn!("state_key must be non-empty");
|
||||||
// return Ok(false); // and be non-empty state_key (point to a user_id)
|
// return Ok(false); // and be non-empty state_key (point to a user_id)
|
||||||
@ -239,7 +214,12 @@ pub fn auth_check(
|
|||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !valid_membership_change(incoming_event.to_requester(), prev_event, &auth_events)? {
|
if !valid_membership_change(
|
||||||
|
incoming_event.to_requester(),
|
||||||
|
current_third_party_invite,
|
||||||
|
prev_event,
|
||||||
|
&auth_events,
|
||||||
|
)? {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,11 +240,13 @@ pub fn auth_check(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special case to allow m.room.third_party_invite events where ever
|
// Allow if and only if sender's current power level is greater than
|
||||||
// a user is allowed to issue invites
|
// or equal to the invite level
|
||||||
if incoming_event.kind() == EventType::RoomThirdPartyInvite {
|
if incoming_event.kind() == EventType::RoomThirdPartyInvite
|
||||||
// TODO impl this
|
&& !can_send_invite(&incoming_event.to_requester(), &auth_events)?
|
||||||
unimplemented!("third party invite")
|
{
|
||||||
|
tracing::warn!("sender's cannot send invites in this room");
|
||||||
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@ -313,6 +295,7 @@ pub fn auth_check(
|
|||||||
pub fn valid_membership_change(
|
pub fn valid_membership_change(
|
||||||
user: Requester<'_>,
|
user: Requester<'_>,
|
||||||
prev_event: Option<&StateEvent>,
|
prev_event: Option<&StateEvent>,
|
||||||
|
current_third_party_invite: Option<&StateEvent>,
|
||||||
auth_events: &StateMap<StateEvent>,
|
auth_events: &StateMap<StateEvent>,
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
let state_key = if let Some(s) = user.state_key.as_ref() {
|
let state_key = if let Some(s) = user.state_key.as_ref() {
|
||||||
@ -422,12 +405,12 @@ pub fn valid_membership_change(
|
|||||||
|| join_rules == JoinRule::Public
|
|| join_rules == JoinRule::Public
|
||||||
}
|
}
|
||||||
} else if target_membership == MembershipState::Invite {
|
} else if target_membership == MembershipState::Invite {
|
||||||
if let Some(_tp_id) = content.third_party_invite {
|
// If content has third_party_invite key
|
||||||
|
if let Some(tp_id) = content.third_party_invite {
|
||||||
if current_membership == MembershipState::Ban {
|
if current_membership == MembershipState::Ban {
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
// TODO this is not filled out
|
verify_third_party_invite(&user, &tp_id, current_third_party_invite)
|
||||||
verify_third_party_invite(&user, auth_events)
|
|
||||||
}
|
}
|
||||||
} else if sender_membership != MembershipState::Join
|
} else if sender_membership != MembershipState::Join
|
||||||
|| current_membership == MembershipState::Join
|
|| current_membership == MembershipState::Join
|
||||||
@ -470,7 +453,6 @@ pub fn check_event_sender_in_room(
|
|||||||
auth_events: &StateMap<StateEvent>,
|
auth_events: &StateMap<StateEvent>,
|
||||||
) -> Option<bool> {
|
) -> Option<bool> {
|
||||||
let mem = auth_events.get(&(EventType::RoomMember, Some(sender.to_string())))?;
|
let mem = auth_events.get(&(EventType::RoomMember, Some(sender.to_string())))?;
|
||||||
// TODO this is check_membership a helper fn in synapse but it does this
|
|
||||||
Some(
|
Some(
|
||||||
mem.deserialize_content::<room::member::MemberEventContent>()
|
mem.deserialize_content::<room::member::MemberEventContent>()
|
||||||
.ok()?
|
.ok()?
|
||||||
@ -508,12 +490,11 @@ pub fn can_send_event(event: &StateEvent, auth_events: &StateMap<StateEvent>) ->
|
|||||||
|
|
||||||
/// 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(
|
pub fn check_power_levels(
|
||||||
room_version: &RoomVersionId,
|
_: &RoomVersionId,
|
||||||
power_event: &StateEvent,
|
power_event: &StateEvent,
|
||||||
auth_events: &StateMap<StateEvent>,
|
auth_events: &StateMap<StateEvent>,
|
||||||
) -> Option<bool> {
|
) -> Option<bool> {
|
||||||
let key = (power_event.kind(), power_event.state_key());
|
let key = (power_event.kind(), power_event.state_key());
|
||||||
|
|
||||||
let current_state = if let Some(current_state) = auth_events.get(&key) {
|
let current_state = if let Some(current_state) = auth_events.get(&key) {
|
||||||
current_state
|
current_state
|
||||||
} else {
|
} else {
|
||||||
@ -555,21 +536,18 @@ pub fn check_power_levels(
|
|||||||
|
|
||||||
tracing::debug!("events to check {:?}", event_levels_to_check);
|
tracing::debug!("events to check {:?}", event_levels_to_check);
|
||||||
|
|
||||||
// TODO validate MSC2209 depending on room version check "notifications".
|
// [synapse] validate MSC2209 depending on room version check "notifications".
|
||||||
// synapse does this very differently with the loops (see comments below)
|
// if RoomVersion::new(room_version).limit_notifications_power_levels {
|
||||||
// but since we have a validated JSON event we can check the levels directly
|
// let old_level: i64 = current_content.notifications.room.into();
|
||||||
// I hope...
|
// let new_level: i64 = user_content.notifications.room.into();
|
||||||
if RoomVersion::new(room_version).limit_notifications_power_levels {
|
|
||||||
let old_level: i64 = current_content.notifications.room.into();
|
|
||||||
let new_level: i64 = user_content.notifications.room.into();
|
|
||||||
|
|
||||||
let old_level_too_big = old_level > user_level;
|
// let old_level_too_big = old_level > user_level;
|
||||||
let new_level_too_big = new_level > user_level;
|
// let new_level_too_big = new_level > user_level;
|
||||||
if old_level_too_big || new_level_too_big {
|
// if old_level_too_big || new_level_too_big {
|
||||||
tracing::warn!("m.room.power_level cannot add ops > than own");
|
// tracing::warn!("m.room.power_level cannot add ops > than own");
|
||||||
return Some(false); // cannot add ops greater than own
|
// return Some(false); // cannot add ops greater than own
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
let old_state = ¤t_content;
|
let old_state = ¤t_content;
|
||||||
let new_state = &user_content;
|
let new_state = &user_content;
|
||||||
@ -584,11 +562,15 @@ pub fn check_power_levels(
|
|||||||
if old_level.is_some() && new_level.is_some() && old_level == new_level {
|
if old_level.is_some() && new_level.is_some() && old_level == new_level {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the current value is equal to the sender's current power level, reject
|
||||||
if user != power_event.sender() && old_level.map(|int| (*int).into()) == Some(user_level) {
|
if user != power_event.sender() && old_level.map(|int| (*int).into()) == Some(user_level) {
|
||||||
tracing::warn!("m.room.power_level cannot remove ops == to own");
|
tracing::warn!("m.room.power_level cannot remove ops == to own");
|
||||||
return Some(false); // cannot remove ops level == to own
|
return Some(false); // cannot remove ops level == to own
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the current value is higher than the sender's current power level, reject
|
||||||
|
// If the new value is higher than the sender's current power level, reject
|
||||||
let old_level_too_big = old_level.map(|int| (*int).into()) > Some(user_level);
|
let old_level_too_big = old_level.map(|int| (*int).into()) > Some(user_level);
|
||||||
let new_level_too_big = new_level.map(|int| (*int).into()) > Some(user_level);
|
let new_level_too_big = new_level.map(|int| (*int).into()) > Some(user_level);
|
||||||
if old_level_too_big || new_level_too_big {
|
if old_level_too_big || new_level_too_big {
|
||||||
@ -605,6 +587,8 @@ pub fn check_power_levels(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the current value is higher than the sender's current power level, reject
|
||||||
|
// If the new value is higher than the sender's current power level, reject
|
||||||
let old_level_too_big = old_level.map(|int| (*int).into()) > Some(user_level);
|
let old_level_too_big = old_level.map(|int| (*int).into()) > Some(user_level);
|
||||||
let new_level_too_big = new_level.map(|int| (*int).into()) > Some(user_level);
|
let new_level_too_big = new_level.map(|int| (*int).into()) > Some(user_level);
|
||||||
if old_level_too_big || new_level_too_big {
|
if old_level_too_big || new_level_too_big {
|
||||||
@ -664,9 +648,17 @@ pub fn check_redaction(
|
|||||||
return Ok(RedactAllowed::CanRedact);
|
return Ok(RedactAllowed::CanRedact);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FROM SPEC:
|
||||||
|
// Redaction events are always accepted (provided the event is allowed by `events` and
|
||||||
|
// `events_default` in the power levels). However, servers should not apply or send redaction's
|
||||||
|
// to clients until both the redaction event and original event have been seen, and are valid.
|
||||||
|
// Servers should only apply redaction's to events where the sender's domains match,
|
||||||
|
// or the sender of the redaction has the appropriate permissions per the power levels.
|
||||||
|
|
||||||
|
// version 1 check
|
||||||
if let RoomVersionId::Version1 = room_version {
|
if let RoomVersionId::Version1 = room_version {
|
||||||
// are the redacter and redactee in the same domain
|
// If the domain of the event_id of the event being redacted is the same as the domain of the event_id of the m.room.redaction, allow
|
||||||
if Some(redaction_event.sender().server_name())
|
if redaction_event.event_id().server_name()
|
||||||
== redaction_event.redacts().and_then(|id| id.server_name())
|
== redaction_event.redacts().and_then(|id| id.server_name())
|
||||||
{
|
{
|
||||||
tracing::info!("redaction event allowed via room version 1 rules");
|
tracing::info!("redaction event allowed via room version 1 rules");
|
||||||
@ -789,10 +781,66 @@ pub fn get_send_level(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TODO this is unimplemented
|
/// Check user can send invite.
|
||||||
pub fn verify_third_party_invite(
|
pub fn can_send_invite(event: &Requester<'_>, auth_events: &StateMap<StateEvent>) -> Result<bool> {
|
||||||
_event: &Requester<'_>,
|
let user_level = get_user_power_level(event.sender, auth_events);
|
||||||
_auth_events: &StateMap<StateEvent>,
|
let key = (EventType::RoomPowerLevels, Some("".into()));
|
||||||
) -> bool {
|
let invite_level = auth_events
|
||||||
unimplemented!("impl third party invites")
|
.get(&key)
|
||||||
|
.map_or_else(
|
||||||
|
|| Ok::<_, Error>(js_int::int!(50)),
|
||||||
|
|power_levels| {
|
||||||
|
power_levels
|
||||||
|
.deserialize_content::<PowerLevelsEventContent>()
|
||||||
|
.map(|pl| pl.invite)
|
||||||
|
.map_err(Into::into)
|
||||||
|
},
|
||||||
|
)?
|
||||||
|
.into();
|
||||||
|
|
||||||
|
Ok(user_level >= invite_level)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_third_party_invite(
|
||||||
|
event: &Requester<'_>,
|
||||||
|
tp_id: &member::ThirdPartyInvite,
|
||||||
|
current_third_party_invite: Option<&StateEvent>,
|
||||||
|
) -> bool {
|
||||||
|
// 1. check for user being banned happens before this is called
|
||||||
|
// checking for mxid and token keys is done by ruma when deserializing
|
||||||
|
|
||||||
|
if event.state_key != Some(tp_id.signed.mxid.to_string()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is no m.room.third_party_invite event in the current room state
|
||||||
|
// with state_key matching token, reject
|
||||||
|
if let Some(current_tpid) = current_third_party_invite {
|
||||||
|
if current_tpid.state_key() != Some(tp_id.signed.token.to_string()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.sender != current_tpid.sender() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any signature in signed matches any public key in the m.room.third_party_invite event, allow
|
||||||
|
if let Ok(tpid_ev) = serde_json::from_value::<
|
||||||
|
ruma::events::room::third_party_invite::ThirdPartyInviteEventContent,
|
||||||
|
>(current_tpid.content().clone())
|
||||||
|
{
|
||||||
|
// A list of public keys in the public_keys field
|
||||||
|
for key in tpid_ev.public_keys.unwrap_or_default() {
|
||||||
|
if key.public_key == tp_id.signed.token {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// A single public key in the public_key field
|
||||||
|
tpid_ev.public_key == tp_id.signed.token
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
12
src/lib.rs
12
src/lib.rs
@ -546,12 +546,22 @@ impl StateResolution {
|
|||||||
.filter_map(|id| StateResolution::get_or_load_event(room_id, id, event_map, store))
|
.filter_map(|id| StateResolution::get_or_load_event(room_id, id, event_map, store))
|
||||||
.next_back();
|
.next_back();
|
||||||
|
|
||||||
|
// The key for this is (eventType + a state_key of the signed token not sender) so search
|
||||||
|
// for it
|
||||||
|
let current_third_party = auth_events.iter().find_map(|(_, pdu)| {
|
||||||
|
if pdu.kind() == EventType::RoomThirdPartyInvite {
|
||||||
|
Some(pdu.clone()) // TODO no clone, auth_events is borrowed while moved
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if event_auth::auth_check(
|
if event_auth::auth_check(
|
||||||
room_version,
|
room_version,
|
||||||
&event,
|
&event,
|
||||||
most_recent_prev_event.as_ref(),
|
most_recent_prev_event.as_ref(),
|
||||||
auth_events,
|
auth_events,
|
||||||
false,
|
current_third_party.as_ref(),
|
||||||
)? {
|
)? {
|
||||||
// add event to resolved state map
|
// add event to resolved state map
|
||||||
resolved_state.insert((event.kind(), event.state_key()), event_id.clone());
|
resolved_state.insert((event.kind(), event.state_key()), event_id.clone());
|
||||||
|
@ -261,7 +261,7 @@ fn test_ban_pass() {
|
|||||||
sender: &alice(),
|
sender: &alice(),
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(valid_membership_change(requester, prev, &auth_events).unwrap())
|
assert!(valid_membership_change(requester, prev, None, &auth_events).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -285,5 +285,5 @@ fn test_ban_fail() {
|
|||||||
sender: &charlie(),
|
sender: &charlie(),
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(!valid_membership_change(requester, prev, &auth_events).unwrap())
|
assert!(!valid_membership_change(requester, prev, None, &auth_events).unwrap())
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user