diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0988063..81ad511 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -110,7 +110,7 @@ jobs: - name: Sicherheits-Advisories pruefen working-directory: app/src-tauri - run: cargo audit + run: cargo audit --deny warnings build: name: Tauri-Build (Windows) diff --git a/app/src-tauri/.cargo/audit.toml b/app/src-tauri/.cargo/audit.toml new file mode 100644 index 0000000..5b945ea --- /dev/null +++ b/app/src-tauri/.cargo/audit.toml @@ -0,0 +1,26 @@ +# cargo-audit: gezielt ignorierte, geprüfte Advisories. Jeder Eintrag mit Begründung; +# bei naechster Dependency-Aktualisierung erneut pruefen (`cargo audit` ohne Ignore-Filter). +[advisories] +ignore = [ + # gtk-rs GTK3-Bindings: nur als Linux-Backend-Option in tauri/wry vorhanden, + # auf diesem Windows-only-Target (bundle.targets = ["msi"]) nie kompiliert/erreichbar. + "RUSTSEC-2024-0411", # gdkwayland-sys unmaintained + "RUSTSEC-2024-0412", # gdk unmaintained + "RUSTSEC-2024-0413", # atk unmaintained + "RUSTSEC-2024-0414", # gdkx11-sys unmaintained + "RUSTSEC-2024-0415", # gtk unmaintained + "RUSTSEC-2024-0416", # atk-sys unmaintained + "RUSTSEC-2024-0417", # gdkx11 unmaintained + "RUSTSEC-2024-0418", # gdk-sys unmaintained + "RUSTSEC-2024-0419", # gtk3-macros unmaintained + "RUSTSEC-2024-0420", # gtk-sys unmaintained + "RUSTSEC-2024-0429", # glib unsound iterator impl (selbe Linux-only GTK-Kette) + # Build-/Proc-Macro-Zeit, kein Laufzeitcode im Release-Binary. + "RUSTSEC-2024-0370", # proc-macro-error unmaintained + # Transitive Unicode-Hilfscrates, nur "unmaintained", kein bekannter Vuln. + "RUSTSEC-2025-0075", # unic-char-range + "RUSTSEC-2025-0080", # unic-common + "RUSTSEC-2025-0081", # unic-char-property + "RUSTSEC-2025-0098", # unic-ucd-version + "RUSTSEC-2025-0100", # unic-ucd-ident +] diff --git a/app/src-tauri/Cargo.lock b/app/src-tauri/Cargo.lock index 51347dd..148533e 100644 --- a/app/src-tauri/Cargo.lock +++ b/app/src-tauri/Cargo.lock @@ -43,9 +43,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" [[package]] name = "atk" @@ -1202,6 +1202,7 @@ dependencies = [ "serde_json", "tauri", "tauri-build", + "windows-sys 0.61.2", ] [[package]] diff --git a/app/src-tauri/Cargo.toml b/app/src-tauri/Cargo.toml index b25677e..55901af 100644 --- a/app/src-tauri/Cargo.toml +++ b/app/src-tauri/Cargo.toml @@ -20,6 +20,13 @@ serde_json = "1" chrono = { version = "0.4", features = ["clock"] } csv = "1" +[target.'cfg(windows)'.dependencies] +windows-sys = { version = "0.61", features = [ + "Win32_Foundation", + "Win32_System_SystemInformation", + "Win32_Security_Authentication_Identity", +] } + [profile.release] panic = "abort" codegen-units = 1 diff --git a/app/src-tauri/src/commands.rs b/app/src-tauri/src/commands.rs index 14408fc..2d9672f 100644 --- a/app/src-tauri/src/commands.rs +++ b/app/src-tauri/src/commands.rs @@ -1,6 +1,6 @@ //! Tauri-Befehle (Bruecke Frontend <-> Backend). Halten Geraeteliste & AD-Cache im State. use crate::ad; -use crate::identity::synth_sam; +use crate::identity::{current_user_domain, synth_sam}; use crate::model::*; use crate::store; use std::collections::BTreeSet; @@ -277,17 +277,3 @@ pub fn export_devices(state: State, format: String) -> Result (String, String) { - let user = std::env::var("USERNAME").unwrap_or_else(|_| "Unbekannt".into()); - let domain = std::env::var("USERDNSDOMAIN") - .or_else(|_| std::env::var("USERDOMAIN")) - .unwrap_or_else(|_| "corp.local".into()) - .to_lowercase(); - let full = format!( - "{}\\{}", - std::env::var("USERDOMAIN").unwrap_or_else(|_| "CORP".into()), - user - ); - (full, domain) -} diff --git a/app/src-tauri/src/commands_tests.rs b/app/src-tauri/src/commands_tests.rs index c4d9356..c6ae438 100644 --- a/app/src-tauri/src/commands_tests.rs +++ b/app/src-tauri/src/commands_tests.rs @@ -1,4 +1,20 @@ -use crate::identity::synth_sam; +use crate::identity::{current_user_domain, synth_sam}; + +#[test] +fn current_user_domain_never_returns_blank_identity() { + // Kann den exakten Wert nicht pruefen (haengt vom ausfuehrenden Konto ab, z. B. + // GitHub-Actions-Runner statt Domaenen-Benutzer) - stellt aber sicher, dass die + // Win32-Ermittlung (oder ihr Umgebungsvariablen-Fallback) nie leer/panisch ist, + // sondern immer eine anzeigbare "DOMAENE\Benutzer"-Kennung liefert. + let (full, domain) = current_user_domain(); + assert!(!full.trim().is_empty(), "Identitaet darf nicht leer sein"); + assert!( + full.contains('\\'), + "erwarte DOMAENE\\Benutzer-Format: {}", + full + ); + assert!(!domain.trim().is_empty(), "Domaene darf nicht leer sein"); +} #[test] fn synth_sam_transliterates_umlauts() { diff --git a/app/src-tauri/src/identity.rs b/app/src-tauri/src/identity.rs index b4a2dab..d2fee62 100644 --- a/app/src-tauri/src/identity.rs +++ b/app/src-tauri/src/identity.rs @@ -1,3 +1,95 @@ +/// Ermittelt die angemeldete Windows-Identitaet fuer den Zuordnungs-Audit-Trail +/// (`confirmedBy`/`updatedBy` in assignments.json) und die Sidebar-Anzeige. +/// Liest die SAM-kompatible Kennung ("DOMAENE\Benutzer") und die DNS-Domaene direkt aus dem +/// authentifizierten Token/System (statt aus `%USERNAME%`/`%USERDOMAIN%`-Umgebungsvariablen, +/// die vor dem Start des Prozesses beliebig gesetzt werden koennen). Faellt bei Fehlern +/// (z. B. nicht domaenengebundener Rechner) je Feld unabhaengig auf die bisherige, +/// umgebungsvariablenbasierte Ermittlung zurueck. +pub(crate) fn current_user_domain() -> (String, String) { + let full = windows_sam_compatible_name().unwrap_or_else(env_full_identity); + let domain = windows_dns_domain().unwrap_or_else(env_dns_domain); + (full, domain) +} + +fn env_full_identity() -> String { + format!( + "{}\\{}", + std::env::var("USERDOMAIN").unwrap_or_else(|_| "CORP".into()), + std::env::var("USERNAME").unwrap_or_else(|_| "Unbekannt".into()) + ) +} + +fn env_dns_domain() -> String { + std::env::var("USERDNSDOMAIN") + .or_else(|_| std::env::var("USERDOMAIN")) + .unwrap_or_else(|_| "corp.local".into()) + .to_lowercase() +} + +/// SAM-kompatible Anmeldekennung ("DOMAENE\Benutzer") ueber `GetUserNameExW`, direkt aus dem +/// Sicherheitstoken des laufenden Prozesses — nicht aus einer Umgebungsvariable ableitbar. +#[cfg(windows)] +fn windows_sam_compatible_name() -> Option { + use std::ffi::OsString; + use std::os::windows::ffi::OsStringExt; + use windows_sys::Win32::Security::Authentication::Identity::{ + GetUserNameExW, NameSamCompatible, + }; + + let mut buffer = [0u16; 256]; + let mut len = buffer.len() as u32; + let ok = unsafe { GetUserNameExW(NameSamCompatible, buffer.as_mut_ptr(), &mut len) }; + if !ok || len == 0 { + return None; + } + let name = OsString::from_wide(&buffer[..len as usize]) + .to_string_lossy() + .into_owned(); + if name.is_empty() { + None + } else { + Some(name) + } +} + +#[cfg(not(windows))] +fn windows_sam_compatible_name() -> Option { + None +} + +/// DNS-Domaene des Rechners ueber `GetComputerNameExW` (nur Anzeige in der Sidebar). +/// Auf einem nicht domaenengebundenen Rechner liefert die API erfolgreich einen leeren +/// String zurueck — das behandeln wir wie einen Fehlschlag, um auf den bisherigen +/// "corp.local"-Default auszuweichen statt eine leere Sidebar-Zeile zu zeigen. +#[cfg(windows)] +fn windows_dns_domain() -> Option { + use std::ffi::OsString; + use std::os::windows::ffi::OsStringExt; + use windows_sys::Win32::System::SystemInformation::{ + ComputerNameDnsDomain, GetComputerNameExW, + }; + + let mut buffer = [0u16; 256]; + let mut len = buffer.len() as u32; + let ok = unsafe { GetComputerNameExW(ComputerNameDnsDomain, buffer.as_mut_ptr(), &mut len) }; + if ok == 0 { + return None; + } + let name = OsString::from_wide(&buffer[..len as usize]) + .to_string_lossy() + .into_owned(); + if name.is_empty() { + None + } else { + Some(name.to_lowercase()) + } +} + +#[cfg(not(windows))] +fn windows_dns_domain() -> Option { + None +} + /// Leitet aus einem Anzeigenamen einen plausiblen SAM-Account ab — nur als /// CSV-Fallback, wenn kein AD verfuegbar ist. Deutsche Umlaute werden /// transliteriert, damit der Wert ASCII-stabil und deterministisch bleibt. diff --git a/app/src-tauri/tauri.conf.json b/app/src-tauri/tauri.conf.json index 83fa045..598b304 100644 --- a/app/src-tauri/tauri.conf.json +++ b/app/src-tauri/tauri.conf.json @@ -21,7 +21,7 @@ } ], "security": { - "csp": "default-src 'self'; connect-src 'self' ipc: https://ipc.localhost; style-src 'self' 'unsafe-inline'; font-src 'self'; img-src 'self';" + "csp": "default-src 'self'; connect-src 'self' ipc: https://ipc.localhost; style-src 'self'; font-src 'self'; img-src 'self'; object-src 'none'; base-uri 'self';" } }, "bundle": {