use std::collections::BTreeMap;
use crate::store::RoomKind;
pub type Claims = BTreeMap<String, String>;
pub fn claims_from(paid_flag: u8) -> Claims {
let mut c = Claims::new();
c.insert("flag".into(), paid_flag.to_string());
c
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Action {
CreateRoom,
Invite,
JoinRoom,
SendEvent,
UploadMedia,
}
#[derive(Debug, Clone)]
pub struct PolicyContext<'a> {
pub claims: &'a Claims,
pub action: Action,
pub room_kind: Option<RoomKind>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Decision {
Allow,
Deny(String),
}
pub trait PolicyHook: Send + Sync {
fn decide(&self, ctx: &PolicyContext<'_>) -> Decision;
}
pub struct AllowAll;
impl PolicyHook for AllowAll {
fn decide(&self, _ctx: &PolicyContext<'_>) -> Decision {
Decision::Allow
}
}
pub fn enforce(hook: &dyn PolicyHook, claims: &Claims, action: Action, room_kind: Option<RoomKind>) -> Result<(), crate::error::MatrixError> {
match hook.decide(&PolicyContext { claims, action, room_kind }) {
Decision::Allow => Ok(()),
Decision::Deny(why) => Err(crate::error::MatrixError::policy_denied(why)),
}
}
#[cfg(test)]
mod tests {
use super::*;
struct DenyFlaglessChannels;
impl PolicyHook for DenyFlaglessChannels {
fn decide(&self, c: &PolicyContext<'_>) -> Decision {
if c.action == Action::CreateRoom && c.room_kind == Some(RoomKind::Channel) && c.claims.get("flag").map(String::as_str) != Some("1") {
Decision::Deny("not available".into())
} else {
Decision::Allow
}
}
}
#[test]
fn default_allows_and_custom_hook_denies_with_policy_error() {
let c0 = claims_from(0);
assert!(enforce(&AllowAll, &c0, Action::CreateRoom, Some(RoomKind::Channel)).is_ok());
let e = enforce(&DenyFlaglessChannels, &c0, Action::CreateRoom, Some(RoomKind::Channel)).unwrap_err();
assert_eq!((e.status, e.errcode), (403, "M4A_POLICY_DENIED"));
assert!(enforce(&DenyFlaglessChannels, &c0, Action::CreateRoom, Some(RoomKind::Dm)).is_ok());
assert!(enforce(&DenyFlaglessChannels, &claims_from(1), Action::CreateRoom, Some(RoomKind::Channel)).is_ok());
}
}