Done transcribing all logic is filled in no more unimplemented!

This commit is contained in:
Devin R 2020-07-19 08:42:45 -04:00
parent b2cbc3cd5d
commit 954fe5e51e
7 changed files with 1064 additions and 45 deletions

View File

@ -6,6 +6,8 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
itertools = "0.9.0"
js_int = "0.1.8"
petgraph = "0.5.1"
serde = { version = "1.0.114", features = ["derive"] }
serde_json = "1.0.56"

567
src/event_auth.rs Normal file
View File

@ -0,0 +1,567 @@
use std::convert::TryFrom;
use ruma::{
events::{
room::{self, join_rules::JoinRule, member::MembershipState},
EventType,
},
identifiers::{RoomVersionId, UserId},
};
use crate::{room_version::RoomVersion, state_event::StateEvent, StateMap};
/// Represents the 3 event redaction outcomes.
pub enum RedactAllowed {
/// The event is the users so redaction can take place.
OwnEvent,
/// The user can easily redact the event.
CanRedact,
/// The user does not have enough power to redact this event.
No,
}
pub(crate) fn auth_types_for_event(event: &StateEvent) -> Vec<(EventType, String)> {
if event.kind() == EventType::RoomCreate {
return vec![];
}
let mut auth_types = vec![
(EventType::RoomPowerLevels, "".to_string()),
(EventType::RoomMember, event.sender().to_string()),
(EventType::RoomCreate, "".to_string()),
];
if event.kind() == EventType::RoomMember {
if let Ok(content) = event.deserialize_content::<room::member::MemberEventContent>() {
if [MembershipState::Join, MembershipState::Invite].contains(&content.membership) {
auth_types.push((EventType::RoomJoinRules, "".into()))
}
// TODO is None the same as "" for state_key, probably NOT
auth_types.push((EventType::RoomMember, event.state_key().unwrap_or_default()));
if content.membership == MembershipState::Invite {
if let Some(t_id) = content.third_party_invite {
auth_types.push((EventType::RoomThirdPartyInvite, t_id.signed.token))
}
}
}
}
auth_types
}
pub(crate) fn auth_check(
room_version: &RoomVersionId,
event: &StateEvent,
auth_events: StateMap<StateEvent>,
) -> Option<bool> {
tracing::debug!("auth_check begingin");
for auth_event in auth_events.values() {
if auth_event.room_id() != event.room_id() {
return Some(false);
}
}
// TODO sig_check is false when called by `iterative_auth_check`
// Implementation of https://matrix.org/docs/spec/rooms/v1#authorization-rules
//
// 1. If type is m.room.create:
if event.kind() == EventType::RoomCreate {
// domain of room_id must match domain of sender.
if event.room_id().map(|id| id.server_name()) != Some(event.sender().server_name()) {
return Some(false);
}
// if content.room_version is present and is not a valid version
// TODO check this out (what event has this as content?)
if serde_json::from_value::<RoomVersionId>(
event
.content()
.get("room_version")
.cloned()
.unwrap_or(serde_json::json!({})),
)
.is_err()
{
return Some(false);
}
tracing::debug!("m.room.create event was allowed");
return Some(true);
}
// 3. If event does not have m.room.create in auth_events reject.
if auth_events
.get(&(EventType::RoomCreate, "".into()))
.is_none()
{
return Some(false);
}
// check for m.federate
if event.room_id().map(|id| id.server_name()) != Some(event.sender().server_name()) {
if !can_federate(&auth_events) {
return Some(false);
}
}
// 4. if type is m.room.aliases
if event.kind() == EventType::RoomAliases {
// TODO && room_version "special case aliases auth" ??
if event.state_key().is_none() {
return Some(false); // must have state_key
}
if event.state_key().unwrap().is_empty() {
return Some(false); // and be non-empty state_key (point to a user_id)
}
if event.state_key() != Some(event.sender().to_string()) {
return Some(false);
}
tracing::debug!("m.room.aliases event was allowed");
return Some(true);
}
if event.kind() == EventType::RoomMember {
if is_membership_change_allowed(event, &auth_events)? {
tracing::debug!("m.room.member event was allowed");
return Some(true);
}
}
if !check_event_sender_in_room(event, &auth_events)? {
return Some(false);
}
// Special case to allow m.room.third_party_invite events where ever
// a user is allowed to issue invites
if event.kind() == EventType::RoomThirdPartyInvite {
// TODO impl this
unimplemented!("third party invite")
}
if !can_send_event(event, &auth_events)? {
return Some(false);
}
if event.kind() == EventType::RoomPowerLevels {
if !check_power_levels(room_version, event, &auth_events)? {
return Some(false);
}
}
if event.kind() == EventType::RoomRedaction {
if let RedactAllowed::No = check_redaction(room_version, event, &auth_events)? {
return Some(false);
}
}
tracing::debug!("allowing event passed all checks");
Some(true)
}
// synapse has an `event: &StateEvent` param but it's never used
/// Can this room federate based on its m.room.create event.
fn can_federate(auth_events: &StateMap<StateEvent>) -> bool {
let creation_event = auth_events.get(&(EventType::RoomCreate, "".into()));
if let Some(ev) = creation_event {
if let Some(fed) = ev.content().get("m.federate") {
fed.to_string() == "true"
} else {
false
}
} else {
false
}
}
/// Dose the user who sent this member event have required power levels to do so.
fn is_membership_change_allowed(
event: &StateEvent,
auth_events: &StateMap<StateEvent>,
) -> Option<bool> {
let content = event
.deserialize_content::<room::member::MemberEventContent>()
.ok()?;
let membership = content.membership;
// check if this is the room creator joining
if event.prev_event_ids().len() == 1 && membership == MembershipState::Join {
if let Some(create) = auth_events.get(&(EventType::RoomCreate, "".into())) {
if let Ok(create_ev) = create.deserialize_content::<room::create::CreateEventContent>()
{
if event.state_key() == Some(create_ev.creator.to_string()) {
tracing::debug!("m.room.member event allowed via m.room.create");
return Some(true);
}
}
}
}
let target_user_id = UserId::try_from(event.state_key()?).ok()?;
// if the server_names are different and federation is NOT allowed
if event.room_id()?.server_name() != target_user_id.server_name() {
if !can_federate(auth_events) {
return Some(false);
}
}
// TODO according to
// https://github.com/matrix-org/synapse/blob/f2af3e4fc550e7e93be1b0f425c3e9c484b96293/synapse/events/__init__.py#L240
// sender is the `user_id`?
let key = (EventType::RoomMember, event.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 key = (EventType::RoomMember, target_user_id.to_string());
let target = auth_events.get(&key);
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::RoomJoinRules, "".to_string());
let join_rules_event = auth_events.get(&key);
let mut join_rule = JoinRule::Invite;
if let Some(jr) = join_rules_event {
join_rule = jr
.deserialize_content::<room::join_rules::JoinRulesEventContent>()
.ok()? // TODO these are errors? and should be treated as a DB failure?
.join_rule;
}
let user_level = get_user_power_level(event.sender(), auth_events);
let target_level = get_user_power_level(event.sender(), auth_events);
// synapse has a not "what to do for default here ##"
let ban_level = get_named_level(auth_events, "ban", 50);
// TODO clean this up
tracing::debug!(
"_is_membership_change_allowed: {}",
serde_json::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": event.sender(),
}),
);
if membership == MembershipState::Invite && content.third_party_invite.is_some() {
// TODO impl this
unimplemented!("third party invite")
}
if membership != MembershipState::Join {
if caller_invited
&& membership == MembershipState::Leave
&& &target_user_id == event.sender()
{
return Some(true);
}
}
if membership == MembershipState::Invite {
if target_banned {
return Some(false);
} else if target_in_room {
return Some(false);
} else {
let invite_level = get_named_level(auth_events, "invite", 0);
if user_level < invite_level {
return Some(false);
}
}
} else if membership == MembershipState::Join {
if event.sender() != &target_user_id {
return Some(false); // cannot force another user to join
} else if target_banned {
return Some(false); // cannot joined when banned
} else if join_rule == JoinRule::Public {
// pass
} else if join_rule == JoinRule::Invite {
if !caller_in_room && !caller_invited {
return Some(false); // you are not invited to this room
}
} else {
// synapse has 2 TODO's may_join list and private rooms
return Some(false);
}
} else if membership == MembershipState::Leave {
if target_banned && user_level < ban_level {
return Some(false); // you cannot unban this user
} else if &target_user_id != event.sender() {
let kick_level = get_named_level(auth_events, "kick", 50);
if user_level < kick_level || user_level <= target_level {
return Some(false); // you do not have the power to kick user
}
}
} else if membership == MembershipState::Ban {
if user_level < ban_level || user_level <= target_level {
return Some(false);
}
} else {
// Unknown membership status
return Some(false);
}
Some(false)
}
/// Is the event's sender in the room that they sent the event to.
fn check_event_sender_in_room(
event: &StateEvent,
auth_events: &StateMap<StateEvent>,
) -> Option<bool> {
let mem = auth_events.get(&(EventType::RoomMember, event.sender().to_string()))?;
// TODO this is check_membership
Some(
mem.deserialize_content::<room::member::MemberEventContent>()
.ok()?
.membership
== MembershipState::Join,
)
}
/// Is the user allowed to send a specific event.
fn can_send_event(event: &StateEvent, auth_events: &StateMap<StateEvent>) -> Option<bool> {
let ple = auth_events.get(&(EventType::RoomPowerLevels, "".into()));
let send_level = get_send_level(event.kind(), event.state_key(), ple);
let user_level = get_user_power_level(event.sender(), auth_events);
if user_level < send_level {
return Some(false);
}
if let Some(sk) = event.state_key() {
if sk.starts_with("@") {
if sk != event.sender().to_string() {
return Some(false); // permission required to post in this room
}
}
}
Some(true)
}
/// Confirm that the event sender has the required power levels.
fn check_power_levels(
room_version: &RoomVersionId,
power_event: &StateEvent,
auth_events: &StateMap<StateEvent>,
) -> Option<bool> {
use itertools::Itertools;
let key = (power_event.kind(), power_event.state_key()?);
let current_state = auth_events.get(&key)?;
let user_content = power_event
.deserialize_content::<room::power_levels::PowerLevelsEventContent>()
.ok()?;
let current_content = current_state
.deserialize_content::<room::power_levels::PowerLevelsEventContent>()
.ok()?;
// validation of users is done in Ruma, synapse for loops validating user_ids and integers here
let user_level = get_user_power_level(power_event.sender(), auth_events);
let mut user_levels_to_check = vec![];
let old_list = &current_content.users;
let user_list = &user_content.users;
for user in old_list.keys().chain(user_list.keys()).dedup() {
let user: &UserId = user;
user_levels_to_check.push(user);
}
let mut event_levels_to_check = vec![];
let old_list = &current_content.events;
let new_list = &user_content.events;
for ev_id in old_list.keys().chain(new_list.keys()).dedup() {
let ev_id: &EventType = ev_id;
event_levels_to_check.push(ev_id);
}
// TODO validate MSC2209 depending on room version check "notifications".
// synapse does this very differently with the loops (see comments below)
// but since we have a validated JSON event we can check the levels directly
// I hope...
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 new_level_too_big = new_level > user_level;
if old_level_too_big || new_level_too_big {
return Some(false); // cannot add ops greater than own
}
}
let old_state = &current_content;
let new_state = &user_content;
// synapse does not have to split up these checks since we can't combine UserIds and
// EventTypes we do 2 loops
// UserId loop
for user in user_levels_to_check {
let old_level = old_state.users.get(user);
let new_level = new_state.users.get(user);
if old_level.is_some() && new_level.is_some() {
if old_level == new_level {
continue;
}
}
if user != power_event.sender() {
if old_level.map(|int| (*int).into()) == Some(user_level) {
return Some(false); // cannot remove ops level == to own
}
}
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);
if old_level_too_big || new_level_too_big {
return Some(false); // cannot add ops greater than own
}
}
// EventType loop
for ev_type in event_levels_to_check {
let old_level = old_state.events.get(ev_type);
let new_level = new_state.events.get(ev_type);
if old_level.is_some() && new_level.is_some() {
if old_level == new_level {
continue;
}
}
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);
if old_level_too_big || new_level_too_big {
return Some(false); // cannot add ops greater than own
}
}
Some(true)
}
/// Does the event redacting come from a user with enough power to redact the given event.
fn check_redaction(
room_version: &RoomVersionId,
redaction_event: &StateEvent,
auth_events: &StateMap<StateEvent>,
) -> Option<RedactAllowed> {
let user_level = get_user_power_level(redaction_event.sender(), auth_events);
let redact_level = get_named_level(auth_events, "redact", 50);
if user_level >= redact_level {
return Some(RedactAllowed::CanRedact);
}
if room_version.is_version_1() {
if redaction_event.event_id() == redaction_event.redacts() {
return Some(RedactAllowed::OwnEvent);
}
} else {
// TODO synapse has this line also
// event.internal_metadata.recheck_redaction = True
return Some(RedactAllowed::OwnEvent);
}
Some(RedactAllowed::No)
}
/// Check that the member event matches `state`.
///
/// This function returns false instead of failing when deserialization fails.
fn check_membership(member_event: Option<&StateEvent>, state: MembershipState) -> bool {
if let Some(event) = member_event {
if let Ok(content) =
serde_json::from_value::<room::member::MemberEventContent>(event.content())
{
content.membership == state
} else {
false
}
} else {
false
}
}
fn get_named_level(auth_events: &StateMap<StateEvent>, name: &str, default: i64) -> i64 {
let power_level_event = auth_events.get(&(EventType::RoomPowerLevels, "".into()));
if let Some(pl) = power_level_event {
// TODO do this the right way and deserialize
if let Some(level) = pl.content().get(name) {
level.to_string().parse().unwrap_or(default)
} else {
0
}
} else {
default
}
}
fn get_user_power_level(user_id: &UserId, auth_events: &StateMap<StateEvent>) -> i64 {
if let Some(pl) = auth_events.get(&(EventType::RoomPowerLevels, "".into())) {
if let Ok(content) = pl.deserialize_content::<room::power_levels::PowerLevelsEventContent>()
{
if let Some(level) = content.users.get(user_id) {
(*level).into()
} else {
0
}
} else {
0 // TODO if this fails DB error?
}
} else {
// if no power level event found the creator gets 100 everyone else gets 0
let key = (EventType::RoomCreate, "".into());
if let Some(create) = auth_events.get(&key) {
if let Ok(c) = create.deserialize_content::<room::create::CreateEventContent>() {
if &c.creator == user_id {
100
} else {
0
}
} else {
0
}
} else {
0
}
}
}
fn get_send_level(
e_type: EventType,
state_key: Option<String>,
power_lvl: Option<&StateEvent>,
) -> i64 {
if let Some(ple) = power_lvl {
if state_key.is_some() {
if let Ok(content) =
serde_json::from_value::<room::power_levels::PowerLevelsEventContent>(ple.content())
{
if let Some(_specific_ev) = content.events.get(&e_type) {
// this is done differently in synapse the `specific_ev` is set and if `users_default` is
// found it is used
}
content.users_default.into()
} else {
return 50;
}
} else {
return 0;
}
} else {
return 0;
}
}

