]> git.proxmox.com Git - proxmox.git/blame - proxmox-notify/src/api/smtp.rs
notify: convert Option<Vec<T>> -> Vec<T> in config structs
[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,
a4d55947
LW
43 endpoint_config: SmtpConfig,
44 private_endpoint_config: SmtpPrivateConfig,
20b29089
LW
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
d61e3fc7 53 if endpoint_config.mailto.is_empty() && endpoint_config.mailto_user.is_empty() {
20b29089
LW
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
a4d55947 69 .set_data(&endpoint_config.name, SMTP_TYPENAME, &endpoint_config)
20b29089
LW
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,
a4d55947
LW
89 updater: SmtpConfigUpdater,
90 private_endpoint_config_updater: SmtpPrivateConfigUpdater,
20b29089
LW
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,
d61e3fc7
LW
104 DeleteableSmtpProperty::Mailto => endpoint.mailto.clear(),
105 DeleteableSmtpProperty::MailtoUser => endpoint.mailto_user.clear(),
20b29089
LW
106 DeleteableSmtpProperty::Password => super::set_private_config_entry(
107 config,
a4d55947 108 SmtpPrivateConfig {
20b29089
LW
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
a4d55947 121 if let Some(mailto) = updater.mailto {
d61e3fc7 122 endpoint.mailto = mailto;
20b29089 123 }
a4d55947 124 if let Some(mailto_user) = updater.mailto_user {
d61e3fc7 125 endpoint.mailto_user = mailto_user;
20b29089 126 }
a4d55947
LW
127 if let Some(from_address) = updater.from_address {
128 endpoint.from_address = from_address;
20b29089 129 }
a4d55947
LW
130 if let Some(server) = updater.server {
131 endpoint.server = server;
20b29089 132 }
a4d55947
LW
133 if let Some(port) = updater.port {
134 endpoint.port = Some(port);
20b29089 135 }
a4d55947
LW
136 if let Some(username) = updater.username {
137 endpoint.username = Some(username);
20b29089 138 }
a4d55947
LW
139 if let Some(mode) = updater.mode {
140 endpoint.mode = Some(mode);
20b29089 141 }
a4d55947 142 if let Some(password) = private_endpoint_config_updater.password {
20b29089
LW
143 super::set_private_config_entry(
144 config,
a4d55947 145 SmtpPrivateConfig {
20b29089 146 name: name.into(),
a4d55947 147 password: Some(password),
20b29089
LW
148 },
149 SMTP_TYPENAME,
150 name,
151 )?;
152 }
153
a4d55947
LW
154 if let Some(author) = updater.author {
155 endpoint.author = Some(author);
20b29089
LW
156 }
157
a4d55947
LW
158 if let Some(comment) = updater.comment {
159 endpoint.comment = Some(comment);
20b29089
LW
160 }
161
a4d55947
LW
162 if let Some(disable) = updater.disable {
163 endpoint.disable = Some(disable);
306f4005
LW
164 }
165
d61e3fc7 166 if endpoint.mailto.is_empty() && endpoint.mailto_user.is_empty() {
20b29089
LW
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)?;
50fa98e2 195 super::ensure_safe_to_delete(config, name)?;
20b29089
LW
196
197 super::remove_private_config_entry(config, name)?;
198 config.config.sections.remove(name);
199
200 Ok(())
201}
202
9bea76c6 203#[cfg(all(feature = "pve-context", test))]
20b29089
LW
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,
a4d55947 212 SmtpConfig {
20b29089 213 name: name.into(),
d61e3fc7
LW
214 mailto: vec!["user1@example.com".into()],
215 mailto_user: vec![],
20b29089
LW
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 224 },
a4d55947 225 SmtpPrivateConfig {
20b29089
LW
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",
a4d55947
LW
255 Default::default(),
256 Default::default(),
20b29089
LW
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",
a4d55947
LW
273 Default::default(),
274 Default::default(),
20b29089
LW
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",
a4d55947 293 SmtpConfigUpdater {
20b29089
LW
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 },
a4d55947 305 Default::default(),
20b29089
LW
306 None,
307 Some(&digest),
308 )?;
309
310 let endpoint = get_endpoint(&config, "smtp-endpoint")?;
311
312 assert_eq!(
313 endpoint.mailto,
d61e3fc7 314 vec![
20b29089
LW
315 "user2@example.com".to_string(),
316 "user3@example.com".to_string()
d61e3fc7 317 ]
20b29089 318 );
d61e3fc7 319 assert_eq!(endpoint.mailto_user, vec!["root@pam".to_string(),]);
20b29089
LW
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",
a4d55947
LW
328 Default::default(),
329 Default::default(),
20b29089
LW
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);
d61e3fc7 346 assert!(endpoint.mailto_user.is_empty());
20b29089
LW
347
348 Ok(())
349 }
350
9bea76c6
LW
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 // }
20b29089 362}