From 17958665f6592af3ef478024fd1d75c384a30e7f Mon Sep 17 00:00:00 2001 From: Devin Ragotzy Date: Wed, 26 Aug 2020 20:51:39 -0400 Subject: [PATCH] Update docs in event_auth and add first few event_auth tests --- src/event_auth.rs | 5 - tests/event_auth.rs | 280 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 280 insertions(+), 5 deletions(-) diff --git a/src/event_auth.rs b/src/event_auth.rs index 56172480..e0fa6d0c 100644 --- a/src/event_auth.rs +++ b/src/event_auth.rs @@ -286,7 +286,6 @@ pub fn auth_check( Ok(true) } -// synapse has an `event: &StateEvent` param but it's never used /// Can this room federate based on its m.room.create event. pub fn can_federate(auth_events: &StateMap) -> bool { let creation_event = auth_events.get(&(EventType::RoomCreate, Some("".into()))); @@ -302,10 +301,6 @@ pub fn can_federate(auth_events: &StateMap) -> bool { } /// Does the user who sent this member event have required power levels to do so. -/// -/// If called on it's own the following must be true: -/// - there must be a valid state_key in `user` -/// - there must be a membership key in `user.content` i.e. the event is of type "m.room.member" pub fn is_membership_change_allowed( user: Requester<'_>, auth_events: &StateMap, diff --git a/tests/event_auth.rs b/tests/event_auth.rs index e69de29b..f9d0c752 100644 --- a/tests/event_auth.rs +++ b/tests/event_auth.rs @@ -0,0 +1,280 @@ +use std::{cell::RefCell, collections::BTreeMap, convert::TryFrom}; + +use ruma::{ + events::{ + pdu::EventHash, + room::{ + join_rules::JoinRule, + member::{MemberEventContent, MembershipState}, + }, + EventType, + }, + identifiers::{EventId, RoomId, RoomVersionId, UserId}, +}; +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, + }, + Requester, StateEvent, StateMap, StateStore, +}; +use tracing_subscriber as tracer; + +use std::sync::Once; + +static LOGGER: Once = Once::new(); + +static mut SERVER_TIMESTAMP: i32 = 0; + +fn event_id(id: &str) -> EventId { + if id.contains('$') { + return EventId::try_from(id).unwrap(); + } + EventId::try_from(format!("${}:foo", id)).unwrap() +} + +fn alice() -> UserId { + UserId::try_from("@alice:foo").unwrap() +} +fn bob() -> UserId { + UserId::try_from("@bob:foo").unwrap() +} +fn charlie() -> UserId { + UserId::try_from("@charlie:foo").unwrap() +} + +fn room_id() -> RoomId { + RoomId::try_from("!test:foo").unwrap() +} + +fn member_content_ban() -> JsonValue { + serde_json::to_value(MemberEventContent { + membership: MembershipState::Ban, + displayname: None, + avatar_url: None, + is_direct: None, + third_party_invite: None, + }) + .unwrap() +} + +fn member_content_join() -> JsonValue { + serde_json::to_value(MemberEventContent { + membership: MembershipState::Join, + displayname: None, + avatar_url: None, + is_direct: None, + third_party_invite: None, + }) + .unwrap() +} + +pub struct TestStore(RefCell>); + +#[allow(unused)] +impl StateStore for TestStore { + fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result { + self.0 + .borrow() + .get(event_id) + .cloned() + .ok_or(format!("{} not found", event_id.to_string())) + } +} + +fn to_pdu_event( + id: &str, + sender: UserId, + ev_type: EventType, + state_key: Option<&str>, + content: JsonValue, + auth_events: &[S], + prev_events: &[S], +) -> StateEvent +where + S: AsRef, +{ + let ts = unsafe { + let ts = SERVER_TIMESTAMP; + // increment the "origin_server_ts" value + SERVER_TIMESTAMP += 1; + ts + }; + let id = if id.contains('$') { + id.to_string() + } else { + format!("${}:foo", id) + }; + let auth_events = auth_events + .iter() + .map(AsRef::as_ref) + .map(event_id) + .map(|id| { + ( + id, + EventHash { + sha256: "hello".into(), + }, + ) + }) + .collect::>(); + let prev_events = prev_events + .iter() + .map(AsRef::as_ref) + .map(event_id) + .map(|id| { + ( + id, + EventHash { + sha256: "hello".into(), + }, + ) + }) + .collect::>(); + + let json = if let Some(state_key) = state_key { + json!({ + "auth_events": auth_events, + "prev_events": prev_events, + "event_id": id, + "sender": sender, + "type": ev_type, + "state_key": state_key, + "content": content, + "origin_server_ts": ts, + "room_id": room_id(), + "origin": "foo", + "depth": 0, + "hashes": { "sha256": "hello" }, + "signatures": {}, + }) + } else { + json!({ + "auth_events": auth_events, + "prev_events": prev_events, + "event_id": id, + "sender": sender, + "type": ev_type, + "content": content, + "origin_server_ts": ts, + "room_id": room_id(), + "origin": "foo", + "depth": 0, + "hashes": { "sha256": "hello" }, + "signatures": {}, + }) + }; + serde_json::from_value(json).unwrap() +} + +// all graphs start with these input events +#[allow(non_snake_case)] +fn INITIAL_EVENTS() -> BTreeMap { + // this is always called so we can init the logger here + let _ = LOGGER.call_once(|| { + tracer::fmt() + .with_env_filter(tracer::EnvFilter::from_default_env()) + .init() + }); + + vec![ + to_pdu_event::( + "CREATE", + alice(), + EventType::RoomCreate, + Some(""), + json!({ "creator": alice() }), + &[], + &[], + ), + to_pdu_event( + "IMA", + alice(), + EventType::RoomMember, + Some(alice().to_string().as_str()), + member_content_join(), + &["CREATE"], + &["CREATE"], + ), + to_pdu_event( + "IPOWER", + alice(), + EventType::RoomPowerLevels, + Some(""), + json!({"users": {alice().to_string(): 100}}), + &["CREATE", "IMA"], + &["IMA"], + ), + to_pdu_event( + "IJR", + alice(), + EventType::RoomJoinRules, + Some(""), + json!({ "join_rule": JoinRule::Public }), + &["CREATE", "IMA", "IPOWER"], + &["IPOWER"], + ), + to_pdu_event( + "IMB", + bob(), + EventType::RoomMember, + Some(bob().to_string().as_str()), + member_content_join(), + &["CREATE", "IJR", "IPOWER"], + &["IJR"], + ), + to_pdu_event( + "IMC", + charlie(), + EventType::RoomMember, + Some(charlie().to_string().as_str()), + member_content_join(), + &["CREATE", "IJR", "IPOWER"], + &["IMB"], + ), + ] + .into_iter() + .map(|ev| (ev.event_id(), ev)) + .collect() +} + +#[test] +fn test_ban_pass() { + let events = INITIAL_EVENTS(); + + let auth_events = events + .values() + .map(|ev| ((ev.kind(), ev.state_key()), ev.clone())) + .collect::>(); + + let requester = Requester { + prev_event_ids: vec![event_id("IMC")], + room_id: &room_id(), + content: &member_content_ban(), + state_key: Some(charlie().to_string()), + sender: &alice(), + }; + + assert!(is_membership_change_allowed(requester, &auth_events).unwrap()) +} + +#[test] +fn test_ban_fail() { + let events = INITIAL_EVENTS(); + + let auth_events = events + .values() + .map(|ev| ((ev.kind(), ev.state_key()), ev.clone())) + .collect::>(); + + let requester = Requester { + prev_event_ids: vec![event_id("IMC")], + room_id: &room_id(), + content: &member_content_ban(), + state_key: Some(alice().to_string()), + sender: &charlie(), + }; + + assert!(!is_membership_change_allowed(requester, &auth_events).unwrap()) +}