feat(daemon): D-Bus interface with all 9 stub methods + system-bus wiring
Bundles plan tasks 1.8 (read methods), 1.9 (EnrollOwn/RemoveOwn with polkit gate), 1.10 (EnrollOther, SetPolicy, pending, recovery-code), and 1.11 (main.rs system-bus registration) — they land together because Polkit::System is only constructed by main.rs, so splitting them mid-implementation would require dead_code allows that immediately reverse. Adds: - daemon/src/dbus.rs — AuthForge struct + #[zbus::interface] impl with all 9 methods. Reads (ListCredentials, GetPolicy) are unauthenticated; writes call authz() which dispatches to polkit. Includes 9 integration tests via a tokio::net::UnixStream::pair p2p connection — no system bus needed for tests. - daemon/src/main.rs — connects to system bus, picks Polkit::system or Polkit::permissive based on AUTHFORGE_POLKIT_BYPASS env var, registers the AuthForge interface at /io/dangerousthings/AuthForge, requests well-known name io.dangerousthings.AuthForge, then parks forever. - daemon/src/polkit.rs — drop dead_code allows now that System is wired. - daemon/src/state.rs — gate has_pending() behind cfg(test); production reads go through the on-disk file in later phases, not this in-memory cache. - common/src/types.rs (formatting only via rustfmt). Tests: 14/14 daemon tests pass (4 state, 1 polkit, 9 dbus). 7/7 common tests pass. cargo clippy --workspace --all-targets -D warnings clean. cargo fmt clean.
This commit is contained in:
@@ -14,8 +14,6 @@ struct StateInner {
|
||||
pending: HashMap<String, PendingFlag>,
|
||||
}
|
||||
|
||||
// `dead_code` allow lifts in Task 1.8 when dbus.rs starts calling these methods.
|
||||
#[allow(dead_code)]
|
||||
impl AppState {
|
||||
pub fn with_fixtures() -> Self {
|
||||
let mut inner = StateInner::default();
|
||||
@@ -29,20 +27,36 @@ impl AppState {
|
||||
created_unix: 1_700_000_000,
|
||||
}],
|
||||
);
|
||||
Self { inner: RwLock::new(inner) }
|
||||
Self {
|
||||
inner: RwLock::new(inner),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_credentials(&self, user: &str) -> Vec<Credential> {
|
||||
self.inner.read().await.credentials.get(user).cloned().unwrap_or_default()
|
||||
self.inner
|
||||
.read()
|
||||
.await
|
||||
.credentials
|
||||
.get(user)
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub async fn add_credential(&self, user: &str, c: Credential) {
|
||||
self.inner.write().await.credentials.entry(user.to_string()).or_default().push(c);
|
||||
self.inner
|
||||
.write()
|
||||
.await
|
||||
.credentials
|
||||
.entry(user.to_string())
|
||||
.or_default()
|
||||
.push(c);
|
||||
}
|
||||
|
||||
pub async fn remove_credential(&self, user: &str, cred_id: &str) -> bool {
|
||||
let mut g = self.inner.write().await;
|
||||
let Some(list) = g.credentials.get_mut(user) else { return false };
|
||||
let Some(list) = g.credentials.get_mut(user) else {
|
||||
return false;
|
||||
};
|
||||
let before = list.len();
|
||||
list.retain(|c| c.id != cred_id);
|
||||
list.len() != before
|
||||
@@ -64,6 +78,10 @@ impl AppState {
|
||||
self.inner.write().await.pending.remove(user).is_some()
|
||||
}
|
||||
|
||||
/// Tests assert pending flag round-trips through SetPendingFlag/ClearPendingFlag
|
||||
/// via this read accessor. Production readers (PAM module, GUI status) come in
|
||||
/// later phases and will read the on-disk file directly, not this in-memory cache.
|
||||
#[cfg(test)]
|
||||
pub async fn has_pending(&self, user: &str) -> bool {
|
||||
self.inner.read().await.pending.contains_key(user)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user