feat: 完成 OpenNexus 第三阶段核心功能与生产化基础 #45
@@ -429,3 +429,12 @@ Core 的独立数据目录目前不等于已授权 Vault。Python 旧笔记写
|
|||||||
- 真实 AppContainer 双进程探针验证:本轮许可作废后约 56.90 ms、保险库锁定后约 47.80 ms、签发器释放后约 55.45 ms 观察到进程树清空;之后新工具调用均被拒绝。两秒测试租约自然到期也主动清空进程树。创建前撤销和创建后恢复前撤销均拒绝继续,无普通子进程降级。
|
- 真实 AppContainer 双进程探针验证:本轮许可作废后约 56.90 ms、保险库锁定后约 47.80 ms、签发器释放后约 55.45 ms 观察到进程树清空;之后新工具调用均被拒绝。两秒测试租约自然到期也主动清空进程树。创建前撤销和创建后恢复前撤销均拒绝继续,无普通子进程降级。
|
||||||
- 原生参数/环境探针已使用 prepare → create_suspended → 带租约恢复路径。52 项扩展回归通过,4 项 ignored 为三个父测试实际驱动的 Job 辅助入口及单独执行的 60 秒验收;全目标 Clippy -D warnings 通过。日志 `.build/extension-revocation-tests.log`、`.build/extension-revocation-clippy.log`。
|
- 原生参数/环境探针已使用 prepare → create_suspended → 带租约恢复路径。52 项扩展回归通过,4 项 ignored 为三个父测试实际驱动的 Job 辅助入口及单独执行的 60 秒验收;全目标 Clippy -D warnings 通过。日志 `.build/extension-revocation-tests.log`、`.build/extension-revocation-clippy.log`。
|
||||||
- 本轮锁定由真实 CredentialBroker.lock 触发,尚不是操作系统锁屏事件的桌面端到端测试。监视器提供有界撤销,并非对核验与 ResumeThread 之间竞态作“零指令执行”证明。Host 仍需把实际 Vault 切换、权限撤回、在线签名撤销等事件接入签发器/实例注册表,并完成 broker 与全部资源策略,整体生产化尚未完成。
|
- 本轮锁定由真实 CredentialBroker.lock 触发,尚不是操作系统锁屏事件的桌面端到端测试。监视器提供有界撤销,并非对核验与 ResumeThread 之间竞态作“零指令执行”证明。Host 仍需把实际 Vault 切换、权限撤回、在线签名撤销等事件接入签发器/实例注册表,并完成 broker 与全部资源策略,整体生产化尚未完成。
|
||||||
|
|
||||||
|
|
||||||
|
## 增量:Host Vault 与会话锁定事件接入撤销代次
|
||||||
|
|
||||||
|
- Host 增加持久于应用生命周期的 execution Authority;选择或打开不同 Vault、关闭/撤销 Vault 时推进其撤销代次,再替换工作区。重复打开当前 Vault 的既有早返回路径保持不变,打开新 Vault 失败也不会替换现有工作区;撤销请求在持久记录失败时仍先让旧执行许可失效。
|
||||||
|
- 手动 credentials_lock 先原子撤销 Host 执行许可和凭据会话,再等待 CredentialBroker 互斥锁进行数据清理。凭据原子信号在初始化时缓存于 OnceLock,撤销不再被正在执行的解锁/KDF 互斥锁阻塞。
|
||||||
|
- Windows SessionMonitor 支持有界多域信号组,锁屏、注销、控制台/远程断开同时撤销凭据与 Host execution Authority;解锁消息不会恢复原代次。线程创建失败返回稳定不可用错误,拒绝重复信号或超过 8 个域的配置。
|
||||||
|
- 8 项 Host 二进制测试、2 项原生会话消息测试、3 项许可测试通过,全目标 Clippy -D warnings 通过。验证在持有凭据互斥锁时手动锁定信号已发出、工作区替换/关闭推进代次,以及不轮换 HMAC 密钥只推进事件代次也能拒绝旧许可/租约。日志 `.build/extension-host-lifecycle-tests.log`、`.build/extension-host-session-tests.log`、`.build/extension-host-permit-tests.log`、`.build/extension-host-events-clippy.log`。
|
||||||
|
- 会话测试仅向本测试的隐藏窗口注入原生消息,未锁定用户桌面;尚未完成实际 OS 锁屏与已打包 Host/扩展实例的组合验收。第三方执行仍未开放,后续实例签发必须使用 Host 持有的 Authority,实际在线信任撤销、实例注册/派发、broker 与完整资源策略继续实施。
|
||||||
|
|||||||
@@ -198,6 +198,13 @@ impl Claims {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Authority {
|
impl Authority {
|
||||||
|
/// Host event wiring only; never expose this signal through IPC.
|
||||||
|
pub fn revocation_signal(&self) -> Arc<AtomicU64> {
|
||||||
|
Arc::clone(&self.generation)
|
||||||
|
}
|
||||||
|
pub fn revoke(&self) {
|
||||||
|
self.generation.fetch_add(1, Ordering::SeqCst);
|
||||||
|
}
|
||||||
/// Call only after consent and live trust validation. This authenticates the
|
/// Call only after consent and live trust validation. This authenticates the
|
||||||
/// decision; it does not establish sandbox availability or grant broker access.
|
/// decision; it does not establish sandbox availability or grant broker access.
|
||||||
pub fn issue(&self, claims: &Claims, now_ms: u64) -> Result<Permit> {
|
pub fn issue(&self, claims: &Claims, now_ms: u64) -> Result<Permit> {
|
||||||
@@ -246,7 +253,7 @@ impl Authority {
|
|||||||
/// Lock/logout/policy invalidation may discard all permits. Restart creates a
|
/// Lock/logout/policy invalidation may discard all permits. Restart creates a
|
||||||
/// fresh key, so an old process token cannot silently revive authorization.
|
/// fresh key, so an old process token cannot silently revive authorization.
|
||||||
pub fn invalidate_all(&mut self) {
|
pub fn invalidate_all(&mut self) {
|
||||||
self.generation.fetch_add(1, Ordering::SeqCst);
|
self.revoke();
|
||||||
self.key.zeroize();
|
self.key.zeroize();
|
||||||
rand::rngs::OsRng.fill_bytes(&mut self.key);
|
rand::rngs::OsRng.fill_bytes(&mut self.key);
|
||||||
}
|
}
|
||||||
@@ -333,4 +340,24 @@ mod tests {
|
|||||||
c.source = "http://catalog.example".into();
|
c.source = "http://catalog.example".into();
|
||||||
assert!(authority.issue(&c, 100).is_err());
|
assert!(authority.issue(&c, 100).is_err());
|
||||||
}
|
}
|
||||||
|
#[test]
|
||||||
|
fn external_host_signal_invalidates_mac_and_lease_without_key_rotation() {
|
||||||
|
let authority = Authority::default();
|
||||||
|
let claims = claims();
|
||||||
|
let old = authority.issue(&claims, 1).unwrap();
|
||||||
|
let lease = authority.lease(&old, &claims, 1).unwrap();
|
||||||
|
authority.revocation_signal().fetch_add(1, Ordering::SeqCst);
|
||||||
|
assert_eq!(
|
||||||
|
authority.verify(&old, &claims, 2).unwrap_err().code,
|
||||||
|
"EXTENSION_PERMIT_REVOKED"
|
||||||
|
);
|
||||||
|
assert_eq!(lease.check().unwrap_err().code, "EXTENSION_PERMIT_REVOKED");
|
||||||
|
let fresh = authority.issue(&claims, 2).unwrap();
|
||||||
|
authority.verify(&fresh, &claims, 3).unwrap();
|
||||||
|
authority.revoke();
|
||||||
|
assert_eq!(
|
||||||
|
authority.verify(&fresh, &claims, 3).unwrap_err().code,
|
||||||
|
"EXTENSION_PERMIT_REVOKED"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ struct Host {
|
|||||||
extensions: Arc<Mutex<Option<notesagent_host::extension_store::ExtensionStore>>>,
|
extensions: Arc<Mutex<Option<notesagent_host::extension_store::ExtensionStore>>>,
|
||||||
extension_reviews: extension_commands::Reviews,
|
extension_reviews: extension_commands::Reviews,
|
||||||
extension_requests: Requests,
|
extension_requests: Requests,
|
||||||
|
extension_authority: notesagent_host::extension_permit::Authority,
|
||||||
|
credential_signal: std::sync::OnceLock<Arc<std::sync::atomic::AtomicU64>>,
|
||||||
sync: Arc<sync_commands::Runtime>,
|
sync: Arc<sync_commands::Runtime>,
|
||||||
workspace: Arc<Mutex<Option<Workspace>>>,
|
workspace: Arc<Mutex<Option<Workspace>>>,
|
||||||
recent: Mutex<Option<RecentVaultStore>>,
|
recent: Mutex<Option<RecentVaultStore>>,
|
||||||
@@ -39,6 +41,28 @@ struct Host {
|
|||||||
streams: Arc<Mutex<HashMap<String, tauri::async_runtime::JoinHandle<()>>>>,
|
streams: Arc<Mutex<HashMap<String, tauri::async_runtime::JoinHandle<()>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Host {
|
||||||
|
fn replace_workspace(&self, active: &mut Option<Workspace>, next: Option<Workspace>) {
|
||||||
|
self.extension_authority.revoke();
|
||||||
|
self.sync.cancel();
|
||||||
|
*active = next;
|
||||||
|
}
|
||||||
|
fn lock_credentials(&self) -> Result<(), String> {
|
||||||
|
// These do not wait for an in-flight unlock/KDF or credential operation.
|
||||||
|
self.extension_authority.revoke();
|
||||||
|
if let Some(signal) = self.credential_signal.get() {
|
||||||
|
signal.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
self.credentials
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| "HOST_BUSY")?
|
||||||
|
.as_mut()
|
||||||
|
.ok_or("HOST_NOT_READY")?
|
||||||
|
.lock();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn info(ws: &Workspace) -> RecentVault {
|
fn info(ws: &Workspace) -> RecentVault {
|
||||||
RecentVault {
|
RecentVault {
|
||||||
vault_id: ws.vault_id.clone(),
|
vault_id: ws.vault_id.clone(),
|
||||||
@@ -433,13 +457,7 @@ async fn credentials_unlock(host: State<'_, Host>, password: String) -> Result<(
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn credentials_lock(host: State<'_, Host>) -> Result<(), String> {
|
fn credentials_lock(host: State<'_, Host>) -> Result<(), String> {
|
||||||
host.credentials
|
host.lock_credentials()
|
||||||
.lock()
|
|
||||||
.map_err(|_| "HOST_BUSY")?
|
|
||||||
.as_mut()
|
|
||||||
.ok_or("HOST_NOT_READY")?
|
|
||||||
.lock();
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -580,8 +598,7 @@ fn workspace_choose(host: State<'_, Host>) -> Result<Option<RecentVault>, String
|
|||||||
.as_mut()
|
.as_mut()
|
||||||
.ok_or("HOST_NOT_READY")?
|
.ok_or("HOST_NOT_READY")?
|
||||||
.remember(&result)?;
|
.remember(&result)?;
|
||||||
host.sync.cancel();
|
host.replace_workspace(&mut guard, Some(workspace));
|
||||||
*guard = Some(workspace);
|
|
||||||
Ok(Some(result))
|
Ok(Some(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -605,8 +622,7 @@ fn workspace_open(host: State<'_, Host>, path: String) -> Result<RecentVault, St
|
|||||||
}
|
}
|
||||||
let workspace = Workspace::open(Path::new(&authorized.path)).map_err(|e| e.code)?;
|
let workspace = Workspace::open(Path::new(&authorized.path)).map_err(|e| e.code)?;
|
||||||
let result = info(&workspace);
|
let result = info(&workspace);
|
||||||
host.sync.cancel();
|
host.replace_workspace(&mut guard, Some(workspace));
|
||||||
*guard = Some(workspace);
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -623,6 +639,7 @@ fn workspace_recent(host: State<'_, Host>) -> Result<Vec<RecentVault>, String> {
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn workspace_revoke(host: State<'_, Host>) -> Result<(), String> {
|
fn workspace_revoke(host: State<'_, Host>) -> Result<(), String> {
|
||||||
let mut workspace = host.workspace.lock().map_err(|_| "HOST_BUSY")?;
|
let mut workspace = host.workspace.lock().map_err(|_| "HOST_BUSY")?;
|
||||||
|
host.extension_authority.revoke();
|
||||||
if let Some(active) = workspace.as_ref() {
|
if let Some(active) = workspace.as_ref() {
|
||||||
host.recent
|
host.recent
|
||||||
.lock()
|
.lock()
|
||||||
@@ -631,8 +648,7 @@ fn workspace_revoke(host: State<'_, Host>) -> Result<(), String> {
|
|||||||
.ok_or("HOST_NOT_READY")?
|
.ok_or("HOST_NOT_READY")?
|
||||||
.revoke(&active.root)?;
|
.revoke(&active.root)?;
|
||||||
}
|
}
|
||||||
host.sync.cancel();
|
host.replace_workspace(&mut workspace, None);
|
||||||
*workspace = None;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -729,19 +745,27 @@ fn main() {
|
|||||||
.map_err(|_| std::io::Error::other("HOST_BUSY"))? = Some(CredentialBroker::new(
|
.map_err(|_| std::io::Error::other("HOST_BUSY"))? = Some(CredentialBroker::new(
|
||||||
app.path().app_data_dir()?.join("credentials/stronghold.v1"),
|
app.path().app_data_dir()?.join("credentials/stronghold.v1"),
|
||||||
));
|
));
|
||||||
|
let signal = credential_state
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| std::io::Error::other("HOST_BUSY"))?
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| std::io::Error::other("HOST_NOT_READY"))?
|
||||||
|
.lock_signal();
|
||||||
|
app.state::<Host>()
|
||||||
|
.credential_signal
|
||||||
|
.set(signal.clone())
|
||||||
|
.map_err(|_| std::io::Error::other("HOST_ALREADY_INITIALIZED"))?;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
let signal = credential_state
|
|
||||||
.lock()
|
|
||||||
.map_err(|_| std::io::Error::other("HOST_BUSY"))?
|
|
||||||
.as_ref()
|
|
||||||
.ok_or_else(|| std::io::Error::other("HOST_NOT_READY"))?
|
|
||||||
.lock_signal();
|
|
||||||
*app.state::<Host>()
|
*app.state::<Host>()
|
||||||
.session_monitor
|
.session_monitor
|
||||||
.lock()
|
.lock()
|
||||||
.map_err(|_| std::io::Error::other("HOST_BUSY"))? =
|
.map_err(|_| std::io::Error::other("HOST_BUSY"))? =
|
||||||
notesagent_host::session_lock::SessionMonitor::start(signal).ok();
|
notesagent_host::session_lock::SessionMonitor::start_many(vec![
|
||||||
|
signal,
|
||||||
|
app.state::<Host>().extension_authority.revocation_signal(),
|
||||||
|
])
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
let weak_credentials = Arc::downgrade(&credential_state);
|
let weak_credentials = Arc::downgrade(&credential_state);
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
@@ -908,3 +932,52 @@ fn main() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod lifecycle_tests {
|
||||||
|
use super::*;
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
|
#[test]
|
||||||
|
fn workspace_replacement_and_close_revoke_host_execution_generation() {
|
||||||
|
let host = Host::default();
|
||||||
|
let first = tempfile::tempdir().unwrap();
|
||||||
|
let second = tempfile::tempdir().unwrap();
|
||||||
|
let signal = host.extension_authority.revocation_signal();
|
||||||
|
let mut active = host.workspace.lock().unwrap();
|
||||||
|
host.replace_workspace(&mut active, Some(Workspace::open(first.path()).unwrap()));
|
||||||
|
let initial = signal.load(Ordering::SeqCst);
|
||||||
|
host.replace_workspace(&mut active, Some(Workspace::open(second.path()).unwrap()));
|
||||||
|
assert!(signal.load(Ordering::SeqCst) > initial);
|
||||||
|
let changed = signal.load(Ordering::SeqCst);
|
||||||
|
host.replace_workspace(&mut active, None);
|
||||||
|
assert!(active.is_none());
|
||||||
|
assert!(signal.load(Ordering::SeqCst) > changed);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn manual_lock_revokes_before_waiting_for_credential_mutex() {
|
||||||
|
let host = Host::default();
|
||||||
|
let temp = tempfile::tempdir().unwrap();
|
||||||
|
let broker = CredentialBroker::new(temp.path().join("credentials.v1"));
|
||||||
|
let credentials = broker.lock_signal();
|
||||||
|
host.credential_signal.set(credentials.clone()).unwrap();
|
||||||
|
*host.credentials.lock().unwrap() = Some(broker);
|
||||||
|
let extension = host.extension_authority.revocation_signal();
|
||||||
|
let held = host.credentials.lock().unwrap();
|
||||||
|
std::thread::scope(|scope| {
|
||||||
|
let worker = scope.spawn(|| host.lock_credentials());
|
||||||
|
let start = std::time::Instant::now();
|
||||||
|
while credentials.load(Ordering::SeqCst) == 0
|
||||||
|
&& start.elapsed() < Duration::from_secs(2)
|
||||||
|
{
|
||||||
|
std::thread::sleep(Duration::from_millis(1));
|
||||||
|
}
|
||||||
|
let observed = credentials.load(Ordering::SeqCst);
|
||||||
|
let revoked = extension.load(Ordering::SeqCst);
|
||||||
|
// Release before asserting, so an assertion cannot deadlock scope join.
|
||||||
|
drop(held);
|
||||||
|
worker.join().unwrap().unwrap();
|
||||||
|
assert!(observed > 0);
|
||||||
|
assert!(revoked > 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use windows_sys::Win32::{
|
|||||||
UI::WindowsAndMessaging::*,
|
UI::WindowsAndMessaging::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
thread_local! { static SIGNAL: RefCell<Option<Arc<AtomicU64>>> = const { RefCell::new(None) }; }
|
thread_local! { static SIGNALS: RefCell<Vec<Arc<AtomicU64>>> = const { RefCell::new(Vec::new()) }; }
|
||||||
|
|
||||||
unsafe extern "system" fn window_proc(
|
unsafe extern "system" fn window_proc(
|
||||||
hwnd: HWND,
|
hwnd: HWND,
|
||||||
@@ -25,8 +25,8 @@ unsafe extern "system" fn window_proc(
|
|||||||
WTS_SESSION_LOCK | WTS_SESSION_LOGOFF | WTS_CONSOLE_DISCONNECT | WTS_REMOTE_DISCONNECT
|
WTS_SESSION_LOCK | WTS_SESSION_LOGOFF | WTS_CONSOLE_DISCONNECT | WTS_REMOTE_DISCONNECT
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
SIGNAL.with(|s| {
|
SIGNALS.with(|s| {
|
||||||
if let Some(signal) = s.borrow().as_ref() {
|
for signal in s.borrow().iter() {
|
||||||
signal.fetch_add(1, Ordering::SeqCst);
|
signal.fetch_add(1, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -45,58 +45,73 @@ pub struct SessionMonitor {
|
|||||||
}
|
}
|
||||||
impl SessionMonitor {
|
impl SessionMonitor {
|
||||||
pub fn start(signal: Arc<AtomicU64>) -> Result<Self, String> {
|
pub fn start(signal: Arc<AtomicU64>) -> Result<Self, String> {
|
||||||
|
Self::start_many(vec![signal])
|
||||||
|
}
|
||||||
|
pub fn start_many(signals: Vec<Arc<AtomicU64>>) -> Result<Self, String> {
|
||||||
|
if signals.is_empty()
|
||||||
|
|| signals.len() > 8
|
||||||
|
|| signals
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.any(|(i, signal)| signals[..i].iter().any(|other| Arc::ptr_eq(signal, other)))
|
||||||
|
{
|
||||||
|
return Err("SESSION_MONITOR_UNAVAILABLE".into());
|
||||||
|
}
|
||||||
let (tx, rx) = std::sync::mpsc::sync_channel(1);
|
let (tx, rx) = std::sync::mpsc::sync_channel(1);
|
||||||
let thread = std::thread::spawn(move || unsafe {
|
let thread = std::thread::Builder::new()
|
||||||
SIGNAL.with(|s| *s.borrow_mut() = Some(signal));
|
.name("host-session-monitor".into())
|
||||||
let class: Vec<u16> = format!("OpenNexusSession-{}\0", uuid::Uuid::new_v4())
|
.spawn(move || unsafe {
|
||||||
.encode_utf16()
|
SIGNALS.with(|s| *s.borrow_mut() = signals);
|
||||||
.collect();
|
let class: Vec<u16> = format!("OpenNexusSession-{}\0", uuid::Uuid::new_v4())
|
||||||
let module = GetModuleHandleW(std::ptr::null());
|
.encode_utf16()
|
||||||
let descriptor = WNDCLASSW {
|
.collect();
|
||||||
lpfnWndProc: Some(window_proc),
|
let module = GetModuleHandleW(std::ptr::null());
|
||||||
hInstance: module,
|
let descriptor = WNDCLASSW {
|
||||||
lpszClassName: class.as_ptr(),
|
lpfnWndProc: Some(window_proc),
|
||||||
..std::mem::zeroed()
|
hInstance: module,
|
||||||
};
|
lpszClassName: class.as_ptr(),
|
||||||
if RegisterClassW(&descriptor) == 0 {
|
..std::mem::zeroed()
|
||||||
let _ = tx.send(Err("SESSION_MONITOR_UNAVAILABLE".to_string()));
|
};
|
||||||
return;
|
if RegisterClassW(&descriptor) == 0 {
|
||||||
}
|
let _ = tx.send(Err("SESSION_MONITOR_UNAVAILABLE".to_string()));
|
||||||
let window = CreateWindowExW(
|
return;
|
||||||
0,
|
}
|
||||||
class.as_ptr(),
|
let window = CreateWindowExW(
|
||||||
class.as_ptr(),
|
0,
|
||||||
0,
|
class.as_ptr(),
|
||||||
0,
|
class.as_ptr(),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
std::ptr::null_mut(),
|
0,
|
||||||
std::ptr::null_mut(),
|
0,
|
||||||
module,
|
std::ptr::null_mut(),
|
||||||
std::ptr::null(),
|
std::ptr::null_mut(),
|
||||||
);
|
module,
|
||||||
if window.is_null()
|
std::ptr::null(),
|
||||||
|| WTSRegisterSessionNotification(window, NOTIFY_FOR_THIS_SESSION) == 0
|
);
|
||||||
{
|
if window.is_null()
|
||||||
if !window.is_null() {
|
|| WTSRegisterSessionNotification(window, NOTIFY_FOR_THIS_SESSION) == 0
|
||||||
|
{
|
||||||
|
if !window.is_null() {
|
||||||
|
DestroyWindow(window);
|
||||||
|
}
|
||||||
|
UnregisterClassW(class.as_ptr(), module);
|
||||||
|
let _ = tx.send(Err("SESSION_MONITOR_UNAVAILABLE".to_string()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if tx.send(Ok(window as usize)).is_err() {
|
||||||
DestroyWindow(window);
|
DestroyWindow(window);
|
||||||
|
} else {
|
||||||
|
let mut message: MSG = std::mem::zeroed();
|
||||||
|
while GetMessageW(&mut message, std::ptr::null_mut(), 0, 0) > 0 {
|
||||||
|
TranslateMessage(&message);
|
||||||
|
DispatchMessageW(&message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
UnregisterClassW(class.as_ptr(), module);
|
UnregisterClassW(class.as_ptr(), module);
|
||||||
let _ = tx.send(Err("SESSION_MONITOR_UNAVAILABLE".to_string()));
|
})
|
||||||
return;
|
.map_err(|_| "SESSION_MONITOR_UNAVAILABLE".to_string())?;
|
||||||
}
|
|
||||||
if tx.send(Ok(window as usize)).is_err() {
|
|
||||||
DestroyWindow(window);
|
|
||||||
} else {
|
|
||||||
let mut message: MSG = std::mem::zeroed();
|
|
||||||
while GetMessageW(&mut message, std::ptr::null_mut(), 0, 0) > 0 {
|
|
||||||
TranslateMessage(&message);
|
|
||||||
DispatchMessageW(&message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
UnregisterClassW(class.as_ptr(), module);
|
|
||||||
});
|
|
||||||
match rx
|
match rx
|
||||||
.recv()
|
.recv()
|
||||||
.map_err(|_| "SESSION_MONITOR_UNAVAILABLE".to_string())?
|
.map_err(|_| "SESSION_MONITOR_UNAVAILABLE".to_string())?
|
||||||
@@ -150,4 +165,42 @@ mod tests {
|
|||||||
}
|
}
|
||||||
assert_eq!(signal.load(Ordering::SeqCst), 1);
|
assert_eq!(signal.load(Ordering::SeqCst), 1);
|
||||||
}
|
}
|
||||||
|
#[test]
|
||||||
|
fn native_lock_logoff_and_disconnect_revoke_every_bound_domain() {
|
||||||
|
let credential = Arc::new(AtomicU64::new(0));
|
||||||
|
let extension = Arc::new(AtomicU64::new(0));
|
||||||
|
assert!(SessionMonitor::start_many(vec![credential.clone(), credential.clone()]).is_err());
|
||||||
|
let monitor =
|
||||||
|
SessionMonitor::start_many(vec![credential.clone(), extension.clone()]).unwrap();
|
||||||
|
for (index, event) in [
|
||||||
|
WTS_SESSION_LOCK,
|
||||||
|
WTS_SESSION_LOGOFF,
|
||||||
|
WTS_CONSOLE_DISCONNECT,
|
||||||
|
WTS_REMOTE_DISCONNECT,
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
unsafe {
|
||||||
|
SendMessageW(
|
||||||
|
monitor.window as HWND,
|
||||||
|
WM_WTSSESSION_CHANGE,
|
||||||
|
event as usize,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert_eq!(credential.load(Ordering::SeqCst), index as u64 + 1);
|
||||||
|
assert_eq!(extension.load(Ordering::SeqCst), index as u64 + 1);
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
SendMessageW(
|
||||||
|
monitor.window as HWND,
|
||||||
|
WM_WTSSESSION_CHANGE,
|
||||||
|
WTS_SESSION_UNLOCK as usize,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert_eq!(credential.load(Ordering::SeqCst), 4);
|
||||||
|
assert_eq!(extension.load(Ordering::SeqCst), 4);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user