use ruma_common::ServerName;
use ruma_macros::EventContent;
use serde::{Deserialize, Serialize};
use wildmatch::WildMatch;
use crate::EmptyStateKey;
#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[ruma_event(type = "m.room.server_acl", kind = State, state_key_type = EmptyStateKey)]
pub struct RoomServerAclEventContent {
#[serde(
default = "ruma_common::serde::default_true",
skip_serializing_if = "ruma_common::serde::is_true"
)]
pub allow_ip_literals: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub allow: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub deny: Vec<String>,
}
impl RoomServerAclEventContent {
pub fn new(allow_ip_literals: bool, allow: Vec<String>, deny: Vec<String>) -> Self {
Self { allow_ip_literals, allow, deny }
}
pub fn is_allowed(&self, server_name: &ServerName) -> bool {
if !self.allow_ip_literals && server_name.is_ip_literal() {
return false;
}
let host = server_name.host();
self.deny.iter().all(|d| !WildMatch::new(d).matches(host))
&& self.allow.iter().any(|a| WildMatch::new(a).matches(host))
}
}
#[cfg(test)]
mod tests {
use ruma_common::server_name;
use serde_json::{from_value as from_json_value, json};
use super::RoomServerAclEventContent;
use crate::OriginalStateEvent;
#[test]
fn default_values() {
let json_data = json!({
"content": {},
"event_id": "$h29iv0s8:example.com",
"origin_server_ts": 1,
"room_id": "!n8f893n9:example.com",
"sender": "@carl:example.com",
"state_key": "",
"type": "m.room.server_acl"
});
let server_acl_event: OriginalStateEvent<RoomServerAclEventContent> =
from_json_value(json_data).unwrap();
assert!(server_acl_event.content.allow_ip_literals);
assert_eq!(server_acl_event.content.allow.len(), 0);
assert_eq!(server_acl_event.content.deny.len(), 0);
}
#[test]
fn acl_ignores_port() {
let acl_event = RoomServerAclEventContent {
allow_ip_literals: true,
allow: vec!["*".to_owned()],
deny: vec!["1.1.1.1".to_owned()],
};
assert!(!acl_event.is_allowed(server_name!("1.1.1.1:8000")));
}
#[test]
fn acl_allow_ip_literal() {
let acl_event = RoomServerAclEventContent {
allow_ip_literals: true,
allow: vec!["*".to_owned()],
deny: Vec::new(),
};
assert!(acl_event.is_allowed(server_name!("1.1.1.1")));
}
#[test]
fn acl_deny_ip_literal() {
let acl_event = RoomServerAclEventContent {
allow_ip_literals: false,
allow: vec!["*".to_owned()],
deny: Vec::new(),
};
assert!(!acl_event.is_allowed(server_name!("1.1.1.1")));
}
#[test]
fn acl_deny() {
let acl_event = RoomServerAclEventContent {
allow_ip_literals: false,
allow: vec!["*".to_owned()],
deny: vec!["matrix.org".to_owned()],
};
assert!(!acl_event.is_allowed(server_name!("matrix.org")));
assert!(acl_event.is_allowed(server_name!("conduit.rs")));
}
#[test]
fn acl_explicit_allow() {
let acl_event = RoomServerAclEventContent {
allow_ip_literals: false,
allow: vec!["conduit.rs".to_owned()],
deny: Vec::new(),
};
assert!(!acl_event.is_allowed(server_name!("matrix.org")));
assert!(acl_event.is_allowed(server_name!("conduit.rs")));
}
#[test]
fn acl_explicit_glob_1() {
let acl_event = RoomServerAclEventContent {
allow_ip_literals: false,
allow: vec!["*.matrix.org".to_owned()],
deny: Vec::new(),
};
assert!(!acl_event.is_allowed(server_name!("matrix.org")));
assert!(acl_event.is_allowed(server_name!("server.matrix.org")));
}
#[test]
fn acl_explicit_glob_2() {
let acl_event = RoomServerAclEventContent {
allow_ip_literals: false,
allow: vec!["matrix??.org".to_owned()],
deny: Vec::new(),
};
assert!(!acl_event.is_allowed(server_name!("matrix1.org")));
assert!(acl_event.is_allowed(server_name!("matrix02.org")));
}
#[test]
fn acl_ipv6_glob() {
let acl_event = RoomServerAclEventContent {
allow_ip_literals: true,
allow: vec!["[2001:db8:1234::1]".to_owned()],
deny: Vec::new(),
};
assert!(!acl_event.is_allowed(server_name!("[2001:db8:1234::2]")));
assert!(acl_event.is_allowed(server_name!("[2001:db8:1234::1]")));
}
}