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