]> git.proxmox.com Git - proxmox.git/blame - proxmox-notify/src/api/smtp.rs
notify: add 'disable' parameter for matchers and targets.
[proxmox.git] / proxmox-notify / src / api / smtp.rs
CommitLineData
20b29089
LW
1use proxmox_http_error::HttpError;
2
3use crate::api::{http_bail, http_err};
4use crate::endpoints::smtp::{
5 DeleteableSmtpProperty, SmtpConfig, SmtpConfigUpdater, SmtpPrivateConfig,
6 SmtpPrivateConfigUpdater, SMTP_TYPENAME,
7};
8use crate::Config;
9
10/// Get a list of all smtp endpoints.
11///
12/// The caller is responsible for any needed permission checks.
13/// Returns a list of all smtp endpoints or a `HttpError` if the config is
14/// erroneous (`500 Internal server error`).
15pub fn get_endpoints(config: &Config) -> Result<Vec<SmtpConfig>, HttpError> {
16 config
17 .config
18 .convert_to_typed_array(SMTP_TYPENAME)
19 .map_err(|e| http_err!(NOT_FOUND, "Could not fetch endpoints: {e}"))
20}
21
22/// Get smtp endpoint with given `name`.
23///
24/// The caller is responsible for any needed permission checks.
25/// Returns the endpoint or a `HttpError` if the endpoint was not found (`404 Not found`).
26pub fn get_endpoint(config: &Config, name: &str) -> Result<SmtpConfig, HttpError> {
27 config
28 .config
29 .lookup(SMTP_TYPENAME, name)
30 .map_err(|_| http_err!(NOT_FOUND, "endpoint '{name}' not found"))
31}
32
33/// Add a new smtp endpoint.
34///
35/// The caller is responsible for any needed permission checks.
36/// The caller also responsible for locking the configuration files.
37/// Returns a `HttpError` if:
38/// - an entity with the same name already exists (`400 Bad request`)
39/// - the configuration could not be saved (`500 Internal server error`)
40/// - mailto *and* mailto_user are both set to `None`
41pub fn add_endpoint(
42 config: &mut Config,
43 endpoint_config: &SmtpConfig,
44 private_endpoint_config: &SmtpPrivateConfig,
45) -> Result<(), HttpError> {
46 if endpoint_config.name != private_endpoint_config.name {
47 // Programming error by the user of the crate, thus we panic
48 panic!("name for endpoint config and private config must be identical");
49 }
50
51 super::ensure_unique(config, &endpoint_config.name)?;
52
53 if endpoint_config.mailto.is_none() && endpoint_config.mailto_user.is_none() {
54 http_bail!(
55 BAD_REQUEST,
56 "must at least provide one recipient, either in mailto or in mailto-user"
57 );
58 }
59
60 super::set_private_config_entry(
61 config,
62 private_endpoint_config,
63 SMTP_TYPENAME,
64 &endpoint_config.name,
65 )?;
66
67 config
68 .config
69 .set_data(&endpoint_config.name, SMTP_TYPENAME, endpoint_config)
70 .map_err(|e| {
71 http_err!(
72 INTERNAL_SERVER_ERROR,
73 "could not save endpoint '{}': {e}",
74 endpoint_config.name
75 )
76 })
77}
78
79/// Update existing smtp endpoint
80///
81/// The caller is responsible for any needed permission checks.
82/// The caller also responsible for locking the configuration files.
83/// Returns a `HttpError` if:
84/// - the configuration could not be saved (`500 Internal server error`)
85/// - mailto *and* mailto_user are both set to `None`
86pub fn update_endpoint(
87 config: &mut Config,
88 name: &str,
89 updater: &SmtpConfigUpdater,
90 private_endpoint_config_updater: &SmtpPrivateConfigUpdater,
91 delete: Option<&[DeleteableSmtpProperty]>,
92 digest: Option<&[u8]>,
93) -> Result<(), HttpError> {
94 super::verify_digest(config, digest)?;
95
96 let mut endpoint = get_endpoint(config, name)?;
97
98 if let Some(delete) = delete {
99 for deleteable_property in delete {
100 match deleteable_property {
101 DeleteableSmtpProperty::Author => endpoint.author = None,
102 DeleteableSmtpProperty::Comment => endpoint.comment = None,
306f4005 103 DeleteableSmtpProperty::Disable => endpoint.disable = None,
20b29089
LW
104 DeleteableSmtpProperty::Mailto => endpoint.mailto = None,
105 DeleteableSmtpProperty::MailtoUser => endpoint.mailto_user = None,
106 DeleteableSmtpProperty::Password => super::set_private_config_entry(
107 config,
108 &SmtpPrivateConfig {
109 name: name.to_string(),
110 password: None,
111 },
112 SMTP_TYPENAME,
113 name,
114 )?,
115 DeleteableSmtpProperty::Port => endpoint.port = None,
116 DeleteableSmtpProperty::Username => endpoint.username = None,
117 }
118 }
119 }
120
121 if let Some(mailto) = &updater.mailto {
122 endpoint.mailto = Some(mailto.iter().map(String::from).collect());
123 }
124 if let Some(mailto_user) = &updater.mailto_user {
125 endpoint.mailto_user = Some(mailto_user.iter().map(String::from).collect());
126 }
127 if let Some(from_address) = &updater.from_address {
128 endpoint.from_address = from_address.into();
129 }
130 if let Some(server) = &updater.server {
131 endpoint.server = server.into();
132 }
133 if let Some(port) = &updater.port {
134 endpoint.port = Some(*port);
135 }
136 if let Some(username) = &updater.username {
137 endpoint.username = Some(username.into());
138 }
139 if let Some(mode) = &updater.mode {
140 endpoint.mode = Some(*mode);
141 }
142 if let Some(password) = &private_endpoint_config_updater.password {
143 super::set_private_config_entry(
144 config,
145 &SmtpPrivateConfig {
146 name: name.into(),
147 password: Some(password.into()),
148 },
149 SMTP_TYPENAME,
150 name,
151 )?;
152 }
153
154 if let Some(author) = &updater.author {
155 endpoint.author = Some(author.into());
156 }
157
158 if let Some(comment) = &updater.comment {
159 endpoint.comment = Some(comment.into());
160 }
161
306f4005
LW
162 if let Some(disable) = &updater.disable {
163 endpoint.disable = Some(*disable);
164 }
165
20b29089
LW
166 if endpoint.mailto.is_none() && endpoint.mailto_user.is_none() {
167 http_bail!(
168 BAD_REQUEST,
169 "must at least provide one recipient, either in mailto or in mailto-user"
170 );
171 }
172
173 config
174 .config
175 .set_data(name, SMTP_TYPENAME, &endpoint)
176 .map_err(|e| {
177 http_err!(
178 INTERNAL_SERVER_ERROR,
179 "could not save endpoint '{}': {e}",
180 endpoint.name
181 )
182 })
183}
184
185/// Delete existing smtp endpoint
186///
187/// The caller is responsible for any needed permission checks.
188/// The caller also responsible for locking the configuration files.
189/// Returns a `HttpError` if:
190/// - an entity with the same name already exists (`400 Bad request`)
191/// - the configuration could not be saved (`500 Internal server error`)
192pub fn delete_endpoint(config: &mut Config, name: &str) -> Result<(), HttpError> {
193 // Check if the endpoint exists
194 let _ = get_endpoint(config, name)?;
195 super::ensure_unused(config, name)?;
196
197 super::remove_private_config_entry(config, name)?;
198 config.config.sections.remove(name);
199
200 Ok(())
201}
202
203#[cfg(test)]
204pub mod tests {
205 use super::*;
206 use crate::api::test_helpers::*;
207 use crate::endpoints::smtp::SmtpMode;
208
209 pub fn add_smtp_endpoint_for_test(config: &mut Config, name: &str) -> Result<(), HttpError> {
210 add_endpoint(
211 config,
212 &SmtpConfig {
213 name: name.into(),
214 mailto: Some(vec!["user1@example.com".into()]),
215 mailto_user: None,
216 from_address: "from@example.com".into(),
217 author: Some("root".into()),
218 comment: Some("Comment".into()),
219 mode: Some(SmtpMode::StartTls),
220 server: "localhost".into(),
221 port: Some(555),
222 username: Some("username".into()),
306f4005 223 ..Default::default()
20b29089
LW
224 },
225 &SmtpPrivateConfig {
226 name: name.into(),
227 password: Some("password".into()),
228 },
229 )?;
230
231 assert!(get_endpoint(config, name).is_ok());
232 Ok(())
233 }
234
235 #[test]
236 fn test_smtp_create() -> Result<(), HttpError> {
237 let mut config = empty_config();
238
239 assert_eq!(get_endpoints(&config)?.len(), 0);
240 add_smtp_endpoint_for_test(&mut config, "smtp-endpoint")?;
241
242 // Endpoints must have a unique name
243 assert!(add_smtp_endpoint_for_test(&mut config, "smtp-endpoint").is_err());
244 assert_eq!(get_endpoints(&config)?.len(), 1);
245 Ok(())
246 }
247
248 #[test]
249 fn test_update_not_existing_returns_error() -> Result<(), HttpError> {
250 let mut config = empty_config();
251
252 assert!(update_endpoint(
253 &mut config,
254 "test",
255 &Default::default(),
256 &Default::default(),
257 None,
258 None,
259 )
260 .is_err());
261
262 Ok(())
263 }
264
265 #[test]
266 fn test_update_invalid_digest_returns_error() -> Result<(), HttpError> {
267 let mut config = empty_config();
268 add_smtp_endpoint_for_test(&mut config, "sendmail-endpoint")?;
269
270 assert!(update_endpoint(
271 &mut config,
272 "sendmail-endpoint",
273 &Default::default(),
274 &Default::default(),
275 None,
276 Some(&[0; 32]),
277 )
278 .is_err());
279
280 Ok(())
281 }
282
283 #[test]
284 fn test_update() -> Result<(), HttpError> {
285 let mut config = empty_config();
286 add_smtp_endpoint_for_test(&mut config, "smtp-endpoint")?;
287
288 let digest = config.digest;
289
290 update_endpoint(
291 &mut config,
292 "smtp-endpoint",
293 &SmtpConfigUpdater {
294 mailto: Some(vec!["user2@example.com".into(), "user3@example.com".into()]),
295 mailto_user: Some(vec!["root@pam".into()]),
296 from_address: Some("root@example.com".into()),
297 author: Some("newauthor".into()),
298 comment: Some("new comment".into()),
299 mode: Some(SmtpMode::Insecure),
300 server: Some("pali".into()),
301 port: Some(444),
302 username: Some("newusername".into()),
303 ..Default::default()
304 },
305 &Default::default(),
306 None,
307 Some(&digest),
308 )?;
309
310 let endpoint = get_endpoint(&config, "smtp-endpoint")?;
311
312 assert_eq!(
313 endpoint.mailto,
314 Some(vec![
315 "user2@example.com".to_string(),
316 "user3@example.com".to_string()
317 ])
318 );
319 assert_eq!(endpoint.mailto_user, Some(vec!["root@pam".to_string(),]));
320 assert_eq!(endpoint.from_address, "root@example.com".to_string());
321 assert_eq!(endpoint.author, Some("newauthor".to_string()));
322 assert_eq!(endpoint.comment, Some("new comment".to_string()));
323
324 // Test property deletion
325 update_endpoint(
326 &mut config,
327 "smtp-endpoint",
328 &Default::default(),
329 &Default::default(),
330 Some(&[
331 DeleteableSmtpProperty::Author,
332 DeleteableSmtpProperty::MailtoUser,
333 DeleteableSmtpProperty::Port,
334 DeleteableSmtpProperty::Username,
335 DeleteableSmtpProperty::Comment,
336 ]),
337 None,
338 )?;
339
340 let endpoint = get_endpoint(&config, "smtp-endpoint")?;
341
342 assert_eq!(endpoint.author, None);
343 assert_eq!(endpoint.comment, None);
344 assert_eq!(endpoint.port, None);
345 assert_eq!(endpoint.username, None);
346 assert_eq!(endpoint.mailto_user, None);
347
348 Ok(())
349 }
350
351 #[test]
352 fn test_delete() -> Result<(), HttpError> {
353 let mut config = empty_config();
354 add_smtp_endpoint_for_test(&mut config, "smtp-endpoint")?;
355
356 delete_endpoint(&mut config, "smtp-endpoint")?;
357 assert!(delete_endpoint(&mut config, "smtp-endpoint").is_err());
358 assert_eq!(get_endpoints(&config)?.len(), 0);
359
360 Ok(())
361 }
362}