feat(gui): pure-logic helpers for Policy tab (mode mapping, violation grouping)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ mod enroll_dialog;
|
|||||||
mod error;
|
mod error;
|
||||||
mod firstrun;
|
mod firstrun;
|
||||||
mod keys_page;
|
mod keys_page;
|
||||||
|
mod policy_form;
|
||||||
|
|
||||||
const APP_ID: &str = "io.dangerousthings.AuthForge";
|
const APP_ID: &str = "io.dangerousthings.AuthForge";
|
||||||
|
|
||||||
|
|||||||
92
gui/src/policy_form.rs
Normal file
92
gui/src/policy_form.rs
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
//! Pure helpers backing the Policy tab. Mode <-> combo index, violation
|
||||||
|
//! grouping. Widget code calls these; nothing here touches GTK.
|
||||||
|
//!
|
||||||
|
//! `dead_code` allowances mirror `bus.rs` — these helpers are wired through
|
||||||
|
//! `policy_page.rs` in Task 4; without the allow, this commit's `-D warnings`
|
||||||
|
//! gate would reject the staged-but-unused symbols.
|
||||||
|
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use authforge_common::types::{Mode, Violation};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
pub(crate) const MODE_LABELS: &[(&str, Mode)] = &[
|
||||||
|
("Disabled", Mode::Disabled),
|
||||||
|
("Optional", Mode::Optional),
|
||||||
|
("Required", Mode::Required),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub(crate) fn mode_to_combo_index(m: Mode) -> u32 {
|
||||||
|
match m {
|
||||||
|
Mode::Disabled => 0,
|
||||||
|
Mode::Optional => 1,
|
||||||
|
Mode::Required => 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn combo_index_to_mode(idx: u32) -> Mode {
|
||||||
|
match idx {
|
||||||
|
0 => Mode::Disabled,
|
||||||
|
1 => Mode::Optional,
|
||||||
|
_ => Mode::Required,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Group violations by stack, joining reasons with "; ". Used to render the
|
||||||
|
/// inline AdwBanner content when `SetPolicy(force=false)` returns violations.
|
||||||
|
pub(crate) fn group_violations_by_stack(vs: &[Violation]) -> BTreeMap<String, String> {
|
||||||
|
let mut by_stack: BTreeMap<String, Vec<String>> = BTreeMap::new();
|
||||||
|
for v in vs {
|
||||||
|
by_stack
|
||||||
|
.entry(v.stack.clone())
|
||||||
|
.or_default()
|
||||||
|
.push(format!("{}: {}", v.user, v.reason));
|
||||||
|
}
|
||||||
|
by_stack
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, vs)| (k, vs.join("; ")))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mode_combo_roundtrips() {
|
||||||
|
for m in [Mode::Disabled, Mode::Optional, Mode::Required] {
|
||||||
|
assert_eq!(combo_index_to_mode(mode_to_combo_index(m)), m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn out_of_range_index_clamps_to_required() {
|
||||||
|
assert_eq!(combo_index_to_mode(99), Mode::Required);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn violations_group_by_stack() {
|
||||||
|
let vs = vec![
|
||||||
|
Violation {
|
||||||
|
user: "alice".into(),
|
||||||
|
stack: "sudo".into(),
|
||||||
|
reason: "no fido2".into(),
|
||||||
|
},
|
||||||
|
Violation {
|
||||||
|
user: "bob".into(),
|
||||||
|
stack: "sudo".into(),
|
||||||
|
reason: "no fido2".into(),
|
||||||
|
},
|
||||||
|
Violation {
|
||||||
|
user: "alice".into(),
|
||||||
|
stack: "sshd".into(),
|
||||||
|
reason: "no fido2".into(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let g = group_violations_by_stack(&vs);
|
||||||
|
assert_eq!(g.len(), 2);
|
||||||
|
assert!(g.get("sudo").unwrap().contains("alice"));
|
||||||
|
assert!(g.get("sudo").unwrap().contains("bob"));
|
||||||
|
assert_eq!(g.get("sshd").unwrap(), "alice: no fido2");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user