Skip to content

Commit d0f1cf4

Browse files
committed
Allow configuring the connection to the homeserver to be read-only.
1 parent f484896 commit d0f1cf4

File tree

6 files changed

+153
-11
lines changed

6 files changed

+153
-11
lines changed

crates/cli/src/util.rs

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ use std::{sync::Arc, time::Duration};
99
use anyhow::Context;
1010
use mas_config::{
1111
AccountConfig, BrandingConfig, CaptchaConfig, DatabaseConfig, EmailConfig, EmailSmtpMode,
12-
EmailTransportKind, ExperimentalConfig, MatrixConfig, PasswordsConfig, PolicyConfig,
13-
TemplatesConfig,
12+
EmailTransportKind, ExperimentalConfig, HomeserverKind, MatrixConfig, PasswordsConfig,
13+
PolicyConfig, TemplatesConfig,
1414
};
1515
use mas_data_model::{SessionExpirationConfig, SiteConfig};
1616
use mas_email::{MailTransport, Mailer};
1717
use mas_handlers::passwords::PasswordManager;
18-
use mas_matrix::HomeserverConnection;
18+
use mas_matrix::{HomeserverConnection, ReadOnlyHomeserverConnection};
1919
use mas_matrix_synapse::SynapseConnection;
2020
use mas_policy::PolicyFactory;
2121
use mas_router::UrlBuilder;
@@ -354,12 +354,24 @@ pub fn homeserver_connection_from_config(
354354
config: &MatrixConfig,
355355
http_client: reqwest::Client,
356356
) -> Arc<dyn HomeserverConnection> {
357-
Arc::new(SynapseConnection::new(
358-
config.homeserver.clone(),
359-
config.endpoint.clone(),
360-
config.secret.clone(),
361-
http_client,
362-
))
357+
match config.kind {
358+
HomeserverKind::Synapse => Arc::new(SynapseConnection::new(
359+
config.homeserver.clone(),
360+
config.endpoint.clone(),
361+
config.secret.clone(),
362+
http_client,
363+
)),
364+
HomeserverKind::SynapseReadOnly => {
365+
let connection = SynapseConnection::new(
366+
config.homeserver.clone(),
367+
config.endpoint.clone(),
368+
config.secret.clone(),
369+
http_client,
370+
);
371+
let readonly = ReadOnlyHomeserverConnection::new(connection);
372+
Arc::new(readonly)
373+
}
374+
}
363375
}
364376

365377
#[cfg(test)]

crates/config/src/sections/matrix.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,29 @@ fn default_endpoint() -> Url {
2323
Url::parse("http://localhost:8008/").unwrap()
2424
}
2525

