Replace membership auth with timo's logic

This commit is contained in:
Devin Ragotzy 2020-08-27 15:46:36 -04:00
parent aadccdee64
commit b846aec94a
2 changed files with 146 additions and 160 deletions

View File

@ -1,19 +1,23 @@
use std::convert::TryFrom;
use std::{collections::BTreeMap, convert::TryFrom};
use maplit::btreeset;
use ruma::{
events::{
room::{self, join_rules::JoinRule, member::MembershipState},
room::{
self,
join_rules::JoinRule,
member::{self, MembershipState},
power_levels::{self, PowerLevelsEventContent},
},
EventType,
},
identifiers::{RoomVersionId, UserId},
};
use serde_json::json;
use crate::{
room_version::RoomVersion,
state_event::{Requester, StateEvent},
Result, StateMap,
Error, Result, StateMap,
};
/// Represents the 3 event redaction outcomes.
@ -223,7 +227,7 @@ pub fn auth_check(
return Ok(false);
}
if !is_membership_change_allowed(event.to_requester(), prev_event, &auth_events)? {
if !valid_membership_change(event.to_requester(), prev_event, &auth_events)? {
return Ok(false);
}
@ -298,182 +302,164 @@ pub fn can_federate(auth_events: &StateMap<StateEvent>) -> bool {
}
/// Does the user who sent this member event have required power levels to do so.
pub fn is_membership_change_allowed(
///
/// * `user` - Information about the membership event and user making the request.
/// * `prev_event` - The event that occurred immediately before the `user` event or None.
/// * `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
/// the current State.
pub fn valid_membership_change(
user: Requester<'_>,
prev_event: Option<&StateEvent>,
auth_events: &StateMap<StateEvent>,
) -> Result<bool> {
let state_key = if let Some(s) = user.state_key.as_ref() {
s
} else {
return Err(Error::TempString("State event requires state_key".into()));
};
let content =
serde_json::from_str::<room::member::MemberEventContent>(&user.content.to_string())?;
let membership = content.membership;
let target_membership = content.membership;
// If the only previous event is an m.room.create and the state_key is the creator, allow
if user.prev_event_ids.len() == 1 && membership == MembershipState::Join {
if let Some(create) = prev_event {
if let Ok(create_ev) = create.deserialize_content::<room::create::CreateEventContent>()
{
if user.state_key == Some(create_ev.creator.to_string())
&& create.prev_event_ids().is_empty()
{
tracing::debug!("m.room.member event allowed via m.room.create");
return Ok(true);
}
}
}
}
let target_user_id = UserId::try_from(user.state_key.as_deref().unwrap()).unwrap();
let target_user_id =
UserId::try_from(state_key.as_str()).map_err(|e| Error::TempString(format!("{}", e)))?;
let key = (EventType::RoomMember, Some(user.sender.to_string()));
let caller = auth_events.get(&key);
let caller_in_room = caller.is_some() && check_membership(caller, MembershipState::Join);
let caller_invited = caller.is_some() && check_membership(caller, MembershipState::Invite);
let sender = auth_events.get(&key);
let sender_membership =
sender.map_or(Ok::<_, Error>(member::MembershipState::Leave), |pdu| {
Ok(pdu
.deserialize_content::<room::member::MemberEventContent>()?
.membership)
})?;
let key = (EventType::RoomMember, Some(target_user_id.to_string()));
let target = auth_events.get(&key);
let current = auth_events.get(&key);
let current_membership =
current.map_or(Ok::<_, Error>(member::MembershipState::Leave), |pdu| {
Ok(pdu
.deserialize_content::<room::member::MemberEventContent>()?
.membership)
})?;
let target_in_room = target.is_some() && check_membership(target, MembershipState::Join);
let target_banned = target.is_some() && check_membership(target, MembershipState::Ban);
let key = (EventType::RoomPowerLevels, Some("".into()));
let power_levels = auth_events.get(&key).map_or_else(
|| {
Ok::<_, Error>(power_levels::PowerLevelsEventContent {
ban: 50.into(),
events: BTreeMap::new(),
events_default: 0.into(),
invite: 50.into(),
kick: 50.into(),
redact: 50.into(),
state_default: 0.into(),
users: BTreeMap::new(),
users_default: 0.into(),
notifications: ruma::events::room::power_levels::NotificationPowerLevels {
room: 50.into(),
},
})
},
|power_levels| {
power_levels
.deserialize_content::<PowerLevelsEventContent>()
.map_err(Into::into)
},
)?;
let sender_power = power_levels.users.get(&user.sender).map_or_else(
|| {
if sender_membership != member::MembershipState::Join {
None
} else {
Some(&power_levels.users_default)
}
},
// If it's okay, wrap with Some(_)
Some,
);
let target_power = power_levels.users.get(&target_user_id).map_or_else(
|| {
if target_membership != member::MembershipState::Join {
None
} else {
Some(&power_levels.users_default)
}
},
// If it's okay, wrap with Some(_)
Some,
);
let key = (EventType::RoomJoinRules, Some("".to_string()));
let join_rules_event = auth_events.get(&key);
let mut join_rule = JoinRule::Invite;
let mut join_rules = JoinRule::Invite;
if let Some(jr) = join_rules_event {
join_rule = jr
join_rules = jr
.deserialize_content::<room::join_rules::JoinRulesEventContent>()?
.join_rule;
}
let senders_level = get_user_power_level(user.sender, 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"
let ban_level = get_named_level(auth_events, "ban", 50);
// TODO clean this up
tracing::debug!(
"_is_membership_change_allowed: {}",
serde_json::to_string_pretty(&json!({
"caller_in_room": caller_in_room,
"caller_invited": caller_invited,
"target_banned": target_banned,
"target_in_room": target_in_room,
"membership": membership,
"join_rule": join_rule,
"target_user_id": target_user_id,
"event.user_id": user.sender,
}))
.unwrap(),
);
// we already check if the join event was the room creator
if membership == MembershipState::Invite && content.third_party_invite.is_some() {
// TODO this is unimpled
if !verify_third_party_invite(&user, auth_events) {
tracing::warn!("not invited to this room",);
return Ok(false);
if let Some(prev) = prev_event {
if prev.kind() == EventType::RoomCreate && prev.prev_event_ids().is_empty() {
return Ok(true);
}
if target_banned {
tracing::warn!("banned from this room",);
return Ok(false);
}
tracing::info!("invite succeded");
return Ok(true);
} else if membership == MembershipState::Join {
// If the sender does not match state_key, reject.
if user.sender != &target_user_id {
tracing::warn!("cannot force another user to join");
return Ok(false); // cannot force another user to join
} else if target_banned {
tracing::warn!("cannot join when banned");
return Ok(false); // cannot joined when banned
} else if join_rule == JoinRule::Invite {
if !caller_in_room && !caller_invited {
tracing::warn!("user has not been invited to this room");
return Ok(false); // you are not invited to this room
}
} else if join_rule == JoinRule::Public {
tracing::info!("join rule public")
// pass
} else {
tracing::warn!("the join rule is Private or yet to be spec'ed by Matrix");
// synapse has 2 TODO's may_join list and private rooms
// the join_rule is Private or Knock which means it is not yet spec'ed
return Ok(false);
}
} else if membership == MembershipState::Invite {
// if senders current membership is not join reject
if !caller_in_room {
tracing::warn!("invite sender not in room they are inviting user to");
return Ok(false);
}
// If the targets current membership is ban or join
if target_banned {
tracing::warn!("target has been banned");
return Ok(false);
} else if target_in_room {
tracing::warn!("already in room");
return Ok(false); // already in room
} else {
let invite_level = get_named_level(auth_events, "invite", 0);
// 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);
}
}
} 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 {
tracing::warn!("sender not in room they are leaving");
return Ok(false);
}
// 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");
return Ok(false); // you cannot unban this user
// If the sender's power level is greater than or equal to the kick level,
// and the target user's power level is less than the sender's power level, allow
} 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 {
// if senders current membership is not join reject
if !caller_in_room {
tracing::warn!("ban sender not in room they are banning user from");
return Ok(false);
}
tracing::debug!(
"{} < {} || {} <= {}",
senders_level,
ban_level,
senders_level,
target_level
);
if senders_level < ban_level || senders_level <= target_level {
tracing::warn!("not enough power to ban");
return Ok(false);
}
} else {
tracing::warn!("unknown membership status");
// Unknown membership status
return Ok(false);
}
Ok(true)
Ok(if target_membership == MembershipState::Join {
if user.sender != &target_user_id {
false
} else if let MembershipState::Ban = current_membership {
false
} else {
join_rules == JoinRule::Invite
&& (current_membership == MembershipState::Join
|| current_membership == MembershipState::Invite)
|| join_rules == JoinRule::Public
}
} else if target_membership == MembershipState::Invite {
if let Some(_tp_id) = content.third_party_invite {
if current_membership == MembershipState::Ban {
false
} else {
// TODO this is not filled out
verify_third_party_invite(&user, auth_events)
}
} else if sender_membership != MembershipState::Join
|| current_membership == MembershipState::Join
|| current_membership == MembershipState::Ban
{
false
} else {
sender_power
.filter(|&p| p >= &power_levels.invite)
.is_some()
}
} else if target_membership == MembershipState::Leave {
if user.sender == &target_user_id {
current_membership == MembershipState::Join
|| current_membership == MembershipState::Invite
} else if sender_membership != MembershipState::Join
|| current_membership == MembershipState::Ban
&& sender_power.filter(|&p| p < &power_levels.ban).is_some()
{
false
} else {
sender_power.filter(|&p| p >= &power_levels.kick).is_some()
&& target_power < sender_power
}
} else if target_membership == MembershipState::Ban {
if sender_membership != MembershipState::Join {
false
} else {
sender_power.filter(|&p| p >= &power_levels.ban).is_some()
&& target_power < sender_power
}
} else {
false
})
}
/// Is the event's sender in the room that they sent the event to.

View File

@ -16,7 +16,7 @@ use serde_json::{json, Value as JsonValue};
use state_res::{
event_auth::{
// auth_check, auth_types_for_event, can_federate, check_power_levels, check_redaction,
is_membership_change_allowed,
valid_membership_change,
},
Requester, StateEvent, StateMap, StateStore,
};
@ -261,7 +261,7 @@ fn test_ban_pass() {
sender: &alice(),
};
assert!(is_membership_change_allowed(requester, prev, &auth_events).unwrap())
assert!(valid_membership_change(requester, prev, &auth_events).unwrap())
}
#[test]
@ -285,5 +285,5 @@ fn test_ban_fail() {
sender: &charlie(),
};
assert!(!is_membership_change_allowed(requester, prev, &auth_events).unwrap())
assert!(!valid_membership_change(requester, prev, &auth_events).unwrap())
}