4
4
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
5
5
// Please see LICENSE files in the repository root for full details.
6
6
7
+ use anyhow:: bail;
8
+ use camino:: Utf8PathBuf ;
7
9
use rand:: {
8
10
Rng ,
9
11
distributions:: { Alphanumeric , DistString } ,
@@ -48,6 +50,54 @@ pub enum HomeserverKind {
48
50
SynapseModern ,
49
51
}
50
52
53
+ /// Shared secret between MAS and the homeserver.
54
+ ///
55
+ /// It either holds the secret value directly or references a file where the
56
+ /// secret is stored.
57
+ #[ derive( Clone , Debug ) ]
58
+ pub enum Secret {
59
+ File ( Utf8PathBuf ) ,
60
+ Value ( String ) ,
61
+ }
62
+
63
+ /// Secret fields as serialized in JSON.
64
+ #[ derive( JsonSchema , Serialize , Deserialize , Clone , Debug ) ]
65
+ struct SecretRaw {
66
+ #[ schemars( with = "Option<String>" ) ]
67
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
68
+ secret_file : Option < Utf8PathBuf > ,
69
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
70
+ secret : Option < String > ,
71
+ }
72
+
73
+ impl TryFrom < SecretRaw > for Secret {
74
+ type Error = anyhow:: Error ;
75
+
76
+ fn try_from ( value : SecretRaw ) -> Result < Self , Self :: Error > {
77
+ match ( value. secret , value. secret_file ) {
78
+ ( None , None ) => bail ! ( "Missing `secret` or `secret_file`" ) ,
79
+ ( None , Some ( path) ) => Ok ( Secret :: File ( path) ) ,
80
+ ( Some ( secret) , None ) => Ok ( Secret :: Value ( secret) ) ,
81
+ ( Some ( _) , Some ( _) ) => bail ! ( "Cannot specify both `secret` and `secret_file`" ) ,
82
+ }
83
+ }
84
+ }
85
+
86
+ impl From < Secret > for SecretRaw {
87
+ fn from ( value : Secret ) -> Self {
88
+ match value {
89
+ Secret :: File ( path) => SecretRaw {
90
+ secret_file : Some ( path) ,
91
+ secret : None ,
92
+ } ,
93
+ Secret :: Value ( secret) => SecretRaw {
94
+ secret_file : None ,
95
+ secret : Some ( secret) ,
96
+ } ,
97
+ }
98
+ }
99
+ }
100
+
51
101
/// Configuration related to the Matrix homeserver
52
102
#[ serde_as]
53
103
#[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema ) ]
@@ -61,7 +111,10 @@ pub struct MatrixConfig {
61
111
pub homeserver : String ,
62
112
63
113
/// Shared secret to use for calls to the admin API
64
- pub secret : String ,
114
+ #[ schemars( with = "SecretRaw" ) ]
115
+ #[ serde_as( as = "serde_with::TryFromInto<SecretRaw>" ) ]
116
+ #[ serde( flatten) ]
117
+ pub secret : Secret ,
65
118
66
119
/// The base URL of the homeserver's client API
67
120
#[ serde( default = "default_endpoint" ) ]
@@ -73,14 +126,28 @@ impl ConfigurationSection for MatrixConfig {
73
126
}
74
127
75
128
impl MatrixConfig {
129
+ /// Returns the shared secret.
130
+ ///
131
+ /// If `secret_file` was given, the secret is read from that file.
132
+ ///
133
+ /// # Errors
134
+ ///
135
+ /// Returns an error when the shared secret could not be read from file.
136
+ pub async fn secret ( & self ) -> anyhow:: Result < String > {
137
+ Ok ( match & self . secret {
138
+ Secret :: File ( path) => tokio:: fs:: read_to_string ( path) . await ?,
139
+ Secret :: Value ( secret) => secret. clone ( ) ,
140
+ } )
141
+ }
142
+
76
143
pub ( crate ) fn generate < R > ( mut rng : R ) -> Self
77
144
where
78
145
R : Rng + Send ,
79
146
{
80
147
Self {
81
148
kind : HomeserverKind :: default ( ) ,
82
149
homeserver : default_homeserver ( ) ,
83
- secret : Alphanumeric . sample_string ( & mut rng, 32 ) ,
150
+ secret : Secret :: Value ( Alphanumeric . sample_string ( & mut rng, 32 ) ) ,
84
151
endpoint : default_endpoint ( ) ,
85
152
}
86
153
}
@@ -89,7 +156,7 @@ impl MatrixConfig {
89
156
Self {
90
157
kind : HomeserverKind :: default ( ) ,
91
158
homeserver : default_homeserver ( ) ,
92
- secret : "test" . to_owned ( ) ,
159
+ secret : Secret :: Value ( "test" . to_owned ( ) ) ,
93
160
endpoint : default_endpoint ( ) ,
94
161
}
95
162
}
@@ -101,29 +168,69 @@ mod tests {
101
168
Figment , Jail ,
102
169
providers:: { Format , Yaml } ,
103
170
} ;
171
+ use tokio:: runtime:: Handle ;
172
+ use tokio:: task;
104
173
105
174
use super :: * ;
106
175
107
- #[ test]
108
- fn load_config ( ) {
109
- Jail :: expect_with ( |jail| {
110
- jail. create_file (
111
- "config.yaml" ,
112
- r"
113
- matrix:
114
- homeserver: matrix.org
115
- secret: test
116
- " ,
117
- ) ?;
118
-
119
- let config = Figment :: new ( )
120
- . merge ( Yaml :: file ( "config.yaml" ) )
121
- . extract_inner :: < MatrixConfig > ( "matrix" ) ?;
122
-
123
- assert_eq ! ( & config. homeserver, "matrix.org" ) ;
124
- assert_eq ! ( & config. secret, "test" ) ;
125
-
126
- Ok ( ( ) )
127
- } ) ;
176
+ #[ tokio:: test]
177
+ async fn load_config ( ) {
178
+ task:: spawn_blocking ( || {
179
+ Jail :: expect_with ( |jail| {
180
+ jail. create_file (
181
+ "config.yaml" ,
182
+ r"
183
+ matrix:
184
+ homeserver: matrix.org
185
+ secret_file: secret
186
+ " ,
187
+ ) ?;
188
+ jail. create_file ( "secret" , r"m472!x53c237" ) ?;
189
+
190
+ let config = Figment :: new ( )
191
+ . merge ( Yaml :: file ( "config.yaml" ) )
192
+ . extract_inner :: < MatrixConfig > ( "matrix" ) ?;
193
+
194
+ Handle :: current ( ) . block_on ( async move {
195
+ assert_eq ! ( & config. homeserver, "matrix.org" ) ;
196
+ assert ! ( matches!( config. secret, Secret :: File ( ref p) if p == "secret" ) ) ;
197
+ assert_eq ! ( config. secret( ) . await . unwrap( ) , "m472!x53c237" ) ;
198
+ } ) ;
199
+
200
+ Ok ( ( ) )
201
+ } ) ;
202
+ } )
203
+ . await
204
+ . unwrap ( ) ;
205
+ }
206
+
207
+ #[ tokio:: test]
208
+ async fn load_config_inline_secrets ( ) {
209
+ task:: spawn_blocking ( || {
210
+ Jail :: expect_with ( |jail| {
211
+ jail. create_file (
212
+ "config.yaml" ,
213
+ r"
214
+ matrix:
215
+ homeserver: matrix.org
216
+ secret: m472!x53c237
217
+ " ,
218
+ ) ?;
219
+
220
+ let config = Figment :: new ( )
221
+ . merge ( Yaml :: file ( "config.yaml" ) )
222
+ . extract_inner :: < MatrixConfig > ( "matrix" ) ?;
223
+
224
+ Handle :: current ( ) . block_on ( async move {
225
+ assert_eq ! ( & config. homeserver, "matrix.org" ) ;
226
+ assert ! ( matches!( config. secret, Secret :: Value ( ref v) if v == "m472!x53c237" ) ) ;
227
+ assert_eq ! ( config. secret( ) . await . unwrap( ) , "m472!x53c237" ) ;
228
+ } ) ;
229
+
230
+ Ok ( ( ) )
231
+ } ) ;
232
+ } )
233
+ . await
234
+ . unwrap ( ) ;
128
235
}
129
236
}
0 commit comments