@@ -57,6 +57,18 @@ pub struct PolkitBackendConfig { |
| 57 | type Subject = (String, HashMap<String, OwnedValue>); | 57 | type Subject = (String, HashMap<String, OwnedValue>); |
| 58 | type Details = HashMap<String, String>; | 58 | type Details = HashMap<String, String>; |
| 59 | type TemporaryAuthorization = (String, String, Subject, u64, u64); | 59 | type TemporaryAuthorization = (String, String, Subject, u64, u64); |
| | 60 | +type ActionDescription = ( |
| | 61 | + String, |
| | 62 | + String, |
| | 63 | + String, |
| | 64 | + String, |
| | 65 | + String, |
| | 66 | + String, |
| | 67 | + u32, |
| | 68 | + u32, |
| | 69 | + u32, |
| | 70 | + Details, |
| | 71 | +); |
| 60 | | 72 | |
| 61 | #[derive(Debug, Clone, serde::Serialize)] | 73 | #[derive(Debug, Clone, serde::Serialize)] |
| 62 | pub struct TemporaryAuthorizationRecord { | 74 | pub struct TemporaryAuthorizationRecord { |
@@ -396,7 +408,8 @@ impl PolkitRuntime { |
| 396 | { | 408 | { |
| 397 | identity_options.insert(0, username.clone()); | 409 | identity_options.insert(0, username.clone()); |
| 398 | } | 410 | } |
| 399 | - let retention_options = retention_options_from_details(&request.details); | 411 | + let retention_options = |
| | 412 | + retention_options_with_policy_fallback(&request.action_id, &request.details); |
| 400 | | 413 | |
| 401 | tracing::debug!( | 414 | tracing::debug!( |
| 402 | identity_summary = %summarize_identities(&request.identities), | 415 | identity_summary = %summarize_identities(&request.identities), |
@@ -1189,6 +1202,61 @@ fn retention_options_from_details(details: &Details) -> Vec<RetentionPolicy> { |
| 1189 | options | 1202 | options |
| 1190 | } | 1203 | } |
| 1191 | | 1204 | |
| | 1205 | +fn retention_options_with_policy_fallback( |
| | 1206 | + action_id: &str, |
| | 1207 | + details: &Details, |
| | 1208 | +) -> Vec<RetentionPolicy> { |
| | 1209 | + let options = retention_options_from_details(details); |
| | 1210 | + if options.len() > 1 { |
| | 1211 | + return options; |
| | 1212 | + } |
| | 1213 | + |
| | 1214 | + match retention_options_from_action_policy(action_id) { |
| | 1215 | + Ok(Some(policy_options)) if policy_options.len() > 1 => policy_options, |
| | 1216 | + Ok(_) => options, |
| | 1217 | + Err(err) => { |
| | 1218 | + tracing::debug!( |
| | 1219 | + action_id = %action_id, |
| | 1220 | + error = %err, |
| | 1221 | + "Unable to infer retention options from polkit action policy" |
| | 1222 | + ); |
| | 1223 | + options |
| | 1224 | + } |
| | 1225 | + } |
| | 1226 | +} |
| | 1227 | + |
| | 1228 | +fn retention_options_from_action_policy(action_id: &str) -> Result<Option<Vec<RetentionPolicy>>> { |
| | 1229 | + let connection = Connection::system().context("failed to connect to system bus")?; |
| | 1230 | + let proxy = PolkitAgent::proxy(&connection)?; |
| | 1231 | + let actions: Vec<ActionDescription> = proxy.call("EnumerateActions", &"")?; |
| | 1232 | + Ok(retention_options_from_action_descriptions( |
| | 1233 | + action_id, &actions, |
| | 1234 | + )) |
| | 1235 | +} |
| | 1236 | + |
| | 1237 | +fn retention_options_from_action_descriptions( |
| | 1238 | + action_id: &str, |
| | 1239 | + actions: &[ActionDescription], |
| | 1240 | +) -> Option<Vec<RetentionPolicy>> { |
| | 1241 | + let mut options = vec![RetentionPolicy::OneShot]; |
| | 1242 | + let (_, _, _, _, _, _, implicit_any, implicit_inactive, implicit_active, _) = |
| | 1243 | + actions.iter().find(|(id, ..)| id == action_id)?; |
| | 1244 | + |
| | 1245 | + if implicit_authorization_supports_retention(*implicit_any) |
| | 1246 | + || implicit_authorization_supports_retention(*implicit_inactive) |
| | 1247 | + || implicit_authorization_supports_retention(*implicit_active) |
| | 1248 | + { |
| | 1249 | + options.push(RetentionPolicy::Session); |
| | 1250 | + } |
| | 1251 | + |
| | 1252 | + Some(options) |
| | 1253 | +} |
| | 1254 | + |
| | 1255 | +fn implicit_authorization_supports_retention(value: u32) -> bool { |
| | 1256 | + // 3/4 are *_RETAINED variants in PolkitImplicitAuthorization. |
| | 1257 | + value == 3 || value == 4 |
| | 1258 | +} |
| | 1259 | + |
| 1192 | fn first_detail_value(details: &Details, keys: &[&str]) -> Option<String> { | 1260 | fn first_detail_value(details: &Details, keys: &[&str]) -> Option<String> { |
| 1193 | for key in keys { | 1261 | for key in keys { |
| 1194 | let Some(value) = details.get(*key) else { | 1262 | let Some(value) = details.get(*key) else { |
@@ -2311,6 +2379,47 @@ mod tests { |
| 2311 | ); | 2379 | ); |
| 2312 | } | 2380 | } |
| 2313 | | 2381 | |
| | 2382 | + #[test] |
| | 2383 | + fn retention_options_from_action_descriptions_support_retained_policies() { |
| | 2384 | + let actions = vec![( |
| | 2385 | + "com.gardesk.test".to_string(), |
| | 2386 | + "desc".to_string(), |
| | 2387 | + "msg".to_string(), |
| | 2388 | + "vendor".to_string(), |
| | 2389 | + "url".to_string(), |
| | 2390 | + "icon".to_string(), |
| | 2391 | + 0_u32, |
| | 2392 | + 0_u32, |
| | 2393 | + 4_u32, |
| | 2394 | + HashMap::new(), |
| | 2395 | + )]; |
| | 2396 | + |
| | 2397 | + let options = retention_options_from_action_descriptions("com.gardesk.test", &actions) |
| | 2398 | + .expect("action should resolve"); |
| | 2399 | + assert_eq!( |
| | 2400 | + options, |
| | 2401 | + vec![RetentionPolicy::OneShot, RetentionPolicy::Session] |
| | 2402 | + ); |
| | 2403 | + } |
| | 2404 | + |
| | 2405 | + #[test] |
| | 2406 | + fn retention_options_from_action_descriptions_handle_missing_action() { |
| | 2407 | + let actions = vec![( |
| | 2408 | + "com.gardesk.other".to_string(), |
| | 2409 | + "desc".to_string(), |
| | 2410 | + "msg".to_string(), |
| | 2411 | + "vendor".to_string(), |
| | 2412 | + "url".to_string(), |
| | 2413 | + "icon".to_string(), |
| | 2414 | + 0_u32, |
| | 2415 | + 0_u32, |
| | 2416 | + 0_u32, |
| | 2417 | + HashMap::new(), |
| | 2418 | + )]; |
| | 2419 | + |
| | 2420 | + assert!(retention_options_from_action_descriptions("com.gardesk.test", &actions).is_none()); |
| | 2421 | + } |
| | 2422 | + |
| 2314 | #[test] | 2423 | #[test] |
| 2315 | fn parse_retention_selection_accepts_index_and_label() { | 2424 | fn parse_retention_selection_accepts_index_and_label() { |
| 2316 | let options = vec![ | 2425 | let options = vec![ |