]> git.proxmox.com Git - proxmox.git/blame - proxmox-notify/src/api/sendmail.rs
notify: api: allow resetting built-in targets if used by a matcher
[proxmox.git] / proxmox-notify / src / api / sendmail.rs
CommitLineData
1a75668d
LW
1use proxmox_http_error::HttpError;
2
3use crate::api::{http_bail, http_err};
21c5c9a0
LW
4use crate::endpoints::sendmail::{
5 DeleteableSendmailProperty, SendmailConfig, SendmailConfigUpdater, SENDMAIL_TYPENAME,
6};
7use crate::Config;
8
9/// Get a list of all sendmail endpoints.
10///
11/// The caller is responsible for any needed permission checks.
1a75668d
LW
12/// Returns a list of all sendmail endpoints or a `HttpError` if the config is
13/// erroneous (`500 Internal server error`).
14pub fn get_endpoints(config: &Config) -> Result<Vec<SendmailConfig>, HttpError> {
21c5c9a0
LW
15 config
16 .config
17 .convert_to_typed_array(SENDMAIL_TYPENAME)
1a75668d 18 .map_err(|e| http_err!(NOT_FOUND, "Could not fetch endpoints: {e}"))
21c5c9a0
LW
19}
20
21/// Get sendmail endpoint with given `name`.
22///
23/// The caller is responsible for any needed permission checks.
1a75668d
LW
24/// Returns the endpoint or a `HttpError` if the endpoint was not found (`404 Not found`).
25pub fn get_endpoint(config: &Config, name: &str) -> Result<SendmailConfig, HttpError> {
21c5c9a0
LW
26 config
27 .config
28 .lookup(SENDMAIL_TYPENAME, name)
1a75668d 29 .map_err(|_| http_err!(NOT_FOUND, "endpoint '{name}' not found"))
21c5c9a0
LW
30}
31
32/// Add a new sendmail endpoint.
33///
34/// The caller is responsible for any needed permission checks.
35/// The caller also responsible for locking the configuration files.
1a75668d
LW
36/// Returns a `HttpError` if:
37/// - an entity with the same name already exists (`400 Bad request`)
1a75668d
LW
38/// - the configuration could not be saved (`500 Internal server error`)
39/// - mailto *and* mailto_user are both set to `None`
40pub fn add_endpoint(config: &mut Config, endpoint: &SendmailConfig) -> Result<(), HttpError> {
2756c11c 41 super::ensure_unique(config, &endpoint.name)?;
21c5c9a0 42
c5f91aa1 43 if endpoint.mailto.is_none() && endpoint.mailto_user.is_none() {
1a75668d
LW
44 http_bail!(
45 BAD_REQUEST,
46 "must at least provide one recipient, either in mailto or in mailto-user"
47 );
c5f91aa1
LW
48 }
49
21c5c9a0
LW
50 config
51 .config
52 .set_data(&endpoint.name, SENDMAIL_TYPENAME, endpoint)
53 .map_err(|e| {
1a75668d
LW
54 http_err!(
55 INTERNAL_SERVER_ERROR,
56 "could not save endpoint '{}': {e}",
57 endpoint.name
21c5c9a0 58 )
1a75668d 59 })
21c5c9a0
LW
60}
61
62/// Update existing sendmail endpoint
63///
64/// The caller is responsible for any needed permission checks.
65/// The caller also responsible for locking the configuration files.
1a75668d 66/// Returns a `HttpError` if:
1a75668d
LW
67/// - the configuration could not be saved (`500 Internal server error`)
68/// - mailto *and* mailto_user are both set to `None`
21c5c9a0
LW
69pub fn update_endpoint(
70 config: &mut Config,
71 name: &str,
72 updater: &SendmailConfigUpdater,
73 delete: Option<&[DeleteableSendmailProperty]>,
74 digest: Option<&[u8]>,
1a75668d 75) -> Result<(), HttpError> {
21c5c9a0
LW
76 super::verify_digest(config, digest)?;
77
78 let mut endpoint = get_endpoint(config, name)?;
79
80 if let Some(delete) = delete {
81 for deleteable_property in delete {
82 match deleteable_property {
83 DeleteableSendmailProperty::FromAddress => endpoint.from_address = None,
84 DeleteableSendmailProperty::Author => endpoint.author = None,
85 DeleteableSendmailProperty::Comment => endpoint.comment = None,
c5f91aa1
LW
86 DeleteableSendmailProperty::Mailto => endpoint.mailto = None,
87 DeleteableSendmailProperty::MailtoUser => endpoint.mailto_user = None,
306f4005 88 DeleteableSendmailProperty::Disable => endpoint.disable = None,
21c5c9a0
LW
89 }
90 }
91 }
92
93 if let Some(mailto) = &updater.mailto {
c5f91aa1
LW
94 endpoint.mailto = Some(mailto.iter().map(String::from).collect());
95 }
96
97 if let Some(mailto_user) = &updater.mailto_user {
98 endpoint.mailto_user = Some(mailto_user.iter().map(String::from).collect());
21c5c9a0
LW
99 }
100
101 if let Some(from_address) = &updater.from_address {
102 endpoint.from_address = Some(from_address.into());
103 }
104
105 if let Some(author) = &updater.author {
106 endpoint.author = Some(author.into());
107 }
108
109 if let Some(comment) = &updater.comment {
110 endpoint.comment = Some(comment.into());
111 }
112
306f4005
LW
113 if let Some(disable) = &updater.disable {
114 endpoint.disable = Some(*disable);
115 }
116
c5f91aa1 117 if endpoint.mailto.is_none() && endpoint.mailto_user.is_none() {
1a75668d
LW
118 http_bail!(
119 BAD_REQUEST,
120 "must at least provide one recipient, either in mailto or in mailto-user"
121 );
c5f91aa1
LW
122 }
123
21c5c9a0
LW
124 config
125 .config
126 .set_data(name, SENDMAIL_TYPENAME, &endpoint)
127 .map_err(|e| {
1a75668d
LW
128 http_err!(
129 INTERNAL_SERVER_ERROR,
130 "could not save endpoint '{}': {e}",
131 endpoint.name
21c5c9a0 132 )
1a75668d 133 })
21c5c9a0
LW
134}
135
136/// Delete existing sendmail endpoint
137///
138/// The caller is responsible for any needed permission checks.
139/// The caller also responsible for locking the configuration files.
1a75668d
LW
140/// Returns a `HttpError` if:
141/// - an entity with the same name already exists (`400 Bad request`)
142/// - a referenced filter does not exist (`400 Bad request`)
143/// - the configuration could not be saved (`500 Internal server error`)
144pub fn delete_endpoint(config: &mut Config, name: &str) -> Result<(), HttpError> {
21c5c9a0
LW
145 // Check if the endpoint exists
146 let _ = get_endpoint(config, name)?;
50fa98e2 147 super::ensure_safe_to_delete(config, name)?;
21c5c9a0
LW
148
149 config.config.sections.remove(name);
150
151 Ok(())
152}
153
9bea76c6 154#[cfg(all(feature = "pve-context", test))]
21c5c9a0
LW
155pub mod tests {
156 use super::*;
157 use crate::api::test_helpers::*;
158
1a75668d
LW
159 pub fn add_sendmail_endpoint_for_test(
160 config: &mut Config,
161 name: &str,
162 ) -> Result<(), HttpError> {
21c5c9a0
LW
163 add_endpoint(
164 config,
165 &SendmailConfig {
166 name: name.into(),
c5f91aa1
LW
167 mailto: Some(vec!["user1@example.com".into()]),
168 mailto_user: None,
21c5c9a0
LW
169 from_address: Some("from@example.com".into()),
170 author: Some("root".into()),
171 comment: Some("Comment".into()),
ee0ab52b 172 filter: None,
306f4005 173 ..Default::default()
21c5c9a0
LW
174 },
175 )?;
176
177 assert!(get_endpoint(config, name).is_ok());
178 Ok(())
179 }
180
181 #[test]
1a75668d 182 fn test_sendmail_create() -> Result<(), HttpError> {
21c5c9a0
LW
183 let mut config = empty_config();
184
21c5c9a0
LW
185 add_sendmail_endpoint_for_test(&mut config, "sendmail-endpoint")?;
186
187 // Endpoints must have a unique name
188 assert!(add_sendmail_endpoint_for_test(&mut config, "sendmail-endpoint").is_err());
21c5c9a0
LW
189 Ok(())
190 }
191
192 #[test]
1a75668d 193 fn test_update_not_existing_returns_error() -> Result<(), HttpError> {
21c5c9a0
LW
194 let mut config = empty_config();
195
196 assert!(update_endpoint(&mut config, "test", &Default::default(), None, None,).is_err());
197
198 Ok(())
199 }
200
201 #[test]
1a75668d 202 fn test_update_invalid_digest_returns_error() -> Result<(), HttpError> {
21c5c9a0
LW
203 let mut config = empty_config();
204 add_sendmail_endpoint_for_test(&mut config, "sendmail-endpoint")?;
205
206 assert!(update_endpoint(
207 &mut config,
208 "sendmail-endpoint",
209 &SendmailConfigUpdater {
210 mailto: Some(vec!["user2@example.com".into(), "user3@example.com".into()]),
c5f91aa1 211 mailto_user: None,
21c5c9a0
LW
212 from_address: Some("root@example.com".into()),
213 author: Some("newauthor".into()),
214 comment: Some("new comment".into()),
306f4005 215 ..Default::default()
21c5c9a0
LW
216 },
217 None,
218 Some(&[0; 32]),
219 )
220 .is_err());
221
222 Ok(())
223 }
224
225 #[test]
1a75668d 226 fn test_sendmail_update() -> Result<(), HttpError> {
21c5c9a0
LW
227 let mut config = empty_config();
228 add_sendmail_endpoint_for_test(&mut config, "sendmail-endpoint")?;
229
230 let digest = config.digest;
231
232 update_endpoint(
233 &mut config,
234 "sendmail-endpoint",
235 &SendmailConfigUpdater {
236 mailto: Some(vec!["user2@example.com".into(), "user3@example.com".into()]),
c5f91aa1 237 mailto_user: Some(vec!["root@pam".into()]),
21c5c9a0
LW
238 from_address: Some("root@example.com".into()),
239 author: Some("newauthor".into()),
240 comment: Some("new comment".into()),
306f4005 241 ..Default::default()
21c5c9a0
LW
242 },
243 None,
244 Some(&digest),
245 )?;
246
247 let endpoint = get_endpoint(&config, "sendmail-endpoint")?;
248
249 assert_eq!(
250 endpoint.mailto,
c5f91aa1 251 Some(vec![
21c5c9a0
LW
252 "user2@example.com".to_string(),
253 "user3@example.com".to_string()
c5f91aa1 254 ])
21c5c9a0 255 );
c5f91aa1 256 assert_eq!(endpoint.mailto_user, Some(vec!["root@pam".to_string(),]));
21c5c9a0
LW
257 assert_eq!(endpoint.from_address, Some("root@example.com".to_string()));
258 assert_eq!(endpoint.author, Some("newauthor".to_string()));
259 assert_eq!(endpoint.comment, Some("new comment".to_string()));
260
261 // Test property deletion
262 update_endpoint(
263 &mut config,
264 "sendmail-endpoint",
265 &Default::default(),
266 Some(&[
267 DeleteableSendmailProperty::FromAddress,
268 DeleteableSendmailProperty::Author,
269 ]),
270 None,
271 )?;
272
273 let endpoint = get_endpoint(&config, "sendmail-endpoint")?;
274
275 assert_eq!(endpoint.from_address, None);
276 assert_eq!(endpoint.author, None);
277
278 Ok(())
279 }
280
281 #[test]
1a75668d 282 fn test_sendmail_delete() -> Result<(), HttpError> {
21c5c9a0
LW
283 let mut config = empty_config();
284 add_sendmail_endpoint_for_test(&mut config, "sendmail-endpoint")?;
285
286 delete_endpoint(&mut config, "sendmail-endpoint")?;
287 assert!(delete_endpoint(&mut config, "sendmail-endpoint").is_err());
21c5c9a0
LW
288
289 Ok(())
290 }
291}