View File

@ -1,15 +1,16 @@
use std::{collections::BTreeMap, time::SystemTime};
use std::{
collections::{BTreeMap, BinaryHeap},
time::SystemTime,
};
use petgraph::Graph;
use ruma::{
events::{
room::{self},
AnyStateEvent, AnyStrippedStateEvent, AnySyncStateEvent, EventType,
},
events::EventType,
identifiers::{EventId, RoomId, RoomVersionId},
};
use serde::{Deserialize, Serialize};
mod event_auth;
mod room_version;
mod state_event;
mod state_store;
@ -78,10 +79,11 @@ impl StateResolution {
// add the auth_diff to conflicting now we have a full set of conflicting events
auth_diff.extend(conflicting.values().cloned().flatten());
let all_conflicted = auth_diff;
let mut all_conflicted = auth_diff;
tracing::debug!("full conflicted set is {} events", all_conflicted.len());
// gather missing events for the event_map
let events = store
.get_events(
&all_conflicted
@ -92,6 +94,12 @@ impl StateResolution {
.collect::<Vec<_>>(),
)
.unwrap();
// update event_map to include the fetched events
event_map.extend(
events
.into_iter()
.flat_map(|ev| Some((ev.event_id()?.clone(), ev))),
);
for event in event_map.values() {
if event.room_id() != Some(room_id) {
@ -106,9 +114,11 @@ impl StateResolution {
));
}
}
// TODO throw error if event is not for this room
// TODO make sure each conflicting event is in?? event_map `{eid for eid in full_conflicted_set if eid in event_map}`
// TODO make sure each conflicting event is in?? event_map `{eid for eid in full_conflicted_set if eid in event_map}`
all_conflicted.retain(|id| event_map.contains_key(id));
// get only the power events with a state_key: "" or ban/kick event (sender != state_key)
let power_events = all_conflicted
.iter()
.filter(|id| is_power_event(id, store))
@ -116,7 +126,7 @@ impl StateResolution {
.collect::<Vec<_>>();
// sort the power events based on power_level/clock/event_id and outgoing/incoming edges
let mut sorted_power_levels = self.revers_topological_power_sort(
let mut sorted_power_levels = self.reverse_topological_power_sort(
room_id,
&power_events,
&mut event_map,
@ -132,13 +142,14 @@ impl StateResolution {
&clean,
&mut event_map,
store,
);
)?;
// At this point the power_events have been resolved we now have to
// sort the remaining events using the mainline of the resolved power level.
sorted_power_levels.dedup();
let deduped_power_ev = sorted_power_levels;
// we have resolved the power events so remove them, I'm sure theres other reasons to do so
let events_to_resolve = all_conflicted
.iter()
.filter(|id| deduped_power_ev.contains(id))
@ -157,7 +168,7 @@ impl StateResolution {
&resolved,
&mut event_map,
store,
);
)?;
// add unconflicted state to the resolved state
resolved_state.extend(clean);
@ -197,23 +208,153 @@ impl StateResolution {
fn get_auth_chain_diff(
&mut self,
state_sets: &[StateMap<EventId>],
event_map: &EventMap<StateEvent>,
_event_map: &EventMap<StateEvent>,
store: &dyn StateStore,
) -> Result<Vec<EventId>, String> {
use itertools::Itertools;
tracing::debug!("calculating auth chain difference");
panic!()
store.auth_chain_diff(
&state_sets
.iter()
.flat_map(|map| map.values())
.dedup()
.collect::<Vec<_>>(),
)
}
fn revers_topological_power_sort(
fn reverse_topological_power_sort(
&mut self,
room_id: &RoomId,
power_events: &[EventId],
event_map: &EventMap<StateEvent>,
store: &dyn StateStore,
conflicted_set: &[EventId],
auth_diff: &[EventId],
) -> Vec<EventId> {
tracing::debug!("reverse topological sort of power events");
panic!()
let mut graph = BTreeMap::new();
for (idx, event_id) in power_events.iter().enumerate() {
self.add_event_and_auth_chain_to_graph(room_id, &mut graph, event_id, store, auth_diff);
// We yield occasionally when we're working with large data sets to
// ensure that we don't block the reactor loop for too long.
if idx % _YIELD_AFTER_ITERATIONS == 0 {
// yield clock.sleep(0)
}
}
// this is used in the `key_fn` passed to the lexico_topo_sort fn
let mut event_to_pl = BTreeMap::new();
for (idx, event_id) in graph.keys().enumerate() {
let pl = self.get_power_level_for_sender(room_id, &event_id, event_map, store);
event_to_pl.insert(event_id.clone(), pl);
// We yield occasionally when we're working with large data sets to
// ensure that we don't block the reactor loop for too long.
if idx % _YIELD_AFTER_ITERATIONS == 0 {
// yield clock.sleep(0)
}
}
self.lexicographical_topological_sort(&mut graph, |event_id| {
let ev = event_map.get(event_id).cloned().unwrap();
let pl = event_to_pl.get(event_id).unwrap();
(*pl, ev.origin_server_ts().clone(), ev.event_id().cloned())
})
}
fn lexicographical_topological_sort<F>(
&mut self,
graph: &BTreeMap<EventId, Vec<EventId>>,
key_fn: F,
) -> Vec<EventId>
where
F: Fn(&EventId) -> (i64, SystemTime, Option<EventId>),
{
// Note, this is basically Kahn's algorithm except we look at nodes with no
// outgoing edges, c.f.
// https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm
let outdegree_map = graph;
let mut reverse_graph = BTreeMap::new();
// Vec of nodes that have zero out degree.
let mut zero_outdegree = vec![];
for (node, edges) in graph.iter() {
if edges.is_empty() {
zero_outdegree.push((key_fn(node), node));
}
reverse_graph.insert(node, vec![]);
for edge in edges {
reverse_graph.entry(edge).or_insert(vec![]).push(node);
}
}
let mut heap = BinaryHeap::from(zero_outdegree);
while let Some((_, node)) = heap.pop() {
for parent in reverse_graph.get(node).unwrap() {
let out = outdegree_map.get(parent).unwrap();
if out.iter().filter(|id| *id == node).count() == 0 {
heap.push((key_fn(parent), parent));
}
}
}
heap.into_iter().map(|(_, id)| id).cloned().collect()
}
fn get_power_level_for_sender(
&self,
room_id: &RoomId,
event_id: &EventId,
event_map: &EventMap<StateEvent>,
store: &dyn StateStore,
) -> i64 {
let mut pl = None;
for aid in store.auth_event_ids(room_id, event_id).unwrap() {
if let Ok(aev) = store.get_event(&aid) {
if aev.is_type_and_key(EventType::RoomPowerLevels, "") {
pl = Some(aev);
break;
}
}
}
if pl.is_none() {
for aid in store.auth_event_ids(room_id, event_id).unwrap() {
if let Ok(aev) = store.get_event(&aid) {
if aev.is_type_and_key(EventType::RoomCreate, "") {
if let Ok(content) = aev
.deserialize_content::<ruma::events::room::create::CreateEventContent>()
{
if &content.creator == aev.sender() {
return 100;
}
break;
}
}
}
}
return 0;
}
if let Some(content) = pl
.map(|pl| {
pl.deserialize_content::<ruma::events::room::power_levels::PowerLevelsEventContent>(
)
.ok()
})
.flatten()
{
content.users_default.into()
} else {
0
}
}
fn iterative_auth_check(
@ -224,9 +365,41 @@ impl StateResolution {
unconflicted_state: &StateMap<EventId>,
event_map: &EventMap<StateEvent>,
store: &dyn StateStore,
) -> StateMap<EventId> {
) -> Result<StateMap<EventId>, String> {
tracing::debug!("starting iter auth check");
panic!()
let resolved_state = unconflicted_state.clone();
for (idx, event_id) in power_events.iter().enumerate() {
let event = store.get_event(event_id).unwrap();
let mut auth_events = BTreeMap::new();
for aid in store.auth_event_ids(room_id, event_id).unwrap() {
if let Ok(ev) = store.get_event(&aid) {
// TODO is None the same as "" for state_key, pretty sure it is NOT
auth_events.insert((ev.kind(), ev.state_key().unwrap_or_default()), ev);
} else {
tracing::warn!("auth event id for {} is missing {}", aid, event_id);
}
}
for key in event_auth::auth_types_for_event(&event) {
if let Some(ev_id) = resolved_state.get(&key) {
// TODO synapse gets the event from the store then checks its not None
// then pulls the same `ev_id` event from the event_map??
if let Ok(event) = store.get_event(ev_id) {
auth_events.insert(key.clone(), event);
}
}
}
if !event_auth::auth_check(room_version, &event, auth_events).ok_or("".to_string())? {}
// We yield occasionally when we're working with large data sets to
// ensure that we don't block the reactor loop for too long.
if idx % _YIELD_AFTER_ITERATIONS == 0 {
// yield clock.sleep(0)
}
}
Ok(resolved_state)
}
/// Returns the sorted `to_sort` list of `EventId`s based on a mainline sort using
@ -337,6 +510,33 @@ impl StateResolution {
// Did not find a power level event so we default to zero
0
}
fn add_event_and_auth_chain_to_graph(
&self,
room_id: &RoomId,
graph: &mut BTreeMap<EventId, Vec<EventId>>,
event_id: &EventId,
store: &dyn StateStore,
auth_diff: &[EventId],
) {
let mut state = vec![event_id.clone()];
while !state.is_empty() {
// we just checked if it was empty so unwrap is fine
let eid = state.pop().unwrap();
graph.insert(eid.clone(), vec![]);
for aid in store.auth_event_ids(room_id, &eid).unwrap() {
if auth_diff.contains(&aid) {
if !graph.contains_key(&aid) {
state.push(aid.clone());
}
// we just inserted this at the start of the while loop
graph.get_mut(&eid).unwrap().push(aid);
}
}
}
}
}
pub fn is_power_event(event_id: &EventId, store: &dyn StateStore) -> bool {

147
src/room_version.rs Normal file
View File

@ -0,0 +1,147 @@
use ruma::identifiers::RoomVersionId;
pub enum RoomDisposition {
/// A room version that has a stable specification.
Stable,
/// A room version that is not yet fully specified.
#[allow(dead_code)]
Unstable,
}
pub enum EventFormatVersion {
/// $id:server event id format
V1,
/// MSC1659-style $hash event id format: introduced for room v3
V2,
/// MSC1884-style $hash format: introduced for room v4
V3,
}
pub enum StateResolutionVersion {
/// State resolution for rooms at version 1.
V1,
/// State resolution for room at version 2 or later.
V2,
}
pub struct RoomVersion {
/// The version this room is set to.
pub version: RoomVersionId,
/// The stability of this room.
pub disposition: RoomDisposition,
/// The format of the EventId.
pub event_format: EventFormatVersion,
/// Which state resolution algorithm is used.
pub state_res: StateResolutionVersion,
/// not sure
pub enforce_key_validity: bool,
// bool: before MSC2261/MSC2432, m.room.aliases had special auth rules and redaction rules
pub special_case_aliases_auth: bool,
// Strictly enforce canonicaljson, do not allow:
// * Integers outside the range of [-2 ^ 53 + 1, 2 ^ 53 - 1]
// * Floats
// * NaN, Infinity, -Infinity
pub strict_canonicaljson: bool,
// bool: MSC2209: Check 'notifications' key while verifying
// m.room.power_levels auth rules.
pub limit_notifications_power_levels: bool,
}
impl RoomVersion {
pub fn new(version: &RoomVersionId) -> Self {
if version.is_version_1() {
Self::version_1()
} else if version.is_version_2() {
Self::version_2()
} else if version.is_version_3() {
Self::version_3()
} else if version.is_version_4() {
Self::version_4()
} else if version.is_version_5() {
Self::version_5()
} else if version.is_version_6() {
Self::version_6()
} else {
panic!("this crate needs to be updated with ruma")
}
}
fn version_1() -> Self {
Self {
version: RoomVersionId::version_1(),
disposition: RoomDisposition::Stable,
event_format: EventFormatVersion::V1,
state_res: StateResolutionVersion::V1,
enforce_key_validity: false,
special_case_aliases_auth: true,
strict_canonicaljson: false,
limit_notifications_power_levels: false,
}
}
fn version_2() -> Self {
Self {
version: RoomVersionId::version_2(),
disposition: RoomDisposition::Stable,
event_format: EventFormatVersion::V1,
state_res: StateResolutionVersion::V2,
enforce_key_validity: false,
special_case_aliases_auth: true,
strict_canonicaljson: false,
limit_notifications_power_levels: false,
}
}
fn version_3() -> Self {
Self {
version: RoomVersionId::version_3(),
disposition: RoomDisposition::Stable,
event_format: EventFormatVersion::V2,
state_res: StateResolutionVersion::V2,
enforce_key_validity: false,
special_case_aliases_auth: true,
strict_canonicaljson: false,
limit_notifications_power_levels: false,
}
}
fn version_4() -> Self {
Self {
version: RoomVersionId::version_4(),
disposition: RoomDisposition::Stable,
event_format: EventFormatVersion::V3,
state_res: StateResolutionVersion::V2,
enforce_key_validity: false,
special_case_aliases_auth: true,
strict_canonicaljson: false,
limit_notifications_power_levels: false,
}
}
fn version_5() -> Self {
Self {
version: RoomVersionId::version_5(),
disposition: RoomDisposition::Stable,
event_format: EventFormatVersion::V3,
state_res: StateResolutionVersion::V2,
enforce_key_validity: true,
special_case_aliases_auth: true,
strict_canonicaljson: false,
limit_notifications_power_levels: false,
}
}
fn version_6() -> Self {
Self {
version: RoomVersionId::version_6(),
disposition: RoomDisposition::Stable,
event_format: EventFormatVersion::V3,
state_res: StateResolutionVersion::V2,
enforce_key_validity: true,
special_case_aliases_auth: false,
strict_canonicaljson: true,
limit_notifications_power_levels: true,
}
}
}

View File

@ -1,15 +1,15 @@
use ruma::{
events::{
from_raw_json_value,
pdu::{Pdu, PduStub, RoomV1Pdu, RoomV1PduStub, RoomV3Pdu, RoomV3PduStub},
pdu::{Pdu, PduStub},
room::member::{MemberEventContent, MembershipState},
AnyStateEvent, AnyStrippedStateEvent, AnySyncStateEvent, EventDeHelper, EventType,
EventDeHelper, EventType,
},
identifiers::{EventId, RoomId},
identifiers::{EventId, RoomId, UserId},
};
use serde::{de, Serialize};
use serde_json::value::RawValue as RawJsonValue;
use std::{convert::TryFrom, time::SystemTime};
use std::time::SystemTime;
#[derive(Clone, Debug, Serialize)]
#[serde(untagged)]
@ -72,6 +72,20 @@ impl StateEvent {
},
}
}
pub fn deserialize_content<C: serde::de::DeserializeOwned>(
&self,
) -> Result<C, serde_json::Error> {
match self {
Self::Full(ev) => match ev {
Pdu::RoomV1Pdu(ev) => serde_json::from_value(ev.content.clone()),
Pdu::RoomV3Pdu(ev) => serde_json::from_value(ev.content.clone()),
},
Self::Sync(ev) => match ev {
PduStub::RoomV1PduStub(ev) => serde_json::from_value(ev.content.clone()),
PduStub::RoomV3PduStub(ev) => serde_json::from_value(ev.content.clone()),
},
}
}
pub fn origin_server_ts(&self) -> &SystemTime {
match self {
Self::Full(ev) => match ev {
@ -88,9 +102,35 @@ impl StateEvent {
match self {
Self::Full(ev) => match ev {
Pdu::RoomV1Pdu(ev) => Some(&ev.event_id),
Pdu::RoomV3Pdu(ev) => None,
Pdu::RoomV3Pdu(_) => None,
},
Self::Sync(_) => None,
}
}
pub fn sender(&self) -> &UserId {
match self {
Self::Full(ev) => match ev {
Pdu::RoomV1Pdu(ev) => &ev.sender,
Pdu::RoomV3Pdu(ev) => &ev.sender,
},
Self::Sync(ev) => match ev {
PduStub::RoomV1PduStub(ev) => &ev.sender,
PduStub::RoomV3PduStub(ev) => &ev.sender,
},
}
}
pub fn redacts(&self) -> Option<&EventId> {
match self {
Self::Full(ev) => match ev {
Pdu::RoomV1Pdu(ev) => ev.redacts.as_ref(),
Pdu::RoomV3Pdu(ev) => ev.redacts.as_ref(),
},
Self::Sync(ev) => match ev {
PduStub::RoomV1PduStub(ev) => ev.redacts.as_ref(),
PduStub::RoomV3PduStub(ev) => ev.redacts.as_ref(),
},
Self::Sync(ev) => None,
}
}
@ -100,12 +140,81 @@ impl StateEvent {
Pdu::RoomV1Pdu(ev) => Some(&ev.room_id),
Pdu::RoomV3Pdu(ev) => Some(&ev.room_id),
},
Self::Sync(ev) => None,
Self::Sync(_) => None,
}
}
pub fn kind(&self) -> EventType {
match self {
Self::Full(ev) => match ev {
Pdu::RoomV1Pdu(ev) => ev.kind.clone(),
Pdu::RoomV3Pdu(ev) => ev.kind.clone(),
},
Self::Sync(ev) => match ev {
PduStub::RoomV1PduStub(ev) => ev.kind.clone(),
PduStub::RoomV3PduStub(ev) => ev.kind.clone(),
},
}
}
pub fn state_key(&self) -> Option<String> {
match self {
Self::Full(ev) => match ev {
Pdu::RoomV1Pdu(ev) => ev.state_key.clone(),
Pdu::RoomV3Pdu(ev) => ev.state_key.clone(),
},
Self::Sync(ev) => match ev {
PduStub::RoomV1PduStub(ev) => ev.state_key.clone(),
PduStub::RoomV3PduStub(ev) => ev.state_key.clone(),
},
}
}
pub fn prev_event_ids(&self) -> Vec<EventId> {
match self {
Self::Full(ev) => match ev {
Pdu::RoomV1Pdu(ev) => ev.prev_events.iter().map(|(id, _)| id).cloned().collect(),
Pdu::RoomV3Pdu(ev) => ev.prev_events.clone(),
},
Self::Sync(ev) => match ev {
PduStub::RoomV1PduStub(ev) => {
ev.prev_events.iter().map(|(id, _)| id).cloned().collect()
}
PduStub::RoomV3PduStub(ev) => ev.prev_events.clone(),
},
}
}
pub fn content(&self) -> serde_json::Value {
match self {
Self::Full(ev) => match ev {
Pdu::RoomV1Pdu(ev) => ev.content.clone(),
Pdu::RoomV3Pdu(ev) => ev.content.clone(),
},
Self::Sync(ev) => match ev {
PduStub::RoomV1PduStub(ev) => ev.content.clone(),
PduStub::RoomV3PduStub(ev) => ev.content.clone(),
},
}
}
pub fn is_type_and_key(&self, ev_type: EventType, state_key: &str) -> bool {
true
match self {
Self::Full(ev) => match ev {
Pdu::RoomV1Pdu(ev) => {
ev.kind == ev_type && ev.state_key.as_deref() == Some(state_key)
}
Pdu::RoomV3Pdu(ev) => {
ev.kind == ev_type && ev.state_key.as_deref() == Some(state_key)
}
},
Self::Sync(ev) => match ev {
PduStub::RoomV1PduStub(ev) => {
ev.kind == ev_type && ev.state_key.as_deref() == Some(state_key)
}
PduStub::RoomV3PduStub(ev) => {
ev.kind == ev_type && ev.state_key.as_deref() == Some(state_key)
}
},
}
}
}
@ -116,14 +225,10 @@ impl<'de> de::Deserialize<'de> for StateEvent {
{
let json = Box::<RawJsonValue>::deserialize(deserializer)?;
let EventDeHelper {
state_key,
event_id,
room_id,
unsigned,
..
room_id, unsigned, ..
} = from_raw_json_value(&json)?;
// Determine whether the event is a full, sync, or stripped
// Determine whether the event is a full or sync
// based on the fields present.
if room_id.is_some() {
Ok(match unsigned {

View File

@ -1,13 +1,4 @@
use std::{collections::BTreeMap, time::SystemTime};
use petgraph::Graph;
use ruma::{
events::{
room::{self},
AnyStateEvent, AnyStrippedStateEvent, AnySyncStateEvent, EventType,
},
identifiers::{EventId, RoomId, RoomVersionId},
};
use ruma::identifiers::{EventId, RoomId, RoomVersionId};
use crate::StateEvent;
@ -21,6 +12,9 @@ pub trait StateStore {
/// Returns a Vec of the related auth events to the given `event`.
fn auth_event_ids(&self, room_id: &RoomId, event_id: &EventId) -> Result<Vec<EventId>, String>;
/// Returns a Vec<EventId> representing the difference in auth chains of the given `events`.
fn auth_chain_diff(&self, event_id: &[&EventId]) -> Result<Vec<EventId>, String>;
/// Returns a tuple of requested state events from `event_id` and the auth chain events that
/// relate to the.
fn get_remote_state_for_room(

View File

@ -155,6 +155,10 @@ impl StateStore for TestStore {
])
}
fn auth_chain_diff(&self, event_id: &[&EventId]) -> Result<Vec<EventId>, String> {
Ok(vec![])
}
fn get_remote_state_for_room(
&self,
room_id: &RoomId,
@ -176,11 +180,11 @@ fn it_works() {
let room_version = RoomVersionId::version_6();
let initial_state = btreemap! {
(EventType::RoomCreate, "".into()) => EventId::try_from("").unwrap(),
(EventType::RoomCreate, "".into()) => EventId::try_from("$aaa:example.org").unwrap(),
};
let state_to_resolve = btreemap! {
(EventType::RoomCreate, "".into()) => EventId::try_from("").unwrap(),
(EventType::RoomCreate, "".into()) => EventId::try_from("$bbb:example.org").unwrap(),
};
let mut resolver = StateResolution::default();