refactor: Ruma upstreaming, half-baked edition

Co-authored-by: Jade Ellis <jade@ellis.link>
This commit is contained in:
Ginger
2026-03-29 12:25:42 -04:00
parent 1cc9dbf2a4
commit 204bc1367e
141 changed files with 2715 additions and 2279 deletions
+31
View File
@@ -0,0 +1,31 @@
[package]
name = "ruminuwuity"
description.workspace = true
edition.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
version.workspace = true
[lib]
path = "mod.rs"
[features]
default = ["client", "server"]
client = []
server = []
unstable-exhaustive-types = []
unstable-msc3202 = []
unstable-msc4203 = []
[dependencies]
ruma.workspace = true
serde = { workspace = true }
serde_json = { workspace = true }
wildmatch = "2.6.1"
[dev-dependencies]
[lints]
workspace = true
@@ -0,0 +1 @@
pub mod rooms;
@@ -0,0 +1,50 @@
pub mod v1 {
use ruma::{OwnedRoomAliasId, OwnedRoomId, OwnedUserId, api::{auth_scheme::AccessToken, request, response}, metadata};
metadata! {
method: PUT,
rate_limited: false,
authentication: AccessToken,
history: {
1.0 => "/_continuwuity/admin/rooms/{room_id}/ban",
}
}
#[request]
pub struct Request {
#[ruma_api(path)]
pub room_id: OwnedRoomId,
/// Whether to ban (true) or unban (false) the room.
/// If true, and the room is not banned, all local users will be evacuated
/// and prevented from re-joining.
/// If false, and the room is unbanned, local users will be allowed to re-join.
/// No-ops are no-ops.
pub banned: bool,
}
#[response]
pub struct Response {
pub kicked_users: Vec<OwnedUserId>,
pub failed_kicked_users: Vec<OwnedUserId>,
pub local_aliases: Vec<OwnedRoomAliasId>
}
impl Request {
#[must_use]
pub fn new(room_id: OwnedRoomId, banned: bool) -> Self {
Self { room_id, banned }
}
}
impl Response {
#[must_use]
pub fn new(
kicked_users: Vec<OwnedUserId>,
failed_kicked_users: Vec<OwnedUserId>,
local_aliases: Vec<OwnedRoomAliasId>,
) -> Self {
Self { kicked_users, failed_kicked_users, local_aliases }
}
}
}
@@ -0,0 +1,37 @@
pub mod v1 {
use ruma::{
OwnedRoomId, api::{auth_scheme::AccessToken, request, response}, metadata
};
metadata! {
method: GET,
rate_limited: false,
authentication: AccessToken,
history: {
1.0 => "/_continuwuity/admin/rooms/list",
}
}
#[request]
#[derive(Default)]
pub struct Request;
#[response]
pub struct Response {
pub rooms: Vec<OwnedRoomId>,
}
impl Request {
#[must_use]
pub fn new() -> Self {
Self::default()
}
}
impl Response {
#[must_use]
pub fn new(rooms: Vec<OwnedRoomId>) -> Self {
Self { rooms }
}
}
}
@@ -0,0 +1,2 @@
pub mod list;
pub mod ban;
+53
View File
@@ -0,0 +1,53 @@
//! `GET /_matrix/client/v1/admin/suspend/{userId}`
//!
//! Check the suspension status of a target user
pub mod v1 {
//! `/_matrix/client/unstable/uk.timedout.msc4323/admin/suspend/{userID}` ([msc])
//!
//! [msc]: https://github.com/matrix-org/matrix-spec-proposals/pull/4323
use ruma::{
OwnedUserId, api::{auth_scheme::AccessToken, request, response}, metadata
};
metadata! {
method: GET,
rate_limited: false,
authentication: AccessToken,
history: {
unstable => "/_matrix/client/unstable/uk.timedout.msc4323/admin/suspend/{user_id}",
}
}
/// Request type for the get & set user suspension status endpoint.
#[request(error = ruma::api::client::Error)]
pub struct Request {
/// The user to look up.
#[ruma_api(path)]
pub user_id: OwnedUserId,
}
/// Response type for the suspension endpoints
#[response(error = ruma::api::client::Error)]
pub struct Response {
/// Whether the user is currently suspended.
pub suspended: bool,
}
impl Request {
/// Creates a new `Request` with the given user id.
#[must_use]
pub fn new(user_id: OwnedUserId) -> Self {
Self { user_id }
}
}
impl Response {
/// Creates a new `Response` with the given suspension status.
#[must_use]
pub fn new(suspended: bool) -> Self {
Self { suspended }
}
}
}
+3
View File
@@ -0,0 +1,3 @@
pub mod continuwuity;
pub mod get_suspended;
pub mod set_suspended;
+55
View File
@@ -0,0 +1,55 @@
//! `PUT /_matrix/client/v1/admin/suspend/{userId}`
//!
//! Set the suspension status of a target user
pub mod v1 {
//! `/_matrix/client/unstable/uk.timedout.msc4323/admin/suspend/{userID}` ([msc])
//!
//! [msc]: https://github.com/matrix-org/matrix-spec-proposals/pull/4323
use ruma::{
OwnedUserId, api::{auth_scheme::AccessToken, request, response}, metadata
};
metadata! {
method: PUT,
rate_limited: false,
authentication: AccessToken,
history: {
unstable => "/_matrix/client/unstable/uk.timedout.msc4323/admin/suspend/{user_id}",
}
}
/// Request type for the set user suspension status endpoint.
#[request(error = ruma::api::client::Error)]
pub struct Request {
/// The user to look up.
#[ruma_api(path)]
pub user_id: OwnedUserId,
pub suspended: bool,
}
/// Response type for the suspension endpoints
#[response(error = ruma::api::client::Error)]
pub struct Response {
/// Whether the user is currently suspended.
pub suspended: bool,
}
impl Request {
/// Creates a new `Request` with the given user id.
#[must_use]
pub fn new(user_id: OwnedUserId, suspended: bool) -> Self {
Self { user_id, suspended }
}
}
impl Response {
/// Creates a new `Response` with the given suspension status.
#[must_use]
pub fn new(suspended: bool) -> Self {
Self { suspended }
}
}
}
+2
View File
@@ -0,0 +1,2 @@
pub mod user_may_invite;
pub mod user_may_join_room;
@@ -0,0 +1,50 @@
//! `POST /api/1/spam_check/user_may_invite`
//!
//! Checks that a user may invite the given user to the given room via Draupnir anti-spam
pub mod v1 {
use ruma::{
OwnedRoomId, OwnedUserId, api::{auth_scheme::AppserviceToken, request, response}, metadata
};
metadata! {
method: POST,
rate_limited: false,
authentication: AppserviceToken,
history: {
1.0 => "/api/1/spam_check/user_may_invite",
}
}
/// Request type for the `user_may_invite` callback.
#[request]
pub struct Request {
/// The room the invitee is being invited to
pub room_id: OwnedRoomId,
/// The user sending the invite
pub inviter: OwnedUserId,
/// The user being invited
pub invitee: OwnedUserId,
}
/// Response type for the `user_may_invite` callback.
#[response]
#[derive(Default)]
pub struct Response;
impl Request {
/// Creates a new empty `Request`.
#[must_use]
pub fn new(room_id: OwnedRoomId, inviter: OwnedUserId, invitee: OwnedUserId) -> Self {
Self { room_id, inviter, invitee }
}
}
impl Response {
/// Creates a new empty `Response`.
#[must_use]
pub fn new() -> Self {
Self::default()
}
}
}
@@ -0,0 +1,50 @@
//! `POST /api/1/spam_check/user_may_join_room`
//!
//! Endpoint that checks whether a user may join a given room via Draupnir anti-spam
pub mod v1 {
use ruma::{
OwnedRoomId, OwnedUserId, api::{auth_scheme::AppserviceToken, request, response}, metadata
};
metadata! {
method: POST,
rate_limited: false,
authentication: AppserviceToken,
history: {
1.0 => "/api/1/spam_check/user_may_join_room",
}
}
/// Request type for the `user_may_join_room` callback.
#[request]
pub struct Request {
/// The user trying to join a room
pub user: OwnedUserId,
/// The room the user is trying to join
pub room: OwnedRoomId,
/// Whether the user was invited to this room
pub is_invited: bool,
}
/// Response type for the `user_may_join_room` callback.
#[response]
#[derive(Default)]
pub struct Response;
impl Request {
/// Creates a new empty `Request`.
#[must_use]
pub fn new(user: OwnedUserId, room: OwnedRoomId, is_invited: bool) -> Self {
Self { user, room, is_invited }
}
}
impl Response {
/// Creates a new empty `Response`.
#[must_use]
pub fn new() -> Self {
Self::default()
}
}
}
+247
View File
@@ -0,0 +1,247 @@
//! Types for invite filtering ([MSC4155]).
//!
//! MSC4155: https://github.com/matrix-org/matrix-spec-proposals/pull/4155
use ruma::{ServerName, UserId};
use ruma::exports::ruma_macros::EventContent;
use serde::{Deserialize, Serialize};
use wildmatch::WildMatch;
/// Represents a user's level of filtering on actions from another user or server.
/// "Ignore" and "block" are defined in [MSC4283].
///
/// MSC4283: https://github.com/matrix-org/matrix-spec-proposals/pull/4283
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FilterLevel {
Allow,
Ignore,
Block,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, EventContent)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[ruma_event(type = "m.invite_permission_config", kind = GlobalAccountData)]
pub struct InvitePermissionConfigEventContent {
/// A global on/off toggle for all rules
#[serde(default = "ruma::serde::default_true")]
pub enabled: bool,
/// A list of globs matching users which are allowed to send an invite.
/// Entries in this list supersede entries in the ignored and blocked lists.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub allowed_users: Vec<String>,
/// A list of globs matching users whose invites should be ignored (as defined in [MSC4283]).
///
/// MSC4283: https://github.com/matrix-org/matrix-spec-proposals/pull/4283
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub ignored_users: Vec<String>,
/// A list of globs matching users whose invites should be blocked (as defined in [MSC4283]).
/// Invites from blocked users should be refused with the M_INVITE_BLOCKED status code.
///
/// MSC4283: https://github.com/matrix-org/matrix-spec-proposals/pull/4283
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub blocked_users: Vec<String>,
/// A list of globs matching servers which are allowed to send an invite.
/// Entries in this list supersede entries in the ignored and blocked lists.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub allowed_servers: Vec<String>,
/// A list of globs matching servers whose invites should be ignored (as defined in [MSC4283]).
///
/// MSC4283: https://github.com/matrix-org/matrix-spec-proposals/pull/4283
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub ignored_servers: Vec<String>,
/// A list of globs matching servers whose invites should be blocked (as defined in [MSC4283]).
/// Invites from blocked servers should be refused with the M_INVITE_BLOCKED status code.
///
/// MSC4283: https://github.com/matrix-org/matrix-spec-proposals/pull/4283
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub blocked_servers: Vec<String>,
}
impl InvitePermissionConfigEventContent {
/// Creates a new `InvitePermissionConfigEventContent` from six lists of globs.
#[must_use]
pub fn new(
enabled: bool,
allowed_users: Vec<String>,
ignored_users: Vec<String>,
blocked_users: Vec<String>,
allowed_servers: Vec<String>,
ignored_servers: Vec<String>,
blocked_servers: Vec<String>,
) -> Self {
Self {
enabled,
allowed_users,
ignored_users,
blocked_users,
allowed_servers,
ignored_servers,
blocked_servers,
}
}
/// Test the filters against a user id. This function will check both the
/// user rules _and_ the server rules.
#[must_use]
#[allow(clippy::if_same_then_else)]
pub fn user_filter_level(&self, user: &UserId) -> FilterLevel {
if !self.enabled {
FilterLevel::Allow
} else if Self::matches(&self.allowed_users, user.as_str()) {
FilterLevel::Allow
} else if Self::matches(&self.ignored_users, user.as_str()) {
FilterLevel::Ignore
} else if Self::matches(&self.blocked_users, user.as_str()) {
FilterLevel::Block
} else {
self.server_filter_level(user.server_name())
}
}
/// Test the filters against a server name. Port numbers are ignored.
#[must_use]
pub fn server_filter_level(&self, server: &ServerName) -> FilterLevel {
if !self.enabled {
FilterLevel::Allow
} else {
let server = server.host();
if Self::matches(&self.allowed_servers, server) {
FilterLevel::Allow
} else if Self::matches(&self.ignored_servers, server) {
FilterLevel::Ignore
} else if Self::matches(&self.blocked_servers, server) {
FilterLevel::Block
} else {
FilterLevel::Allow
}
}
}
fn matches(a: &[String], s: &str) -> bool {
a.iter().map(String::as_str).any(|a| WildMatch::new(a).matches(s))
}
}
#[cfg(test)]
mod tests {
use ruma::{ServerName, UserId, events::GlobalAccountDataEvent};
use serde_json::{from_value as from_json_value, json};
use crate::invite_permission_config::{FilterLevel, InvitePermissionConfigEventContent};
fn user_id(id: &str) -> &UserId {
<&UserId>::try_from(id).unwrap()
}
fn server_name(name: &str) -> &ServerName {
<&ServerName>::try_from(name).unwrap()
}
#[test]
fn default_values() {
let data = json!({
"content": {},
"type": "org.matrix.msc4155.invite_permission_config"
});
let event: GlobalAccountDataEvent<InvitePermissionConfigEventContent> = from_json_value(data).unwrap();
assert!(event.content.enabled);
assert!(event.content.allowed_users.is_empty());
assert!(event.content.ignored_users.is_empty());
assert!(event.content.blocked_users.is_empty());
assert!(event.content.allowed_servers.is_empty());
assert!(event.content.ignored_servers.is_empty());
assert!(event.content.blocked_servers.is_empty());
assert_eq!(event.content.user_filter_level(user_id("@alice:example.com")), FilterLevel::Allow);
assert_eq!(event.content.server_filter_level(server_name("example.com")), FilterLevel::Allow);
}
#[test]
fn block_the_world() {
let event = InvitePermissionConfigEventContent {
enabled: true,
blocked_servers: vec!["*".to_owned()],
..Default::default()
};
assert_eq!(event.user_filter_level(user_id("@alice:foo.com:8080")), FilterLevel::Block);
assert_eq!(event.user_filter_level(user_id("@bob:bar.com")), FilterLevel::Block);
}
#[test]
fn only_goodguys() {
let event = InvitePermissionConfigEventContent {
enabled: true,
allowed_servers: vec!["goodguys.org".to_owned()],
blocked_servers: vec!["*".to_owned()],
..Default::default()
};
assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org:8080")), FilterLevel::Allow);
assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org")), FilterLevel::Allow);
assert_eq!(event.user_filter_level(user_id("@bob:bar.com")), FilterLevel::Block);
}
#[test]
fn exclude_badguys() {
let event = InvitePermissionConfigEventContent {
enabled: true,
blocked_servers: vec!["badguys.org".to_owned()],
..Default::default()
};
assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org")), FilterLevel::Allow);
assert_eq!(event.user_filter_level(user_id("@bob:bar.com")), FilterLevel::Allow);
assert_eq!(event.user_filter_level(user_id("@kevin:badguys.org:8080")), FilterLevel::Block);
assert_eq!(event.user_filter_level(user_id("@kevin:badguys.org")), FilterLevel::Block);
}
#[test]
fn only_goodguys_except_for_kevin() {
let event = InvitePermissionConfigEventContent {
enabled: true,
blocked_users: vec!["@kevin:goodguys.org".to_owned()],
allowed_servers: vec!["goodguys.org".to_owned()],
blocked_servers: vec!["*".to_owned()],
..Default::default()
};
assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org")), FilterLevel::Allow);
assert_eq!(event.user_filter_level(user_id("@kevin:goodguys.org")), FilterLevel::Block);
assert_eq!(event.user_filter_level(user_id("@kevin:badguys.org")), FilterLevel::Block);
}
#[test]
fn no_badguys_except_for_alice() {
let event = InvitePermissionConfigEventContent {
enabled: true,
allowed_users: vec!["@alice:badguys.org".to_owned()],
blocked_servers: vec!["badguys.org".to_owned()],
..Default::default()
};
assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org")), FilterLevel::Allow);
assert_eq!(event.user_filter_level(user_id("@alice:badguys.org")), FilterLevel::Allow);
assert_eq!(event.user_filter_level(user_id("@bob:bar.com")), FilterLevel::Allow);
assert_eq!(event.user_filter_level(user_id("@kevin:badguys.org")), FilterLevel::Block);
}
#[test]
fn only_goodguys_and_ignore_reallybadguys() {
let event = InvitePermissionConfigEventContent {
enabled: true,
allowed_servers: vec!["goodguys.org".to_owned()],
ignored_servers: vec!["reallybadguys.org".to_owned()],
blocked_servers: vec!["*".to_owned()],
..Default::default()
};
assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org:8080")), FilterLevel::Allow);
assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org")), FilterLevel::Allow);
assert_eq!(event.user_filter_level(user_id("@bob:bar.com")), FilterLevel::Block);
assert_eq!(event.user_filter_level(user_id("@kevin:reallybadguys.org")), FilterLevel::Ignore);
}
}
@@ -0,0 +1,59 @@
//! `POST /_meowlnir/antispam/*/accept_make_join`
//!
//! Endpoint to accept or decline incoming make_join federation requests.
//! Used by the `fi.mau.spam_check` restricted join rule.
//!
//! References:
//! - https://mau.dev/maunium/synapse/-/blob/52741d3/synapse/handlers/event_auth.py#L280-292
pub mod v1 {
use ruma::{
OwnedRoomId, OwnedUserId, api::{auth_scheme::AppserviceToken, request, response}, metadata
};
metadata! {
method: POST,
rate_limited: false,
authentication: AppserviceToken,
history: {
1.0 => "/_meowlnir/antispam/{management_room_id}/accept_make_join",
}
}
/// Request type for the `accept_make_join` callback.
#[request]
pub struct Request {
/// The relevant management room
#[ruma_api(path)]
pub management_room_id: OwnedRoomId,
/// The user trying to join a room
pub user: OwnedUserId,
/// The room the user is trying to join
pub room: OwnedRoomId,
}
/// Response type for the `accept_make_join` callback.
#[response]
#[derive(Default)]
pub struct Response;
impl Request {
/// Creates a new empty `Request`.
#[must_use]
pub fn new(
management_room_id: OwnedRoomId,
user: OwnedUserId,
room: OwnedRoomId,
) -> Self {
Self { management_room_id, user, room }
}
}
impl Response {
/// Creates a new empty `Response`.
#[must_use]
pub fn new() -> Self {
Self::default()
}
}
}
+4
View File
@@ -0,0 +1,4 @@
pub mod user_may_invite;
pub mod user_may_join_room;
pub mod accept_make_join;
@@ -0,0 +1,58 @@
//! `POST /_meowlnir/antispam/*/user_may_invite`
//!
//! Checks that a user may invite the given user to the given room via Meowlnir anti-spam
pub mod v1 {
use ruma::{
OwnedRoomId, OwnedUserId, api::{auth_scheme::AppserviceToken, request, response}, metadata
};
metadata! {
method: POST,
rate_limited: false,
authentication: AppserviceToken,
history: {
1.0 => "/_meowlnir/antispam/{management_room_id}/user_may_invite",
}
}
/// Request type for the `user_may_invite` callback.
#[request]
pub struct Request {
/// The relevant management room
#[ruma_api(path)]
pub management_room_id: OwnedRoomId,
/// The user sending the invite
pub inviter: OwnedUserId,
/// The user being invited
pub invitee: OwnedUserId,
/// The room the invitee is being invited to
pub room_id: OwnedRoomId,
}
/// Response type for the `user_may_invite` callback.
#[response]
#[derive(Default)]
pub struct Response;
impl Request {
/// Creates a new empty `Request`.
#[must_use]
pub fn new(
management_room_id: OwnedRoomId,
inviter: OwnedUserId,
invitee: OwnedUserId,
room_id: OwnedRoomId,
) -> Self {
Self { management_room_id, inviter, invitee, room_id }
}
}
impl Response {
/// Creates a new empty `Response`.
#[must_use]
pub fn new() -> Self {
Self::default()
}
}
}
@@ -0,0 +1,58 @@
//! `POST /_meowlnir/antispam/*/user_may_join_room`
//!
//! Endpoint to track invite joins via Meowlnir anti-spam
pub mod v1 {
use ruma::{
OwnedRoomId, OwnedUserId, api::{auth_scheme::AppserviceToken, request, response}, metadata
};
metadata! {
method: POST,
rate_limited: false,
authentication: AppserviceToken,
history: {
1.0 => "/_meowlnir/antispam/{management_room_id}/user_may_join_room",
}
}
/// Request type for the `user_may_join_room` callback.
#[request]
pub struct Request {
/// The relevant management room
#[ruma_api(path)]
pub management_room_id: OwnedRoomId,
/// The user trying to join a room
pub user: OwnedUserId,
/// The room the user is trying to join
pub room: OwnedRoomId,
/// Whether the user was invited to this room
pub is_invited: bool,
}
/// Response type for the `user_may_join_room` callback.
#[response]
#[derive(Default)]
pub struct Response;
impl Request {
/// Creates a new empty `Request`.
#[must_use]
pub fn new(
management_room_id: OwnedRoomId,
user: OwnedUserId,
room: OwnedRoomId,
is_invited: bool,
) -> Self {
Self { management_room_id, user, room, is_invited }
}
}
impl Response {
/// Creates a new empty `Response`.
#[must_use]
pub fn new() -> Self {
Self::default()
}
}
}
+7
View File
@@ -0,0 +1,7 @@
//! Ruminuwuity: Continuwuity-specific APIs and structs that depend only on Ruma
pub mod admin;
pub mod draupnir_antispam;
pub mod meowlnir_antispam;
pub mod policy;
pub mod invite_permission_config;
+100
View File
@@ -0,0 +1,100 @@
//! Types for the [`org.matrix.msc4284.policy`] event.
//!
//! [`org.matrix.msc4284.policy`]: https://github.com/matrix-org/matrix-spec-proposals/pull/4284
use ruma::exports::ruma_macros::EventContent;
use serde::{Deserialize, Serialize};
use ruma::events::EmptyStateKey;
#[derive(Clone, Debug, Deserialize, Serialize, EventContent, Default)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[ruma_event(type = "org.matrix.msc4284.policy", kind = State, state_key_type = EmptyStateKey)]
pub struct RoomPolicyEventContent {
/// The server name of the room's policy server.
///
/// If the value is empty or unreachable, the policy server should be ignored.
pub via: Option<String>,
/// The public key this policy server will sign with.
pub public_key: Option<String>
}
impl RoomPolicyEventContent {
/// Create an empty `RoomPolicyEventContent`.
#[must_use]
pub fn new() -> Self {
Self::default()
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct PolicyServerResponseContent {
/// The policy server's verdict. Either `ok` or `spam`.
pub recommendation: String,
}
impl PolicyServerResponseContent {
/// Create a new `PolicyServerResponseContent` with the given recommendation.
#[must_use]
pub fn new(recommendation: String) -> Self {
Self { recommendation }
}
}
impl From<String> for PolicyServerResponseContent {
fn from(recommendation: String) -> Self {
Self::new(recommendation)
}
}
#[cfg(test)]
mod tests {
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
use super::RoomPolicyEventContent;
use ruma::events::OriginalStateEvent;
#[test]
fn serialization() {
let content = RoomPolicyEventContent {
via: Some("example.com".to_owned()),
public_key: Some("6yhHGKhCiXTSEN2ksjV7kX_N6rBQZ3Xb-M7LlC6NS-s".to_owned())
};
let actual = to_json_value(content).unwrap();
let expected = json!({
"via": "example.com",
"public_key": "6yhHGKhCiXTSEN2ksjV7kX_N6rBQZ3Xb-M7LlC6NS-s"
});
assert_eq!(actual, expected);
}
#[test]
fn deserialization() {
let json_data = json!({
"content": {
"via": "example.com",
"public_key": "6yhHGKhCiXTSEN2ksjV7kX_N6rBQZ3Xb-M7LlC6NS-s"
},
"event_id": "123:example.com",
"origin_server_ts": 1,
"room_id": "!123456:example.com",
"sender": "@carl:example.com",
"state_key": "",
"type": "org.matrix.msc4284.policy"
});
let content = from_json_value::<OriginalStateEvent<RoomPolicyEventContent>>(json_data)
.unwrap()
.content;
assert_eq!(
content.via,
Some("example.com".to_owned())
);
assert_eq!(
content.public_key,
Some("6yhHGKhCiXTSEN2ksjV7kX_N6rBQZ3Xb-M7LlC6NS-s".to_owned())
);
}
}
+4
View File
@@ -0,0 +1,4 @@
pub mod policy_check;
pub mod policy_sign;
pub mod report_content;
pub mod event;
+60
View File
@@ -0,0 +1,60 @@
//! `POST /_matrix/policy/unstable/org.matrix.msc4284/event/{eventId}/check`
//!
//! Checks if an event is allowed by the room's policy server.
//! This is now a fallback behaviour that will be removed later.
pub mod unstable {
//! `/policy/unstable/org.matrix.msc4284` ([spec])
//!
//! [spec]: https://github.com/matrix-org/matrix-spec-proposals/pull/4284
use ruma::{
OwnedEventId, api::{federation::authentication::ServerSignatures, request, response}, metadata
};
use serde_json::value::RawValue as RawJsonValue;
metadata! {
method: POST,
rate_limited: false,
authentication: ServerSignatures,
history: {
unstable => "/_matrix/policy/unstable/org.matrix.msc4284/event/{event_id}/check",
}
}
/// Response type for the `check` endpoint.
#[response]
pub struct Response {
/// Either `ok` or `spam`, indicating the policy server's recommendation.
pub recommendation: String,
}
impl Response {
/// Creates a new `Response` with the given recommendation.
#[must_use]
pub fn new(recommendation: String) -> Self {
Self { recommendation }
}
}
/// Request type for the `check` endpoint.
#[request]
pub struct Request {
/// The event ID to check.
#[ruma_api(path)]
pub event_id: OwnedEventId,
/// The PDU body (optional)
#[ruma_api(body)]
#[serde(skip_serializing_if = "Option::is_none")]
pub pdu: Option<Box<RawJsonValue>>,
}
impl Request {
/// Creates a new `Request` with the given event ID.
#[must_use]
pub fn new(event_id: OwnedEventId) -> Self {
Self { event_id, pdu: None }
}
}
}
+55
View File
@@ -0,0 +1,55 @@
//! `POST /_matrix/policy/unstable/org.matrix.msc4284/sign`
//!
//! Asks a policy server to sign our event
pub mod unstable {
//! `/policy/unstable/org.matrix.msc4284` ([spec])
//!
//! [spec]: https://github.com/matrix-org/matrix-spec-proposals/pull/4284
use ruma::{
ServerSignatures,
api::{federation::authentication::ServerSignatures as ServerSignaturesAuth, request, response}, metadata
};
use serde_json::value::RawValue as RawJsonValue;
metadata! {
method: POST,
rate_limited: false,
authentication: ServerSignaturesAuth,
history: {
unstable => "/_matrix/policy/unstable/org.matrix.msc4284/sign",
}
}
/// Response type for the `sign` endpoint.
#[response]
pub struct Response {
/// The signatures returned from the policy server (if provided)
#[ruma_api(body)]
pub signatures: Option<ServerSignatures>
}
impl Response {
/// Creates a new `Response` with the given recommendation.
#[must_use]
pub fn new(signatures: Option<ServerSignatures>) -> Self {
Self { signatures }
}
}
/// Request type for the `sign` endpoint.
#[request]
pub struct Request {
/// The PDU body (in canonical JSON)
#[ruma_api(body)]
pub pdu: Box<RawJsonValue>,
}
impl Request {
/// Creates a new `Request` with the given event JSON
#[must_use]
pub fn new(pdu: Box<RawJsonValue>) -> Self {
Self { pdu }
}
}
}
+58
View File
@@ -0,0 +1,58 @@
//! `GET /_matrix/federation/*/rooms/{roomId}/report/{eventId}`
//!
//! Send a request to report an event originating from another server.
pub mod msc3843 {
//! `MSC3843` ([MSC])
//!
//! [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/3843
use ruma::{
OwnedEventId, OwnedRoomId, api::{federation::authentication::ServerSignatures, request, response}, metadata
};
metadata! {
method: POST,
rate_limited: false,
authentication: ServerSignatures,
history: {
unstable => "/_matrix/federation/unstable/org.matrix.msc3843/rooms/{room_id}/report/{event_id}",
}
}
/// Request type for the `report_content` endpoint.
#[request]
pub struct Request {
/// The room ID that the reported event was sent in.
#[ruma_api(path)]
pub room_id: OwnedRoomId,
/// The event being reported.
#[ruma_api(path)]
pub event_id: OwnedEventId,
/// The reason that the event is being reported.
pub reason: String,
}
/// Response type for the `report_content` endpoint.
#[response]
#[derive(Default)]
pub struct Response;
impl Request {
/// Creates a `Request` with the given room ID, event ID and reason.
#[must_use]
pub fn new(room_id: OwnedRoomId, event_id: OwnedEventId, reason: String) -> Self {
Self { room_id, event_id, reason }
}
}
impl Response {
/// Creates a new empty `Response`.
#[must_use]
pub fn new() -> Self {
Self::default()
}
}
}