26+
/// The kind of homeserver it is.
27+
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
28+
#[serde(rename_all = "snake_case")]
29+
pub enum HomeserverKind {
30+
/// Homeserver is Synapse
31+
#[default]
32+
Synapse,
33+
34+
/// Homeserver is Synapse, in read-only mode
35+
///
36+
/// This is meant for testing rolling out Matrix Authentication Service with
37+
/// no risk of writing data to the homeserver.
38+
SynapseReadOnly,
39+
}
40+
2641
/// Configuration related to the Matrix homeserver
2742
#[serde_as]
2843
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
2944
pub struct MatrixConfig {
45+
/// The kind of homeserver it is.
46+
#[serde(default)]
47+
pub kind: HomeserverKind,
48+
3049
/// The server name of the homeserver.
3150
#[serde(default = "default_homeserver")]
3251
pub homeserver: String,
@@ -49,6 +68,7 @@ impl MatrixConfig {
4968
R: Rng + Send,
5069
{
5170
Self {
71+
kind: HomeserverKind::default(),
5272
homeserver: default_homeserver(),
5373
secret: Alphanumeric.sample_string(&mut rng, 32),
5474
endpoint: default_endpoint(),
@@ -57,6 +77,7 @@ impl MatrixConfig {
5777

5878
pub(crate) fn test() -> Self {
5979
Self {
80+
kind: HomeserverKind::default(),
6081
homeserver: default_homeserver(),
6182
secret: "test".to_owned(),
6283
endpoint: default_endpoint(),

crates/config/src/sections/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ pub use self::{
3737
BindConfig as HttpBindConfig, HttpConfig, ListenerConfig as HttpListenerConfig,
3838
Resource as HttpResource, TlsConfig as HttpTlsConfig, UnixOrTcp,
3939
},
40-
matrix::MatrixConfig,
40+
matrix::{HomeserverKind, MatrixConfig},
4141
passwords::{Algorithm as PasswordAlgorithm, PasswordsConfig},
4242
policy::PolicyConfig,
4343
rate_limiting::RateLimitingConfig,

crates/matrix/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@
55
// Please see LICENSE in the repository root for full details.
66

77
mod mock;
8+
mod readonly;
89

910
use std::{collections::HashSet, sync::Arc};
1011

1112
use ruma_common::UserId;
1213

13-
pub use self::mock::HomeserverConnection as MockHomeserverConnection;
14+
pub use self::{
15+
mock::HomeserverConnection as MockHomeserverConnection, readonly::ReadOnlyHomeserverConnection,
16+
};
1417

1518
#[derive(Debug)]
1619
pub struct MatrixUser {

crates/matrix/src/readonly.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright 2025 New Vector Ltd.
2+
//
3+
// SPDX-License-Identifier: AGPL-3.0-only
4+
// Please see LICENSE in the repository root for full details.
5+
6+
use std::collections::HashSet;
7+
8+
use crate::{HomeserverConnection, MatrixUser, ProvisionRequest};
9+
10+
/// A wrapper around a [`HomeserverConnection`] that only allows read
11+
/// operations.
12+
pub struct ReadOnlyHomeserverConnection<C> {
13+
inner: C,
14+
}
15+
16+
impl<C> ReadOnlyHomeserverConnection<C> {
17+
pub fn new(inner: C) -> Self
18+
where
19+
C: HomeserverConnection,
20+
{
21+
Self { inner }
22+
}
23+
}
24+
25+
#[async_trait::async_trait]
26+
impl<C: HomeserverConnection> HomeserverConnection for ReadOnlyHomeserverConnection<C> {
27+
fn homeserver(&self) -> &str {
28+
self.inner.homeserver()
29+
}
30+
31+
async fn query_user(&self, mxid: &str) -> Result<MatrixUser, anyhow::Error> {
32+
self.inner.query_user(mxid).await
33+
}
34+
35+
async fn provision_user(&self, _request: &ProvisionRequest) -> Result<bool, anyhow::Error> {
36+
anyhow::bail!("Provisioning is not supported in read-only mode");
37+
}
38+
39+
async fn is_localpart_available(&self, localpart: &str) -> Result<bool, anyhow::Error> {
40+
self.inner.is_localpart_available(localpart).await
41+
}
42+
43+
async fn create_device(&self, _mxid: &str, _device_id: &str) -> Result<(), anyhow::Error> {
44+
anyhow::bail!("Device creation is not supported in read-only mode");
45+
}
46+
47+
async fn delete_device(&self, _mxid: &str, _device_id: &str) -> Result<(), anyhow::Error> {
48+
anyhow::bail!("Device deletion is not supported in read-only mode");
49+
}
50+
51+
async fn sync_devices(
52+
&self,
53+
_mxid: &str,
54+
_devices: HashSet<String>,
55+
) -> Result<(), anyhow::Error> {
56+
anyhow::bail!("Device synchronization is not supported in read-only mode");
57+
}
58+
59+
async fn delete_user(&self, _mxid: &str, _erase: bool) -> Result<(), anyhow::Error> {
60+
anyhow::bail!("User deletion is not supported in read-only mode");
61+
}
62+
63+
async fn reactivate_user(&self, _mxid: &str) -> Result<(), anyhow::Error> {
64+
anyhow::bail!("User reactivation is not supported in read-only mode");
65+
}
66+
67+
async fn set_displayname(&self, _mxid: &str, _displayname: &str) -> Result<(), anyhow::Error> {
68+
anyhow::bail!("User displayname update is not supported in read-only mode");
69+
}
70+
71+
async fn unset_displayname(&self, _mxid: &str) -> Result<(), anyhow::Error> {
72+
anyhow::bail!("User displayname update is not supported in read-only mode");
73+
}
74+
75+
async fn allow_cross_signing_reset(&self, _mxid: &str) -> Result<(), anyhow::Error> {
76+
anyhow::bail!("Allowing cross-signing reset is not supported in read-only mode");
77+
}
78+
}

docs/config.schema.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1612,6 +1612,15 @@
16121612
"secret"
16131613
],
16141614
"properties": {
1615+
"kind": {
1616+
"description": "The kind of homeserver it is.",
1617+
"default": "synapse",
1618+
"allOf": [
1619+
{
1620+
"$ref": "#/definitions/HomeserverKind"
1621+
}
1622+
]
1623+
},
16151624
"homeserver": {
16161625
"description": "The server name of the homeserver.",
16171626
"default": "localhost:8008",
@@ -1629,6 +1638,25 @@
16291638
}
16301639
}
16311640
},
1641+
"HomeserverKind": {
1642+
"description": "The kind of homeserver it is.",
1643+
"oneOf": [
1644+
{
1645+
"description": "Homeserver is Synapse",
1646+
"type": "string",
1647+
"enum": [
1648+
"synapse"
1649+
]
1650+
},
1651+
{
1652+
"description": "Homeserver is Synapse, in read-only mode\n\nThis is meant for testing rolling out Matrix Authentication Service with no risk of writing data to the homeserver.",
1653+
"type": "string",
1654+
"enum": [
1655+
"synapse_read_only"
1656+
]
1657+
}
1658+
]
1659+
},
16321660
"PolicyConfig": {
16331661
"description": "Application secrets",
16341662
"type": "object",

0 commit comments

Comments
 (0)