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,68 @@ mod tests {
101
168
Figment , Jail ,
102
169
providers:: { Format , Yaml } ,
103
170
} ;
171
+ use tokio:: { runtime:: Handle , task} ;
104
172
105
173
use super :: * ;
106
174
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
- } ) ;
175
+ #[ tokio:: test]
176
+ async fn load_config ( ) {
177
+ task:: spawn_blocking ( || {
178
+ Jail :: expect_with ( |jail| {
179
+ jail. create_file (
180
+ "config.yaml" ,
181
+ r"
182
+ matrix:
183
+ homeserver: matrix.org
184
+ secret_file: secret
185
+ " ,
186
+ ) ?;
187
+ jail. create_file ( "secret" , r"m472!x53c237" ) ?;
188
+
189
+ let config = Figment :: new ( )
190
+ . merge ( Yaml :: file ( "config.yaml" ) )
191
+ . extract_inner :: < MatrixConfig > ( "matrix" ) ?;
192
+
193
+ Handle :: current ( ) . block_on ( async move {
194
+ assert_eq ! ( & config. homeserver, "matrix.org" ) ;
195
+ assert ! ( matches!( config. secret, Secret :: File ( ref p) if p == "secret" ) ) ;
196
+ assert_eq ! ( config. secret( ) . await . unwrap( ) , "m472!x53c237" ) ;
197
+ } ) ;
198
+
199
+ Ok ( ( ) )
200
+ } ) ;
201
+ } )
202
+ . await
203
+ . unwrap ( ) ;
204
+ }
205
+
206
+ #[ tokio:: test]
207
+ async fn load_config_inline_secrets ( ) {
208
+ task:: spawn_blocking ( || {
209
+ Jail :: expect_with ( |jail| {
210
+ jail. create_file (
211
+ "config.yaml" ,
212
+ r"
213
+ matrix:
214
+ homeserver: matrix.org
215
+ secret: m472!x53c237
216
+ " ,
217
+ ) ?;
218
+
219
+ let config = Figment :: new ( )
220
+ . merge ( Yaml :: file ( "config.yaml" ) )
221
+ . extract_inner :: < MatrixConfig > ( "matrix" ) ?;
222
+
223
+ Handle :: current ( ) . block_on ( async move {
224
+ assert_eq ! ( & config. homeserver, "matrix.org" ) ;
225
+ assert ! ( matches!( config. secret, Secret :: Value ( ref v) if v == "m472!x53c237" ) ) ;
226
+ assert_eq ! ( config. secret( ) . await . unwrap( ) , "m472!x53c237" ) ;
227
+ } ) ;
228
+
229
+ Ok ( ( ) )
230
+ } ) ;
231
+ } )
232
+ . await
233
+ . unwrap ( ) ;
128
234
}
129
235
}
0 commit comments