feat(daemon): EnrollTotp + IsTotpEnrolled + RevokeTotp D-Bus methods
This commit is contained in:
@@ -4,6 +4,8 @@ use authforge_common::policy::Policy;
|
|||||||
use authforge_common::types::{
|
use authforge_common::types::{
|
||||||
Credential, PendingFlag, PendingStatus, PolicyApplyResult, RecoveryCodeSummary,
|
Credential, PendingFlag, PendingStatus, PolicyApplyResult, RecoveryCodeSummary,
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "totp")]
|
||||||
|
use authforge_common::types::TotpEnrollment;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub struct AuthForge {
|
pub struct AuthForge {
|
||||||
@@ -174,6 +176,35 @@ impl AuthForge {
|
|||||||
.map_err(|e| zbus::fdo::Error::Failed(e.to_string()))
|
.map_err(|e| zbus::fdo::Error::Failed(e.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "totp")]
|
||||||
|
async fn enroll_totp(&self, user: String) -> zbus::fdo::Result<TotpEnrollment> {
|
||||||
|
self.authz("io.dangerousthings.AuthForge.enroll-totp")
|
||||||
|
.await?;
|
||||||
|
self.state
|
||||||
|
.enroll_totp(&user)
|
||||||
|
.await
|
||||||
|
.map_err(|e| zbus::fdo::Error::Failed(e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "totp")]
|
||||||
|
async fn is_totp_enrolled(&self, user: String) -> zbus::fdo::Result<bool> {
|
||||||
|
// No polkit gate — pure read; daemon's pattern is gates on writes only.
|
||||||
|
self.state
|
||||||
|
.is_totp_enrolled(&user)
|
||||||
|
.await
|
||||||
|
.map_err(|e| zbus::fdo::Error::Failed(e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "totp")]
|
||||||
|
async fn revoke_totp(&self, user: String) -> zbus::fdo::Result<bool> {
|
||||||
|
self.authz("io.dangerousthings.AuthForge.revoke-totp")
|
||||||
|
.await?;
|
||||||
|
self.state
|
||||||
|
.revoke_totp(&user)
|
||||||
|
.await
|
||||||
|
.map_err(|e| zbus::fdo::Error::Failed(e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
/// Emitted by main.rs whenever the inotify watcher on the policy.d/ dir
|
/// Emitted by main.rs whenever the inotify watcher on the policy.d/ dir
|
||||||
/// reports any change. Subscribers (GUI) reload via `GetPolicy`.
|
/// reports any change. Subscribers (GUI) reload via `GetPolicy`.
|
||||||
#[zbus(signal)]
|
#[zbus(signal)]
|
||||||
@@ -520,4 +551,42 @@ central_path = "{}"
|
|||||||
.expect("signal not received within 2s")
|
.expect("signal not received within 2s")
|
||||||
.expect("stream closed before signal");
|
.expect("stream closed before signal");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "totp")]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn enroll_totp_returns_otpauth_uri() {
|
||||||
|
let (_srv, client, _state, _tmp) = p2p_pair().await;
|
||||||
|
let p = proxy(&client).await;
|
||||||
|
let e: TotpEnrollment = p.call("EnrollTotp", &("alice",)).await.unwrap();
|
||||||
|
assert_eq!(e.user, "alice");
|
||||||
|
assert!(e.otpauth_uri.starts_with("otpauth://totp/AuthForge:alice?"));
|
||||||
|
assert!(e
|
||||||
|
.secret_b32
|
||||||
|
.chars()
|
||||||
|
.all(|c| c.is_ascii_uppercase() || c.is_ascii_digit()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "totp")]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn is_totp_enrolled_round_trips() {
|
||||||
|
let (_srv, client, _state, _tmp) = p2p_pair().await;
|
||||||
|
let p = proxy(&client).await;
|
||||||
|
let before: bool = p.call("IsTotpEnrolled", &("alice",)).await.unwrap();
|
||||||
|
assert!(!before);
|
||||||
|
let _: TotpEnrollment = p.call("EnrollTotp", &("alice",)).await.unwrap();
|
||||||
|
let after: bool = p.call("IsTotpEnrolled", &("alice",)).await.unwrap();
|
||||||
|
assert!(after);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "totp")]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn revoke_totp_removes_enrollment() {
|
||||||
|
let (_srv, client, _state, _tmp) = p2p_pair().await;
|
||||||
|
let p = proxy(&client).await;
|
||||||
|
let _: TotpEnrollment = p.call("EnrollTotp", &("alice",)).await.unwrap();
|
||||||
|
let removed: bool = p.call("RevokeTotp", &("alice",)).await.unwrap();
|
||||||
|
assert!(removed);
|
||||||
|
let after: bool = p.call("IsTotpEnrolled", &("alice",)).await.unwrap();
|
||||||
|
assert!(!after);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
debian/io.dangerousthings.AuthForge.policy
vendored
20
debian/io.dangerousthings.AuthForge.policy
vendored
@@ -97,4 +97,24 @@
|
|||||||
</defaults>
|
</defaults>
|
||||||
</action>
|
</action>
|
||||||
|
|
||||||
|
<action id="io.dangerousthings.AuthForge.enroll-totp">
|
||||||
|
<description>Enroll a TOTP secret</description>
|
||||||
|
<message>Authentication is required to enroll a TOTP secret.</message>
|
||||||
|
<defaults>
|
||||||
|
<allow_any>auth_self_keep</allow_any>
|
||||||
|
<allow_inactive>auth_admin_keep</allow_inactive>
|
||||||
|
<allow_active>auth_self_keep</allow_active>
|
||||||
|
</defaults>
|
||||||
|
</action>
|
||||||
|
|
||||||
|
<action id="io.dangerousthings.AuthForge.revoke-totp">
|
||||||
|
<description>Revoke a TOTP enrollment</description>
|
||||||
|
<message>Administrator authentication is required to revoke a TOTP enrollment.</message>
|
||||||
|
<defaults>
|
||||||
|
<allow_any>auth_admin_keep</allow_any>
|
||||||
|
<allow_inactive>auth_admin_keep</allow_inactive>
|
||||||
|
<allow_active>auth_admin_keep</allow_active>
|
||||||
|
</defaults>
|
||||||
|
</action>
|
||||||
|
|
||||||
</policyconfig>
|
</policyconfig>
|
||||||
|
|||||||
Reference in New Issue
Block a user