All of event_auth follows the spec strictly, all the synapse-isms removed

This commit is contained in:
Devin Ragotzy 2020-08-26 20:08:48 -04:00
parent 025c2df752
commit fbcd26c6d2
2 changed files with 117 additions and 65 deletions

View File

@ -80,6 +80,7 @@ pub fn auth_types_for_event(
pub fn auth_check( pub fn auth_check(
room_version: &RoomVersionId, room_version: &RoomVersionId,
event: &StateEvent, event: &StateEvent,
redacted_event: Option<&StateEvent>,
auth_events: StateMap<StateEvent>, auth_events: StateMap<StateEvent>,
do_sig_check: bool, do_sig_check: bool,
) -> Result<bool> { ) -> Result<bool> {
@ -132,19 +133,25 @@ pub fn auth_check(
if event.kind() == EventType::RoomCreate { if event.kind() == EventType::RoomCreate {
tracing::info!("start m.room.create check"); tracing::info!("start m.room.create check");
// domain of room_id must match domain of sender. // If it has any previous events, reject
if !event.prev_event_ids().is_empty() {
tracing::warn!("the room creation event had previous events");
return Ok(false);
}
// If the domain of the room_id does not match the domain of the sender, reject
if event.room_id().map(|id| id.server_name()) != Some(event.sender().server_name()) { if event.room_id().map(|id| id.server_name()) != Some(event.sender().server_name()) {
tracing::warn!("creation events server does not match sender"); tracing::warn!("creation events server does not match sender");
return Ok(false); // creation events room id does not match senders return Ok(false); // creation events room id does not match senders
} }
// if content.room_version is present and is not a valid version // If content.room_version is present and is not a recognized version, reject
if serde_json::from_value::<RoomVersionId>( if serde_json::from_value::<RoomVersionId>(
event event
.content() .content()
.get("room_version") .get("room_version")
.cloned() .cloned()
// synapse defaults to version 1 // TODO synapse defaults to version 1
.unwrap_or_else(|| serde_json::json!("1")), .unwrap_or_else(|| serde_json::json!("1")),
) )
.is_err() .is_err()
@ -153,11 +160,17 @@ pub fn auth_check(
return Ok(false); return Ok(false);
} }
// If content has no creator field, reject
if event.content().get("creator").is_none() {
tracing::warn!("no creator field found in room create content");
return Ok(false);
}
tracing::info!("m.room.create event was allowed"); tracing::info!("m.room.create event was allowed");
return Ok(true); return Ok(true);
} }
// 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())))
.is_none() .is_none()
@ -167,16 +180,7 @@ pub fn auth_check(
return Ok(false); return Ok(false);
} }
// check for m.federate // [synapse] checks for federation here
if event.room_id().map(|id| id.server_name()) != Some(event.sender().server_name()) {
tracing::info!("checking federation");
if !can_federate(&auth_events) {
tracing::warn!("federation not allowed");
return Ok(false);
}
}
// 4. if type is m.room.aliases // 4. if type is m.room.aliases
if event.kind() == EventType::RoomAliases { if event.kind() == EventType::RoomAliases {
@ -186,13 +190,17 @@ pub fn auth_check(
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
} }
if event.state_key().unwrap().is_empty() {
tracing::warn!("state_key must be non-empty");
return Ok(false); // and be non-empty state_key (point to a user_id)
}
// TODO this is not part of the spec
// if event.state_key().unwrap().is_empty() {
// tracing::warn!("state_key must be non-empty");
// return Ok(false); // and be non-empty state_key (point to a user_id)
// }
// TODO what? "sender's domain doesn't matches"
// If sender's domain doesn't matches state_key, reject
if event.state_key() != Some(event.sender().to_string()) { if event.state_key() != Some(event.sender().to_string()) {
tracing::warn!("no state_key field found for event"); tracing::warn!("state_key does not match sender");
return Ok(false); return Ok(false);
} }
@ -203,6 +211,19 @@ pub fn auth_check(
if event.kind() == EventType::RoomMember { if event.kind() == EventType::RoomMember {
tracing::info!("starting m.room.member check"); tracing::info!("starting m.room.member check");
if event.state_key().is_none() {
tracing::warn!("no state_key found for m.room.member event");
return Ok(false);
}
if event
.deserialize_content::<room::member::MemberEventContent>()
.is_err()
{
tracing::warn!("no membership filed found for m.room.member event content");
return Ok(false);
}
if !is_membership_change_allowed(event.to_requester(), &auth_events)? { if !is_membership_change_allowed(event.to_requester(), &auth_events)? {
return Ok(false); return Ok(false);
} }
@ -211,14 +232,17 @@ pub fn auth_check(
return Ok(true); return Ok(true);
} }
if let Ok(in_room) = check_event_sender_in_room(event, &auth_events) { // If the sender's current membership state is not join, reject
if !in_room { match check_event_sender_in_room(event, &auth_events) {
tracing::warn!("sender not in room"); Some(true) => {} // sender in room
Some(false) => {
tracing::warn!("sender's membership is not join");
return Ok(false);
}
None => {
tracing::warn!("sender not found in room");
return Ok(false); return Ok(false);
} }
} else {
tracing::warn!("sender not in room");
return Ok(false);
} }
// Special case to allow m.room.third_party_invite events where ever // Special case to allow m.room.third_party_invite events where ever
@ -228,6 +252,8 @@ pub fn auth_check(
unimplemented!("third party invite") unimplemented!("third party invite")
} }
// 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 !can_send_event(event, &auth_events)? { if !can_send_event(event, &auth_events)? {
tracing::warn!("user cannot send event"); tracing::warn!("user cannot send event");
return Ok(false); return Ok(false);
@ -235,6 +261,7 @@ pub fn auth_check(
if event.kind() == EventType::RoomPowerLevels { if event.kind() == EventType::RoomPowerLevels {
tracing::info!("starting m.room.power_levels check"); tracing::info!("starting m.room.power_levels check");
if let Some(required_pwr_lvl) = check_power_levels(room_version, event, &auth_events) { if let Some(required_pwr_lvl) = check_power_levels(room_version, event, &auth_events) {
if !required_pwr_lvl { if !required_pwr_lvl {
tracing::warn!("power level was not allowed"); tracing::warn!("power level was not allowed");
@ -248,7 +275,9 @@ pub fn auth_check(
} }
if event.kind() == EventType::RoomRedaction { if event.kind() == EventType::RoomRedaction {
if let RedactAllowed::No = check_redaction(room_version, event, &auth_events)? { if let RedactAllowed::No =
check_redaction(room_version, event, redacted_event, &auth_events)?
{
return Ok(false); return Ok(false);
} }
} }
@ -282,7 +311,6 @@ pub fn is_membership_change_allowed(
auth_events: &StateMap<StateEvent>, auth_events: &StateMap<StateEvent>,
) -> Result<bool> { ) -> Result<bool> {
let content = let content =
// TODO return error
serde_json::from_str::<room::member::MemberEventContent>(&user.content.to_string())?; serde_json::from_str::<room::member::MemberEventContent>(&user.content.to_string())?;
let membership = content.membership; let membership = content.membership;
@ -326,7 +354,7 @@ pub fn is_membership_change_allowed(
.join_rule; .join_rule;
} }
let user_level = get_user_power_level(user.sender, auth_events); let senders_level = get_user_power_level(user.sender, auth_events);
let target_level = get_user_power_level(&target_user_id, auth_events); let target_level = get_user_power_level(&target_user_id, auth_events);
// synapse has a not "what to do for default here 50" // synapse has a not "what to do for default here 50"
@ -363,11 +391,13 @@ pub fn is_membership_change_allowed(
} }
if membership == MembershipState::Invite { if membership == MembershipState::Invite {
// if senders current membership is not join reject
if !caller_in_room { if !caller_in_room {
tracing::warn!("invite sender not in room they are inviting user to"); tracing::warn!("invite sender not in room they are inviting user to");
return Ok(false); return Ok(false);
} }
// If the targets current membership is ban or join
if target_banned { if target_banned {
tracing::warn!("target has been banned"); tracing::warn!("target has been banned");
return Ok(false); return Ok(false);
@ -376,11 +406,15 @@ pub fn is_membership_change_allowed(
return Ok(false); // already in room return Ok(false); // already in room
} else { } else {
let invite_level = get_named_level(auth_events, "invite", 0); let invite_level = get_named_level(auth_events, "invite", 0);
if user_level < invite_level { // If the sender's power level is greater than or equal to the invite level, allow.
if senders_level < invite_level {
tracing::warn!("invite sender does not have power to invite");
return Ok(false); return Ok(false);
} }
} }
// we already check if the join event was the room creator
} else if membership == MembershipState::Join { } else if membership == MembershipState::Join {
// If the sender does not match state_key, reject.
if user.sender != &target_user_id { if user.sender != &target_user_id {
tracing::warn!("cannot force another user to join"); tracing::warn!("cannot force another user to join");
return Ok(false); // cannot force another user to join return Ok(false); // cannot force another user to join
@ -403,23 +437,28 @@ pub fn is_membership_change_allowed(
return Ok(false); return Ok(false);
} }
} else if membership == MembershipState::Leave { } else if membership == MembershipState::Leave {
if user.sender == &target_user_id && !(caller_in_room || caller_invited) {}
// if senders current membership is not join reject
if !caller_in_room { if !caller_in_room {
tracing::warn!("sender not in room they are leaving"); tracing::warn!("sender not in room they are leaving");
return Ok(false); return Ok(false);
} }
if target_banned && user_level < ban_level { // If the target user's current membership state is ban, and the sender's power level is less than the ban level, reject
if target_banned && senders_level < ban_level {
tracing::warn!("not enough power to unban"); tracing::warn!("not enough power to unban");
return Ok(false); // you cannot unban this user return Ok(false); // you cannot unban this user
} else if &target_user_id != user.sender {
let kick_level = get_named_level(auth_events, "kick", 50);
if user_level < kick_level || user_level <= target_level { // If the sender's power level is greater than or equal to the kick level,
tracing::warn!("not enough power to kick user"); // and the target user's power level is less than the sender's power level, allow
return Ok(false); // you do not have the power to kick user } else if senders_level <= get_named_level(auth_events, "kick", 50)
} || target_level < senders_level
{
tracing::warn!("not enough power to kick user");
return Ok(false); // you do not have the power to kick user
} }
} else if membership == MembershipState::Ban { } else if membership == MembershipState::Ban {
// if senders current membership is not join reject
if !caller_in_room { if !caller_in_room {
tracing::warn!("ban sender not in room they are banning user from"); tracing::warn!("ban sender not in room they are banning user from");
return Ok(false); return Ok(false);
@ -427,13 +466,13 @@ pub fn is_membership_change_allowed(
tracing::debug!( tracing::debug!(
"{} < {} || {} <= {}", "{} < {} || {} <= {}",
user_level, senders_level,
ban_level, ban_level,
user_level, senders_level,
target_level target_level
); );
if user_level < ban_level || user_level <= target_level { if senders_level < ban_level || senders_level <= target_level {
tracing::warn!("not enough power to ban"); tracing::warn!("not enough power to ban");
return Ok(false); return Ok(false);
} }
@ -447,37 +486,36 @@ pub fn is_membership_change_allowed(
} }
/// 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.
///
/// A return value of None is not a failure
pub fn check_event_sender_in_room( pub fn check_event_sender_in_room(
event: &StateEvent, event: &StateEvent,
auth_events: &StateMap<StateEvent>, auth_events: &StateMap<StateEvent>,
) -> Result<bool> { ) -> Option<bool> {
let mem = auth_events let mem = auth_events.get(&(EventType::RoomMember, Some(event.sender().to_string())))?;
.get(&(EventType::RoomMember, Some(event.sender().to_string())))
.ok_or_else(|| crate::Error::NotFound("Authe event was not found".into()))?;
// TODO this is check_membership a helper fn in synapse but it does this // TODO this is check_membership a helper fn in synapse but it does this
Ok(mem Some(
.deserialize_content::<room::member::MemberEventContent>()? mem.deserialize_content::<room::member::MemberEventContent>()
.membership .ok()?
== MembershipState::Join) .membership
== MembershipState::Join,
)
} }
/// Is the user allowed to send a specific event based on the rooms power levels. /// 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.
pub fn can_send_event(event: &StateEvent, auth_events: &StateMap<StateEvent>) -> Result<bool> { pub fn can_send_event(event: &StateEvent, auth_events: &StateMap<StateEvent>) -> Result<bool> {
let ple = auth_events.get(&(EventType::RoomPowerLevels, Some("".into()))); let ple = auth_events.get(&(EventType::RoomPowerLevels, Some("".into())));
let send_level = get_send_level(event.kind(), event.state_key(), ple); let event_type_power_level = get_send_level(event.kind(), event.state_key(), ple);
let user_level = get_user_power_level(event.sender(), auth_events); let user_level = get_user_power_level(event.sender(), auth_events);
tracing::debug!( tracing::debug!(
"{} snd {} usr {}", "{} ev_type {} usr {}",
event.event_id().to_string(), event.event_id().to_string(),
send_level, event_type_power_level,
user_level user_level
); );
if user_level < send_level { if user_level < event_type_power_level {
return Ok(false); return Ok(false);
} }
@ -495,17 +533,17 @@ pub fn check_power_levels(
power_event: &StateEvent, power_event: &StateEvent,
auth_events: &StateMap<StateEvent>, auth_events: &StateMap<StateEvent>,
) -> Option<bool> { ) -> Option<bool> {
use itertools::Itertools;
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 {
// TODO synapse returns here, shouldn't this be an error ?? // 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
// with values that are integers (or a string that is an integer), reject.
let user_content = power_event let user_content = power_event
.deserialize_content::<room::power_levels::PowerLevelsEventContent>() .deserialize_content::<room::power_levels::PowerLevelsEventContent>()
.unwrap(); .unwrap();
@ -521,7 +559,7 @@ pub fn check_power_levels(
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;
let user_list = &user_content.users; let user_list = &user_content.users;
for user in old_list.keys().chain(user_list.keys()).dedup() { for user in old_list.keys().chain(user_list.keys()) {
let user: &UserId = user; let user: &UserId = user;
user_levels_to_check.insert(user); user_levels_to_check.insert(user);
} }
@ -531,7 +569,7 @@ pub fn check_power_levels(
let mut event_levels_to_check = btreeset![]; let mut event_levels_to_check = btreeset![];
let old_list = &current_content.events; let old_list = &current_content.events;
let new_list = &user_content.events; let new_list = &user_content.events;
for ev_id in old_list.keys().chain(new_list.keys()).dedup() { for ev_id in old_list.keys().chain(new_list.keys()) {
let ev_id: &EventType = ev_id; let ev_id: &EventType = ev_id;
event_levels_to_check.insert(ev_id); event_levels_to_check.insert(ev_id);
} }
@ -637,27 +675,31 @@ fn get_deserialize_levels(
pub fn check_redaction( pub fn check_redaction(
room_version: &RoomVersionId, room_version: &RoomVersionId,
redaction_event: &StateEvent, redaction_event: &StateEvent,
redacted_event: Option<&StateEvent>,
auth_events: &StateMap<StateEvent>, auth_events: &StateMap<StateEvent>,
) -> Result<RedactAllowed> { ) -> Result<RedactAllowed> {
let user_level = get_user_power_level(redaction_event.sender(), auth_events); let user_level = get_user_power_level(redaction_event.sender(), auth_events);
let redact_level = get_named_level(auth_events, "redact", 50); let redact_level = get_named_level(auth_events, "redact", 50);
if user_level >= redact_level { if user_level >= redact_level {
tracing::info!("redaction allowed via power levels");
return Ok(RedactAllowed::CanRedact); return Ok(RedactAllowed::CanRedact);
} }
if let RoomVersionId::Version1 = room_version { if let RoomVersionId::Version1 = room_version {
// are the redacter and redactee in the same domain // are the redacter and redactee in the same domain
if Some(redaction_event.event_id().server_name()) if Some(redaction_event.sender().server_name())
== redaction_event.redacts().map(|id| id.server_name()) == redaction_event.redacts().and_then(|id| id.server_name())
{ {
tracing::info!("redaction event allowed via room version 1 rules");
return Ok(RedactAllowed::OwnEvent); return Ok(RedactAllowed::OwnEvent);
} }
} else { // redactions to events where the sender's domains match, allow
// TODO synapse has this line also } else if redacted_event.map(|ev| ev.sender()) == Some(redaction_event.sender()) {
// event.internal_metadata.recheck_redaction = True tracing::info!("redaction allowed via own redaction");
return Ok(RedactAllowed::OwnEvent); return Ok(RedactAllowed::OwnEvent);
} }
Ok(RedactAllowed::No) Ok(RedactAllowed::No)
} }

View File

@ -553,7 +553,17 @@ impl StateResolution {
tracing::debug!("event to check {:?}", event.event_id().to_string()); tracing::debug!("event to check {:?}", event.event_id().to_string());
if event_auth::auth_check(room_version, &event, auth_events, false)? { let redacted_event = event
.redacts()
.and_then(|id| StateResolution::get_or_load_event(room_id, id, event_map, store));
if event_auth::auth_check(
room_version,
&event,
redacted_event.as_ref(),
auth_events,
false,
)? {
// 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());
} else { } else {