From 44361e0846ff8d3532371e997845dbc04510d727 Mon Sep 17 00:00:00 2001 From: Tyrese Luo Date: Fri, 28 Nov 2025 21:21:15 +0800 Subject: [PATCH 01/16] feat(authentication): add Linux support using zbus and polkit - Replace polkit and gio dependencies with zbus and zbus_polkit - Implement D-Bus-based authentication for Linux using polkit - Add complete Linux implementation module with proper error handling - Add Tyrese Luo as co-author to authentication crate - Update example to handle Linux async authentication pattern - Add comprehensive Linux usage documentation to README - Enable Linux target in sys.rs module --- Cargo.lock | 998 +++++++++++------- Cargo.toml | 7 +- crates/authentication/Cargo.toml | 7 +- crates/authentication/README.md | 67 ++ .../examples/simple_authentication.rs | 56 +- crates/authentication/src/sys.rs | 6 +- crates/authentication/src/sys/linux.rs | 84 -- crates/authentication/src/sys/linux/mod.rs | 137 +++ 8 files changed, 900 insertions(+), 462 deletions(-) delete mode 100644 crates/authentication/src/sys/linux.rs create mode 100644 crates/authentication/src/sys/linux/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 9fc54e4..5d5bafc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,10 +27,141 @@ dependencies = [ ] [[package]] -name = "anyhow" -version = "1.0.80" +name = "async-broadcast" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-signal" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" @@ -59,12 +190,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dfdb4953a096c551ce9ace855a604d702e6e62d77fac690575ae347571717f5" -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.9.1" @@ -80,6 +205,25 @@ dependencies = [ "objc2", ] +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + [[package]] name = "bytes" version = "1.5.0" @@ -101,22 +245,18 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" -[[package]] -name = "cfg-expr" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa50868b64a9a6fda9d593ce778849ea8715cd2a3d2cc17ffdb4a2f2f2f1961d" -dependencies = [ - "smallvec", - "target-lexicon", -] - [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "combine" version = "4.6.6" @@ -127,6 +267,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "dirs-sys" version = "0.5.0" @@ -136,7 +291,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -145,205 +300,157 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ - "bitflags 2.9.1", + "bitflags", "objc2", ] [[package]] -name = "equivalent" -version = "1.0.1" +name = "endi" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" [[package]] -name = "futures-channel" -version = "0.3.30" +name = "enumflags2" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" dependencies = [ - "futures-core", + "enumflags2_derive", + "serde", ] [[package]] -name = "futures-core" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" - -[[package]] -name = "futures-executor" -version = "0.3.30" +name = "enumflags2_derive" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ - "futures-core", - "futures-task", - "futures-util", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "futures-io" -version = "0.3.30" +name = "equivalent" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] -name = "futures-macro" -version = "0.3.30" +name = "errno" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", + "libc", + "windows-sys 0.61.2", ] [[package]] -name = "futures-task" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" - -[[package]] -name = "futures-util" -version = "0.3.30" +name = "event-listener" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ - "futures-core", - "futures-macro", - "futures-task", + "concurrent-queue", + "parking", "pin-project-lite", - "pin-utils", - "slab", ] [[package]] -name = "getrandom" -version = "0.2.14" +name = "event-listener-strategy" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ - "cfg-if", - "libc", - "wasi", + "event-listener", + "pin-project-lite", ] [[package]] -name = "gimli" -version = "0.29.0" +name = "fastrand" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] -name = "gio" -version = "0.17.0" +name = "futures-core" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1981edf8679d2f2c8ec3120015867f45aa0a1c2d5e3e129ca2f7dda174d3d2a9" -dependencies = [ - "bitflags 1.3.2", - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "gio-sys", - "glib", - "libc", - "once_cell", - "pin-project-lite", - "smallvec", - "thiserror 1.0.57", -] +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] -name = "gio-sys" -version = "0.17.10" +name = "futures-io" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ccf87c30a12c469b6d958950f6a9c09f2be20b7773f7e70d20b867fdf2628c3" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", - "winapi", -] +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] -name = "glib" -version = "0.17.10" +name = "futures-lite" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fad45ba8d4d2cea612b432717e834f48031cd8853c8aaf43b2c79fec8d144b" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ - "bitflags 1.3.2", - "futures-channel", + "fastrand", "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "memchr", - "once_cell", - "smallvec", - "thiserror 1.0.57", + "futures-io", + "parking", + "pin-project-lite", ] [[package]] -name = "glib-macros" -version = "0.17.10" +name = "getrandom" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca5c79337338391f1ab8058d6698125034ce8ef31b72a442437fa6c8580de26" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ - "anyhow", - "heck", - "proc-macro-crate", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", + "cfg-if", + "libc", + "wasi", ] [[package]] -name = "glib-sys" -version = "0.17.10" +name = "getrandom" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d80aa6ea7bba0baac79222204aa786a6293078c210abe69ef1336911d4bdc4f0" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ + "cfg-if", "libc", - "system-deps", + "r-efi", + "wasip2", ] [[package]] -name = "gobject-sys" -version = "0.17.10" +name = "gimli" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd34c3317740a6358ec04572c1bcfd3ac0b5b6529275fae255b237b314bb8062" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] -name = "heck" -version = "0.4.1" +name = "hermit-abi" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "indexmap" -version = "2.2.5" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", "hashbrown", @@ -355,7 +462,7 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" dependencies = [ - "bitflags 2.9.1", + "bitflags", "cfg-if", "libc", ] @@ -382,6 +489,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +[[package]] +name = "js-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "libc" version = "0.2.174" @@ -394,10 +511,16 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" dependencies = [ - "bitflags 2.9.1", + "bitflags", "libc", ] +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + [[package]] name = "log" version = "0.4.21" @@ -410,6 +533,15 @@ version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "miniz_oxide" version = "0.7.4" @@ -436,6 +568,19 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + [[package]] name = "objc2" version = "0.6.1" @@ -451,7 +596,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" dependencies = [ - "bitflags 2.9.1", + "bitflags", "objc2", "objc2-foundation", ] @@ -462,7 +607,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" dependencies = [ - "bitflags 2.9.1", + "bitflags", "dispatch2", "objc2", ] @@ -489,7 +634,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" dependencies = [ - "bitflags 2.9.1", + "bitflags", "objc2", "objc2-core-foundation", ] @@ -511,7 +656,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25b1312ad7bc8a0e92adae17aa10f90aae1fb618832f9b993b022b591027daed" dependencies = [ - "bitflags 2.9.1", + "bitflags", "block2", "objc2", "objc2-foundation", @@ -539,46 +684,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] -name = "pin-project-lite" -version = "0.2.13" +name = "ordered-stream" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] [[package]] -name = "pin-utils" -version = "0.1.0" +name = "parking" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] -name = "pkg-config" -version = "0.3.30" +name = "pin-project-lite" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] -name = "polkit" -version = "0.17.0" +name = "piper" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7866121c1e115212fd6e4eca8f84e03af65eda1a3d57babf849a946c791559c" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" dependencies = [ - "bitflags 1.3.2", - "gio", - "glib", - "polkit-sys", + "atomic-waker", + "fastrand", + "futures-io", ] [[package]] -name = "polkit-sys" -version = "0.17.0" +name = "polling" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9277a6580d2cd5b54f9dc428d4ee720e46ca6ba2e7a8b44c26282dbef6ecedf2" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", ] [[package]] @@ -589,36 +738,11 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro-crate" -version = "1.3.1" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", + "toml_edit", ] [[package]] @@ -632,13 +756,19 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.35" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" version = "0.8.5" @@ -666,7 +796,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.14", ] [[package]] @@ -675,7 +805,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ - "getrandom", + "getrandom 0.2.14", "libredox", "thiserror 2.0.12", ] @@ -706,16 +836,16 @@ dependencies = [ "android-build", "block2", "cfg-if", - "gio", "jni", "objc2", "objc2-foundation", "objc2-local-authentication", - "polkit", "retry", "robius-android-env", "windows", "windows-core", + "zbus", + "zbus_polkit", ] [[package]] @@ -766,6 +896,25 @@ version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "same-file" version = "1.0.6" @@ -777,31 +926,43 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.196" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.196" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] -name = "serde_spanned" -version = "0.6.5" +name = "serde_repr" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ - "serde", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -810,6 +971,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +dependencies = [ + "libc", +] + [[package]] name = "slab" version = "0.4.9" @@ -820,21 +990,10 @@ dependencies = [ ] [[package]] -name = "smallvec" -version = "1.13.1" +name = "static_assertions" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "syn" @@ -848,24 +1007,18 @@ dependencies = [ ] [[package]] -name = "system-deps" -version = "6.2.0" +name = "tempfile" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2d580ff6a20c55dfb86be5f9c238f67835d0e81cbdea8bf5680e0897320331" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ - "cfg-expr", - "heck", - "pkg-config", - "toml", - "version-compare", + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys 0.61.2", ] -[[package]] -name = "target-lexicon" -version = "0.12.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" - [[package]] name = "thiserror" version = "1.0.57" @@ -892,7 +1045,7 @@ checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -903,7 +1056,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -921,67 +1074,93 @@ dependencies = [ ] [[package]] -name = "toml" -version = "0.8.10" +name = "toml_datetime" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" dependencies = [ - "serde", - "serde_spanned", + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.23.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" +dependencies = [ + "indexmap", "toml_datetime", - "toml_edit 0.22.6", + "toml_parser", + "winnow", ] [[package]] -name = "toml_datetime" -version = "0.6.5" +name = "toml_parser" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" dependencies = [ - "serde", + "winnow", ] [[package]] -name = "toml_edit" -version = "0.19.15" +name = "tracing" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" dependencies = [ - "indexmap", - "toml_datetime", - "winnow 0.5.40", + "pin-project-lite", + "tracing-attributes", + "tracing-core", ] [[package]] -name = "toml_edit" -version = "0.22.6" +name = "tracing-attributes" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "winnow 0.6.5", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "unicode-ident" -version = "1.0.12" +name = "tracing-core" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +dependencies = [ + "once_cell", +] [[package]] -name = "version-compare" -version = "0.1.1" +name = "uds_windows" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset", + "tempfile", + "winapi", +] [[package]] -name = "version_check" -version = "0.9.4" +name = "unicode-ident" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "uuid" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] [[package]] name = "walkdir" @@ -999,6 +1178,60 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1060,7 +1293,7 @@ checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -1071,9 +1304,15 @@ checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-result" version = "0.1.1" @@ -1103,11 +1342,11 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.60.2" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-targets 0.53.2", + "windows-link", ] [[package]] @@ -1134,29 +1373,13 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.5", "windows_aarch64_msvc 0.52.5", "windows_i686_gnu 0.52.5", - "windows_i686_gnullvm 0.52.5", + "windows_i686_gnullvm", "windows_i686_msvc 0.52.5", "windows_x86_64_gnu 0.52.5", "windows_x86_64_gnullvm 0.52.5", "windows_x86_64_msvc 0.52.5", ] -[[package]] -name = "windows-targets" -version = "0.53.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" -dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", -] - [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -1169,12 +1392,6 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -1187,12 +1404,6 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -1205,24 +1416,12 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -1235,12 +1434,6 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -1253,12 +1446,6 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -1271,12 +1458,6 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -1290,25 +1471,130 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] -name = "winnow" -version = "0.5.40" +name = "zbus" +version = "5.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +checksum = "b622b18155f7a93d1cd2dc8c01d2d6a44e08fb9ebb7b3f9e6ed101488bad6c91" dependencies = [ - "memchr", + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "nix", + "ordered-stream", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow", + "zbus_macros", + "zbus_names", + "zvariant", ] [[package]] -name = "winnow" -version = "0.6.5" +name = "zbus_macros" +version = "5.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8" +checksum = "1cdb94821ca8a87ca9c298b5d1cbd80e2a8b67115d99f6e4551ac49e42b6a314" dependencies = [ - "memchr", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" +dependencies = [ + "serde", + "static_assertions", + "winnow", + "zvariant", +] + +[[package]] +name = "zbus_polkit" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad23d5c4d198c7e2641b33e6e0d1f866f117408ba66fe80bbe52e289eeb77c52" +dependencies = [ + "enumflags2", + "serde", + "serde_repr", + "static_assertions", + "zbus", +] + +[[package]] +name = "zvariant" +version = "5.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2be61892e4f2b1772727be11630a62664a1826b62efa43a6fe7449521cb8744c" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58575a1b2b20766513b1ec59d8e2e68db2745379f961f86650655e862d2006" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6949d142f89f6916deca2232cf26a8afacf2b9fdc35ce766105e104478be599" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn", + "winnow", ] diff --git a/Cargo.toml b/Cargo.toml index f1cc0ad..872c79d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,9 +66,10 @@ objc2-ui-kit = { version = "0.3.1", default-features = false, feat ## Linux-specific dependencies used in multiple crates in the workspace. -polkit = "=0.17.0" -gio = "=0.17.0" - +# polkit = "=0.17.0" +# gio = "=0.17.0" +zbus = "5.12.0" +zbus_polkit = "5.0.0" ## Windows-specific dependencies used in multiple crates in the workspace. windows-core = { version = "0.56.0", default-features = false } diff --git a/crates/authentication/Cargo.toml b/crates/authentication/Cargo.toml index cb1738c..586bce0 100644 --- a/crates/authentication/Cargo.toml +++ b/crates/authentication/Cargo.toml @@ -5,6 +5,7 @@ edition.workspace = true authors = [ "Kevin Boos ", "Klim Tsoutsman ", + "Tyrese Luo ", "Project Robius Maintainers", ] description = "Rust abstractions for multi-platform native authentication: biometrics, fingerprint, password, screen lock, TouchID, FaceID, Windows Hello, etc." @@ -50,8 +51,10 @@ objc2-foundation = { workspace = true, features = ["NSError", "NSString"] } # optional = true [target.'cfg(target_os = "linux")'.dependencies] -polkit.workspace = true -gio.workspace = true +# polkit.workspace = true +# gio.workspace = true +zbus.workspace = true +zbus_polkit = { workspace = true } [target.'cfg(target_os = "windows")'.dependencies] retry.workspace = true diff --git a/crates/authentication/README.md b/crates/authentication/README.md index 2546e9a..bbdcc63 100644 --- a/crates/authentication/README.md +++ b/crates/authentication/README.md @@ -30,6 +30,69 @@ To use this crate on Android, you must add the following to your app's `AndroidM ``` +## Usage on Linux + +On Linux, `robius-authentication` uses **polkit** to request authorization via the +desktop environment's native authentication prompt (GNOME/KDE/etc). + +> [!IMPORTANT] +> **Ensure a polkit agent is running** +> +> The prompt is displayed by a polkit authentication agent (GNOME/KDE usually start one automatically). +> If no agent is running (headless/SSH), no prompt will appear and auth will fail. + +### Add a polkit action policy file + + +#### Development & debugging + +During development and debugging, you can simplify the process using the following steps: + +1. Place the policy file within the project, for example in resources/com.yourapp.policy. + +2. Install it once on your development machine using sudo: + +```bash +sudo install -Dm644 resources/com.yourapp.policy \ + /usr/share/polkit-1/actions/com.yourapp.policy +``` + +3. Verify functionality with pkaction: + +```bash +pkaction --action-id com.yourapp.authenticate --verbose +``` + +Log back into your desktop session (or restart polkitd), then run your example. + + +Plicy file example: + +```xml + + + + Authenticate to use YourApp + Authentication is required + + auth_admin_keep + + + +```` +#### + +> [!TIP] +> Automatic addition during runtime is not recommended and should generally be avoided. +The reason is not technical feasibility, but rather security/distribution policy restrictions: +> +> polkit actions (.policy files) are system-level security policies. By design, they must be written by package managers or administrators during installation. Ordinary applications should not be allowed to modify system security configurations during runtime. +> +> Writing to `/usr/share/...` during runtime requires root privileges; allowing an app to elevate privileges to modify policy files is flagged as a security red flag by many distributions. +> +> The only recommended “automatic method” is installation-time automation. +This means packaging the `.policy` file alongside your deb/rpm/aur/flatpak package during installation. + ## Example ```rust @@ -37,7 +100,10 @@ use robius_authentication::{ AndroidText, BiometricStrength, Context, Policy, PolicyBuilder, Text, WindowsText, }; +// Linux ignores policy options like biometrics/password (kept for parity). let policy: Policy = PolicyBuilder::new() + // The action ID must match your `.policy`. + .action_id("com.yourapp.authenticate") // optional if using default .biometrics(Some(BiometricStrength::Strong)) .password(true) .companion(true) @@ -61,6 +127,7 @@ let callback = |auth_result| { } }; + Context::new(()) .authenticate(text, &policy, callback) .expect("Authentication failed"); diff --git a/crates/authentication/examples/simple_authentication.rs b/crates/authentication/examples/simple_authentication.rs index 45822dc..4513fe1 100644 --- a/crates/authentication/examples/simple_authentication.rs +++ b/crates/authentication/examples/simple_authentication.rs @@ -22,19 +22,47 @@ const TEXT: Text = Text { fn main() { let context = Context::new(()); - let res = context.authenticate( - TEXT, - &POLICY, - |result| match result { - Ok(_) => println!("Authentication successful"), - Err(e) => println!("Authentication failed: {:?}", e), - }, - ); - - // Note: if `res` is `Ok`, the authentication did not necessarily succeed. - // The callback will be called with the result of the authentication. - // If `res` is `Err`, it indicates an error in the authentication policy or context setup. - if let Err(e) = res { - eprintln!("Authentication failed: {:?}", e); + #[cfg(target_os = "linux")] + { + use std::sync::mpsc; + use std::time::Duration; + + let (tx, rx) = mpsc::channel(); + + let res = context.authenticate(TEXT, &POLICY, move |result| { + let _ = tx.send(result); + }); + + if let Err(e) = res { + eprintln!("Authentication request failed early: {:?}", e); + return; + } + + match rx.recv_timeout(Duration::from_secs(120)) { + Ok(Ok(_)) => println!("Authentication successful"), + Ok(Err(e)) => println!("Authentication failed: {:?}", e), + Err(_) => println!("Authentication timed out / callback not fired"), + } + + return; + } + + #[cfg(not(target_os = "linux"))] + { + let res = context.authenticate( + TEXT, + &POLICY, + |result| match result { + Ok(_) => println!("Authentication successful"), + Err(e) => println!("Authentication failed: {:?}", e), + }, + ); + + // Note: if `res` is `Ok`, the authentication did not necessarily succeed. + // The callback will be called with the result of the authentication. + // If `res` is `Err`, it indicates an error in the authentication policy or context setup. + if let Err(e) = res { + eprintln!("Authentication failed: {:?}", e); + } } } diff --git a/crates/authentication/src/sys.rs b/crates/authentication/src/sys.rs index f4d6b04..4c045c0 100644 --- a/crates/authentication/src/sys.rs +++ b/crates/authentication/src/sys.rs @@ -5,9 +5,9 @@ cfg_if::cfg_if! { } else if #[cfg(target_vendor = "apple")] { mod apple; pub(crate) use apple::*; - // } else if #[cfg(target_os = "linux")] { // linux is currently unsupported - // mod linux; - // pub(crate) use linux::*; + } else if #[cfg(target_os = "linux")] { + mod linux; + pub(crate) use linux::*; } else if #[cfg(target_os = "windows")] { mod windows; pub(crate) use windows::*; diff --git a/crates/authentication/src/sys/linux.rs b/crates/authentication/src/sys/linux.rs deleted file mode 100644 index 38ff76c..0000000 --- a/crates/authentication/src/sys/linux.rs +++ /dev/null @@ -1,84 +0,0 @@ -//! This module is not public yet because it is a work in progress. - -use polkit::{Authority, CheckAuthorizationFlags, Details, UnixProcess}; - -use crate::{BiometricStrength, Result}; - -#[derive(Debug)] -pub struct Policy; - -#[derive(Debug)] -pub(crate) struct PolicyBuilder; - -impl PolicyBuilder { - pub(crate) const fn new() -> Self { - Self - } - - pub(crate) const fn biometrics(self, _: Option) -> Self { - Self - } - - pub(crate) const fn password(self, _: bool) -> Self { - Self - } - - pub(crate) const fn companion(self, _: bool) -> Self { - Self - } - - pub(crate) const fn wrist_detection(self, _: bool) -> Self { - Self - } - - pub(crate) const fn build(self) -> Option { - Some(Policy) - } -} - -pub(crate) async fn authenticate_async(_message: &str, _: &Policy) -> Result<()> { - unimplemented!() -} - -pub(crate) fn blocking_authenticate(_message: &str, _: &Policy) -> Result<()> { - // TODO: None? - let authority = Authority::sync(Option::<&gio::Cancellable>::None).unwrap(); - - let current_user = "klim"; - - let details = Details::new(); - details.insert("user", Some(current_user)); - // TODO: user.gecos - details.insert("user.display", Some(current_user)); - // TODO: program - // TODO: command_line - details.insert("polkit.message", Some("Testing robius authentication")); - // TODO: polkit.gettext_domain - - // for action in authority - // .enumerate_actions_sync(Option::<&gio::Cancellable>::None) - // .unwrap() - // { - // println!("-- action --"); - // println!("id: {}", action.action_id()); - // println!("description: {}", action.description()); - // println!( - // "allow_gui: {:?}", - // action.annotation("org.freedesktop.policykit.exec.allow_gui") - // ); - // } - - let subject = UnixProcess::new(std::process::id() as i32); - authority - .check_authorization_sync( - &subject, - "org.hello-world.authenticate", - Some(&details), - // None, - CheckAuthorizationFlags::ALLOW_USER_INTERACTION, - Option::<&gio::Cancellable>::None, - ) - .unwrap(); - - Ok(()) -} diff --git a/crates/authentication/src/sys/linux/mod.rs b/crates/authentication/src/sys/linux/mod.rs new file mode 100644 index 0000000..6c497c8 --- /dev/null +++ b/crates/authentication/src/sys/linux/mod.rs @@ -0,0 +1,137 @@ +use std::collections::HashMap; +use std::thread; +use zbus::blocking::Connection; +use zbus_polkit::policykit1::{AuthorityProxyBlocking, CheckAuthorizationFlags, Subject}; + +use crate::{Error, Result, Text}; + +pub(crate) type RawContext = (); + +#[derive(Debug)] +pub(crate) struct Context; + +impl Context { + #[inline] + pub(crate) fn new(_: RawContext) -> Self { + Self + } + + pub(crate) fn authenticate( + &self, + _message: Text, + policy: &Policy, + callback: F, + ) -> Result<()> + where + F: Fn(Result<()>) + Send + 'static, + { + let action_id = policy.action_id; + + thread::spawn(move || { + let res = do_polkit_check(action_id); + callback(res); + }); + Ok(()) + } +} + +fn do_polkit_check(action_id: &'static str) -> Result<()> { + // 1) system bus (blocking) + let conn = Connection::system().map_err(|_| Error::Unavailable)?; + + // 2) polkit authority proxy (blocking) + let auth = AuthorityProxyBlocking::new(&conn).map_err(|_| Error::Unavailable)?; + + // 3) Subject: current process owner + let pid = std::process::id() as u32; + let subject = Subject::new_for_owner(pid, None, None) + .map_err(|_| Error::Unavailable)?; + + // 4) check authorization, allow interaction => system prompt + let result = auth + .check_authorization( + &subject, + action_id, + &HashMap::new(), + CheckAuthorizationFlags::AllowUserInteraction.into(), + "", + ) + .map_err(|e| map_polkit_error(e.to_string()))?; + + if result.is_authorized { + Ok(()) + } else { + Err(Error::Authentication) + } +} + +fn map_polkit_error(msg: String) -> Error { + let m = msg.to_lowercase(); + if m.contains("cancel") || m.contains("canceled") || m.contains("cancelled") { + Error::UserCanceled + } else if m.contains("locked") || m.contains("exhaust") || m.contains("too many") { + Error::Exhausted + } else if m.contains("unavailable") || m.contains("no agent") || m.contains("not supported") { + Error::Unavailable + } else { + Error::Authentication + } +} + +const DEFAULT_ACTION_ID: &str = "com.yourapp.authenticate"; + +/// Authentication policy on Linux. +/// Only action id matters. +#[derive(Debug, Clone)] +pub struct Policy { + pub(crate) action_id: &'static str, +} + +/// Policy builder for Linux. +/// polkit doesn't really understand "biometrics/password/companion" flags from mobile, +/// so we keep them for API consistency but only store an action id. +#[derive(Debug, Clone)] +pub struct PolicyBuilder { + action_id: Option<&'static str>, +} + +impl PolicyBuilder { + #[inline] + pub const fn new() -> Self { + Self { action_id: None } + } + + /// Optional: allow caller to override polkit action id. + #[inline] + pub const fn action_id(self, id: &'static str) -> Self { + Self { action_id: Some(id) } + } + + // The following are no-ops on Linux but kept for cross-platform API. + #[inline] + pub const fn biometrics(self, _strength: Option) -> Self { + self + } + #[inline] + pub const fn password(self, _password: bool) -> Self { + self + } + #[inline] + pub const fn companion(self, _companion: bool) -> Self { + self + } + #[inline] + pub const fn wrist_detection(self, _wrist: bool) -> Self { + self + } + + #[inline] + pub const fn build(self) -> Option { + Some(Policy { + action_id: match self.action_id { + Some(id) => id, + None => DEFAULT_ACTION_ID, + }, + }) + } +} \ No newline at end of file From db26e85a69365d389ca24fc3651349314ff2cd72 Mon Sep 17 00:00:00 2001 From: Tyrese Luo Date: Sun, 30 Nov 2025 16:03:25 +0800 Subject: [PATCH 02/16] Authentication Linux crate, polkit action ID requirement and caching tweaks - note Linux action ID requirement in example and expose PolicyBuilder::action_id on Linux - cache system bus and polkit authority, name auth thread, refine authorization result handling - make authentication Error clonable for reuse in cached results --- .../examples/simple_authentication.rs | 4 + crates/authentication/src/error.rs | 2 +- crates/authentication/src/lib.rs | 10 ++ crates/authentication/src/sys/linux/mod.rs | 114 +++++++++++------- 4 files changed, 88 insertions(+), 42 deletions(-) diff --git a/crates/authentication/examples/simple_authentication.rs b/crates/authentication/examples/simple_authentication.rs index 4513fe1..102d4b5 100644 --- a/crates/authentication/examples/simple_authentication.rs +++ b/crates/authentication/examples/simple_authentication.rs @@ -2,7 +2,11 @@ use robius_authentication::{ AndroidText, BiometricStrength, Context, Policy, PolicyBuilder, Text, WindowsText, }; +/// If you're targeting Linux, make sure to set an appropriate action ID +/// in your policy. This is required for Polkit authentication. +/// See more README.md (How to use in Linux). const POLICY: Policy = PolicyBuilder::new() + .action_id("com.yourapp.authenticate") .biometrics(Some(BiometricStrength::Strong)) .password(true) .companion(true) diff --git a/crates/authentication/src/error.rs b/crates/authentication/src/error.rs index 23d721a..d6f7aee 100644 --- a/crates/authentication/src/error.rs +++ b/crates/authentication/src/error.rs @@ -2,7 +2,7 @@ pub type Result = std::result::Result; /// An error produced during authentication. -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Error { // TODO: Reexport jni::errors::Error // TODO: Remove target cfg diff --git a/crates/authentication/src/lib.rs b/crates/authentication/src/lib.rs index 3fddb10..ce2899e 100644 --- a/crates/authentication/src/lib.rs +++ b/crates/authentication/src/lib.rs @@ -199,6 +199,16 @@ impl PolicyBuilder { } } + #[inline] + #[must_use] + #[cfg(target_os = "linux")] + pub const fn action_id(self, id: &'static str) -> Self { + Self { + inner: self.inner.action_id(id), + } + } + + /// Configures biometric authentication with the given strength. /// /// The strength only has an effect on Android, see [`BiometricStrength`] diff --git a/crates/authentication/src/sys/linux/mod.rs b/crates/authentication/src/sys/linux/mod.rs index 6c497c8..db4627f 100644 --- a/crates/authentication/src/sys/linux/mod.rs +++ b/crates/authentication/src/sys/linux/mod.rs @@ -1,7 +1,7 @@ -use std::collections::HashMap; +use std::{collections::HashMap, sync::OnceLock}; use std::thread; use zbus::blocking::Connection; -use zbus_polkit::policykit1::{AuthorityProxyBlocking, CheckAuthorizationFlags, Subject}; +use zbus_polkit::policykit1::{AuthorityProxyBlocking, AuthorizationResult, CheckAuthorizationFlags, Subject}; use crate::{Error, Result, Text}; @@ -18,7 +18,7 @@ impl Context { pub(crate) fn authenticate( &self, - _message: Text, + _message: Text, // _message is unused on Linux, see reasons below: auth.check_authorization(); policy: &Policy, callback: F, ) -> Result<()> @@ -26,28 +26,57 @@ impl Context { F: Fn(Result<()>) + Send + 'static, { let action_id = policy.action_id; - - thread::spawn(move || { - let res = do_polkit_check(action_id); - callback(res); - }); + // Here, we perform a simple and direct thread creation because the current calls are infrequent. + thread::Builder::new() + .name(String::from("robius-authentication-polkit")) + .spawn(move || { + let res = do_polkit_check(action_id); + callback(res); + }) + .map_err(|_| Error::Unavailable)?; Ok(()) } } -fn do_polkit_check(action_id: &'static str) -> Result<()> { - // 1) system bus (blocking) - let conn = Connection::system().map_err(|_| Error::Unavailable)?; +static SYSTEM_CONNECTION: OnceLock> = OnceLock::new(); // Cached system bus connection +static AUTHORITY_PROXY: OnceLock>> = OnceLock::new(); // Cached authority proxy + +pub(crate) fn get_system_connection() -> Result { + let r: &Result = SYSTEM_CONNECTION.get_or_init(|| { + Connection::system() + .map_err(|_| Error::Unavailable) + }); + + match r { + Ok(conn) => Ok(conn.clone()), + Err(e) => Err(e.clone()), + } +} + +pub(crate) fn get_authority_proxy() -> Result> { + let r: &Result> = AUTHORITY_PROXY.get_or_init(|| { + let conn = get_system_connection()?; + AuthorityProxyBlocking::new(&conn).map_err(|e| { + let _ = e; + Error::Unavailable + }) + }); - // 2) polkit authority proxy (blocking) - let auth = AuthorityProxyBlocking::new(&conn).map_err(|_| Error::Unavailable)?; + match r { + Ok(p) => Ok(p.clone()), + Err(e) => Err(e.clone()), + } +} + +fn do_polkit_check(action_id: &'static str) -> Result<()> { + // Get authority proxy, and it's cached for future use. + let auth = get_authority_proxy()?; - // 3) Subject: current process owner let pid = std::process::id() as u32; let subject = Subject::new_for_owner(pid, None, None) .map_err(|_| Error::Unavailable)?; - // 4) check authorization, allow interaction => system prompt + // If details is non-empty then the request will fail with POLKIT_ERROR_FAILED unless the process doing the check itsef is sufficiently authorized (e.g. running as uid 0). let result = auth .check_authorization( &subject, @@ -58,11 +87,24 @@ fn do_polkit_check(action_id: &'static str) -> Result<()> { ) .map_err(|e| map_polkit_error(e.to_string()))?; + map_authorization_result(result) +} + +fn map_authorization_result(result: AuthorizationResult) -> Result<()> { if result.is_authorized { - Ok(()) - } else { - Err(Error::Authentication) + return Ok(()); + } + + if result.details.keys().any(|k| k.contains("dismissed")) { + return Err(Error::UserCanceled); } + + if result.is_challenge { + // No agent available or UI interaction not possible even though we requested it. + return Err(Error::Unavailable); + } + + Err(Error::Authentication) } fn map_polkit_error(msg: String) -> Error { @@ -78,8 +120,6 @@ fn map_polkit_error(msg: String) -> Error { } } -const DEFAULT_ACTION_ID: &str = "com.yourapp.authenticate"; - /// Authentication policy on Linux. /// Only action id matters. #[derive(Debug, Clone)] @@ -88,8 +128,8 @@ pub struct Policy { } /// Policy builder for Linux. -/// polkit doesn't really understand "biometrics/password/companion" flags from mobile, -/// so we keep them for API consistency but only store an action id. +/// On Linux, a polkit action id MUST be explicitly provided. +/// If not set, build() returns None. #[derive(Debug, Clone)] pub struct PolicyBuilder { action_id: Option<&'static str>, @@ -101,7 +141,7 @@ impl PolicyBuilder { Self { action_id: None } } - /// Optional: allow caller to override polkit action id. + /// Required on Linux: caller must provide a polkit action id. #[inline] pub const fn action_id(self, id: &'static str) -> Self { Self { action_id: Some(id) } @@ -109,29 +149,21 @@ impl PolicyBuilder { // The following are no-ops on Linux but kept for cross-platform API. #[inline] - pub const fn biometrics(self, _strength: Option) -> Self { - self - } + pub const fn biometrics(self, _strength: Option) -> Self { self } #[inline] - pub const fn password(self, _password: bool) -> Self { - self - } + pub const fn password(self, _password: bool) -> Self { self } #[inline] - pub const fn companion(self, _companion: bool) -> Self { - self - } + pub const fn companion(self, _companion: bool) -> Self { self } #[inline] - pub const fn wrist_detection(self, _wrist: bool) -> Self { - self - } + pub const fn wrist_detection(self, _wrist: bool) -> Self { self } + /// Cross-platform API requirement: return Option. + /// Linux behavior: None if action_id is not explicitly set. #[inline] pub const fn build(self) -> Option { - Some(Policy { - action_id: match self.action_id { - Some(id) => id, - None => DEFAULT_ACTION_ID, - }, - }) + match self.action_id { + Some(id) => Some(Policy { action_id: id }), + None => None, + } } } \ No newline at end of file From dd6af1863aea1592bca613213950c2ce5a856fa8 Mon Sep 17 00:00:00 2001 From: Tyrese Luo Date: Sun, 30 Nov 2025 16:06:36 +0800 Subject: [PATCH 03/16] Add linux authentication screenshot --- crates/authentication/img/linux-screenshot.png | Bin 0 -> 17609 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 crates/authentication/img/linux-screenshot.png diff --git a/crates/authentication/img/linux-screenshot.png b/crates/authentication/img/linux-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..aa65299a3def2f0072954e08a6062322be6cbcab GIT binary patch literal 17609 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 Date: Wed, 17 Dec 2025 20:41:23 +0800 Subject: [PATCH 04/16] Update andclean up --- crates/authentication/Cargo.toml | 2 -- crates/authentication/examples/simple_authentication.rs | 1 - 2 files changed, 3 deletions(-) diff --git a/crates/authentication/Cargo.toml b/crates/authentication/Cargo.toml index 586bce0..acc0d26 100644 --- a/crates/authentication/Cargo.toml +++ b/crates/authentication/Cargo.toml @@ -51,8 +51,6 @@ objc2-foundation = { workspace = true, features = ["NSError", "NSString"] } # optional = true [target.'cfg(target_os = "linux")'.dependencies] -# polkit.workspace = true -# gio.workspace = true zbus.workspace = true zbus_polkit = { workspace = true } diff --git a/crates/authentication/examples/simple_authentication.rs b/crates/authentication/examples/simple_authentication.rs index 102d4b5..b37fb06 100644 --- a/crates/authentication/examples/simple_authentication.rs +++ b/crates/authentication/examples/simple_authentication.rs @@ -6,7 +6,6 @@ use robius_authentication::{ /// in your policy. This is required for Polkit authentication. /// See more README.md (How to use in Linux). const POLICY: Policy = PolicyBuilder::new() - .action_id("com.yourapp.authenticate") .biometrics(Some(BiometricStrength::Strong)) .password(true) .companion(true) From 3cee29d7afdd06aba871cace155e8b016261f705 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tyrese=20Luo=20=28=E7=BE=85=E5=81=A5=E5=B3=AF=29?= <150460738+tyreseluo@users.noreply.github.com> Date: Thu, 18 Dec 2025 09:14:16 +0800 Subject: [PATCH 05/16] Update crates/authentication/README.md Co-authored-by: Kevin Boos <1139460+kevinaboos@users.noreply.github.com> --- crates/authentication/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/authentication/README.md b/crates/authentication/README.md index bbdcc63..b489db0 100644 --- a/crates/authentication/README.md +++ b/crates/authentication/README.md @@ -66,7 +66,7 @@ pkaction --action-id com.yourapp.authenticate --verbose Log back into your desktop session (or restart polkitd), then run your example. -Plicy file example: +Policy file example: ```xml From c2b3ee2eb0108843997af005ba81d1df6887aaa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tyrese=20Luo=20=28=E7=BE=85=E5=81=A5=E5=B3=AF=29?= <150460738+tyreseluo@users.noreply.github.com> Date: Thu, 18 Dec 2025 09:19:41 +0800 Subject: [PATCH 06/16] Update crates/authentication/README.md Co-authored-by: Kevin Boos <1139460+kevinaboos@users.noreply.github.com> --- crates/authentication/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/authentication/README.md b/crates/authentication/README.md index b489db0..0b1eb93 100644 --- a/crates/authentication/README.md +++ b/crates/authentication/README.md @@ -100,7 +100,8 @@ use robius_authentication::{ AndroidText, BiometricStrength, Context, Policy, PolicyBuilder, Text, WindowsText, }; -// Linux ignores policy options like biometrics/password (kept for parity). +// Linux ignores policy options like biometrics/password, +// but it's best to keep them in for proper operation on other platforms. let policy: Policy = PolicyBuilder::new() // The action ID must match your `.policy`. .action_id("com.yourapp.authenticate") // optional if using default From 36116676639f5c9b195e4e569bdb1ae751bd5575 Mon Sep 17 00:00:00 2001 From: Tyrese Luo Date: Tue, 23 Dec 2025 14:04:35 +0800 Subject: [PATCH 07/16] Modify and fix some issues --- Cargo.toml | 2 - crates/authentication/README.md | 3 +- .../examples/simple_authentication.rs | 59 +++++-------------- crates/authentication/src/lib.rs | 10 +++- crates/authentication/src/sys/android/mod.rs | 4 ++ crates/authentication/src/sys/apple.rs | 4 ++ crates/authentication/src/sys/linux/mod.rs | 8 +-- crates/authentication/src/sys/unsupported.rs | 4 ++ crates/authentication/src/sys/windows/mod.rs | 4 ++ 9 files changed, 44 insertions(+), 54 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 872c79d..0d47690 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,8 +66,6 @@ objc2-ui-kit = { version = "0.3.1", default-features = false, feat ## Linux-specific dependencies used in multiple crates in the workspace. -# polkit = "=0.17.0" -# gio = "=0.17.0" zbus = "5.12.0" zbus_polkit = "5.0.0" diff --git a/crates/authentication/README.md b/crates/authentication/README.md index 0b1eb93..5b52e5e 100644 --- a/crates/authentication/README.md +++ b/crates/authentication/README.md @@ -37,13 +37,12 @@ desktop environment's native authentication prompt (GNOME/KDE/etc). > [!IMPORTANT] > **Ensure a polkit agent is running** -> +> > The prompt is displayed by a polkit authentication agent (GNOME/KDE usually start one automatically). > If no agent is running (headless/SSH), no prompt will appear and auth will fail. ### Add a polkit action policy file - #### Development & debugging During development and debugging, you can simplify the process using the following steps: diff --git a/crates/authentication/examples/simple_authentication.rs b/crates/authentication/examples/simple_authentication.rs index b37fb06..45822dc 100644 --- a/crates/authentication/examples/simple_authentication.rs +++ b/crates/authentication/examples/simple_authentication.rs @@ -2,9 +2,6 @@ use robius_authentication::{ AndroidText, BiometricStrength, Context, Policy, PolicyBuilder, Text, WindowsText, }; -/// If you're targeting Linux, make sure to set an appropriate action ID -/// in your policy. This is required for Polkit authentication. -/// See more README.md (How to use in Linux). const POLICY: Policy = PolicyBuilder::new() .biometrics(Some(BiometricStrength::Strong)) .password(true) @@ -25,47 +22,19 @@ const TEXT: Text = Text { fn main() { let context = Context::new(()); - #[cfg(target_os = "linux")] - { - use std::sync::mpsc; - use std::time::Duration; - - let (tx, rx) = mpsc::channel(); - - let res = context.authenticate(TEXT, &POLICY, move |result| { - let _ = tx.send(result); - }); - - if let Err(e) = res { - eprintln!("Authentication request failed early: {:?}", e); - return; - } - - match rx.recv_timeout(Duration::from_secs(120)) { - Ok(Ok(_)) => println!("Authentication successful"), - Ok(Err(e)) => println!("Authentication failed: {:?}", e), - Err(_) => println!("Authentication timed out / callback not fired"), - } - - return; - } - - #[cfg(not(target_os = "linux"))] - { - let res = context.authenticate( - TEXT, - &POLICY, - |result| match result { - Ok(_) => println!("Authentication successful"), - Err(e) => println!("Authentication failed: {:?}", e), - }, - ); - - // Note: if `res` is `Ok`, the authentication did not necessarily succeed. - // The callback will be called with the result of the authentication. - // If `res` is `Err`, it indicates an error in the authentication policy or context setup. - if let Err(e) = res { - eprintln!("Authentication failed: {:?}", e); - } + let res = context.authenticate( + TEXT, + &POLICY, + |result| match result { + Ok(_) => println!("Authentication successful"), + Err(e) => println!("Authentication failed: {:?}", e), + }, + ); + + // Note: if `res` is `Ok`, the authentication did not necessarily succeed. + // The callback will be called with the result of the authentication. + // If `res` is `Err`, it indicates an error in the authentication policy or context setup. + if let Err(e) = res { + eprintln!("Authentication failed: {:?}", e); } } diff --git a/crates/authentication/src/lib.rs b/crates/authentication/src/lib.rs index ce2899e..d9d3b8c 100644 --- a/crates/authentication/src/lib.rs +++ b/crates/authentication/src/lib.rs @@ -199,9 +199,17 @@ impl PolicyBuilder { } } + /// Sets the identifier of the action that requires authorization. + /// + /// On Linux systems, this typically maps to a polkit `action_id`, which + /// represents a specific privileged operation (such as modifying system + /// settings or powering off the device). The authentication backend uses + /// this identifier to determine whether the action is permitted and whether + /// user authentication is required. + /// + /// This only has an effect on linux. #[inline] #[must_use] - #[cfg(target_os = "linux")] pub const fn action_id(self, id: &'static str) -> Self { Self { inner: self.inner.action_id(id), diff --git a/crates/authentication/src/sys/android/mod.rs b/crates/authentication/src/sys/android/mod.rs index 5c38aea..94a55b0 100644 --- a/crates/authentication/src/sys/android/mod.rs +++ b/crates/authentication/src/sys/android/mod.rs @@ -127,6 +127,10 @@ impl PolicyBuilder { self } + pub(crate) const fn action_id(self, _: &'static str) -> Self { + self + } + pub(crate) const fn build(self) -> Option { if let Some(strength) = self.biometrics { return Some(Policy { diff --git a/crates/authentication/src/sys/apple.rs b/crates/authentication/src/sys/apple.rs index e4516ce..bbf5173 100644 --- a/crates/authentication/src/sys/apple.rs +++ b/crates/authentication/src/sys/apple.rs @@ -135,6 +135,10 @@ impl PolicyBuilder { } } + pub(crate) const fn action_id(self, _: &'static str) -> Self { + self + } + pub(crate) const fn build(self) -> Option { // TODO: Test watchos diff --git a/crates/authentication/src/sys/linux/mod.rs b/crates/authentication/src/sys/linux/mod.rs index db4627f..33ce8c9 100644 --- a/crates/authentication/src/sys/linux/mod.rs +++ b/crates/authentication/src/sys/linux/mod.rs @@ -149,13 +149,13 @@ impl PolicyBuilder { // The following are no-ops on Linux but kept for cross-platform API. #[inline] - pub const fn biometrics(self, _strength: Option) -> Self { self } + pub const fn biometrics(self, _: Option) -> Self { self } #[inline] - pub const fn password(self, _password: bool) -> Self { self } + pub const fn password(self, _: bool) -> Self { self } #[inline] - pub const fn companion(self, _companion: bool) -> Self { self } + pub const fn companion(self, _: bool) -> Self { self } #[inline] - pub const fn wrist_detection(self, _wrist: bool) -> Self { self } + pub const fn wrist_detection(self, _: bool) -> Self { self } /// Cross-platform API requirement: return Option. /// Linux behavior: None if action_id is not explicitly set. diff --git a/crates/authentication/src/sys/unsupported.rs b/crates/authentication/src/sys/unsupported.rs index 4671ae8..2a7b880 100644 --- a/crates/authentication/src/sys/unsupported.rs +++ b/crates/authentication/src/sys/unsupported.rs @@ -53,6 +53,10 @@ impl PolicyBuilder { Self } + pub(crate) const fn action_id(self, _: &'static str) -> Self { + self + } + pub(crate) const fn build(self) -> Option { None } diff --git a/crates/authentication/src/sys/windows/mod.rs b/crates/authentication/src/sys/windows/mod.rs index c2974e0..e76fda8 100644 --- a/crates/authentication/src/sys/windows/mod.rs +++ b/crates/authentication/src/sys/windows/mod.rs @@ -103,6 +103,10 @@ impl PolicyBuilder { self } + pub(crate) const fn action_id(self, _: &'static str) -> Self { + self + } + pub(crate) const fn build(self) -> Option { if self.valid { Some(Policy) From 79ad73b68ee5ad22689560bb2ae3c89ae90793f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tyrese=20Luo=20=28=E7=BE=85=E5=81=A5=E5=B3=AF=29?= <150460738+tyreseluo@users.noreply.github.com> Date: Tue, 23 Dec 2025 14:21:23 +0800 Subject: [PATCH 08/16] Update crates/authentication/src/sys/linux/mod.rs Co-authored-by: Kevin Boos <1139460+kevinaboos@users.noreply.github.com> --- crates/authentication/src/sys/linux/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/authentication/src/sys/linux/mod.rs b/crates/authentication/src/sys/linux/mod.rs index 33ce8c9..a657f1a 100644 --- a/crates/authentication/src/sys/linux/mod.rs +++ b/crates/authentication/src/sys/linux/mod.rs @@ -69,7 +69,7 @@ pub(crate) fn get_authority_proxy() -> Result> { } fn do_polkit_check(action_id: &'static str) -> Result<()> { - // Get authority proxy, and it's cached for future use. + // Get authority proxy (and cache it for future usage). let auth = get_authority_proxy()?; let pid = std::process::id() as u32; From fcb1463f121c0e896b3f7f31c56d56bea6fefaf0 Mon Sep 17 00:00:00 2001 From: Tyrese Luo Date: Tue, 23 Dec 2025 16:15:59 +0800 Subject: [PATCH 09/16] Add some comments --- crates/authentication/src/lib.rs | 3 +++ crates/authentication/src/sys/linux/mod.rs | 11 ----------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/crates/authentication/src/lib.rs b/crates/authentication/src/lib.rs index d9d3b8c..53255ca 100644 --- a/crates/authentication/src/lib.rs +++ b/crates/authentication/src/lib.rs @@ -178,6 +178,9 @@ pub enum BiometricStrength { /// when being requested to enable/disable various authentication methods. /// Enabling all options is the safest way to ensure that the authentication prompt /// will be displayed correctly on all platforms. +/// +/// On Linux, a polkit action id MUST be explicitly provided. +/// If not set, build() returns None. None if action_id is not explicitly set. #[derive(Debug)] pub struct PolicyBuilder { inner: sys::PolicyBuilder, diff --git a/crates/authentication/src/sys/linux/mod.rs b/crates/authentication/src/sys/linux/mod.rs index 33ce8c9..7dcf3e9 100644 --- a/crates/authentication/src/sys/linux/mod.rs +++ b/crates/authentication/src/sys/linux/mod.rs @@ -87,10 +87,6 @@ fn do_polkit_check(action_id: &'static str) -> Result<()> { ) .map_err(|e| map_polkit_error(e.to_string()))?; - map_authorization_result(result) -} - -fn map_authorization_result(result: AuthorizationResult) -> Result<()> { if result.is_authorized { return Ok(()); } @@ -120,16 +116,11 @@ fn map_polkit_error(msg: String) -> Error { } } -/// Authentication policy on Linux. -/// Only action id matters. #[derive(Debug, Clone)] pub struct Policy { pub(crate) action_id: &'static str, } -/// Policy builder for Linux. -/// On Linux, a polkit action id MUST be explicitly provided. -/// If not set, build() returns None. #[derive(Debug, Clone)] pub struct PolicyBuilder { action_id: Option<&'static str>, @@ -157,8 +148,6 @@ impl PolicyBuilder { #[inline] pub const fn wrist_detection(self, _: bool) -> Self { self } - /// Cross-platform API requirement: return Option. - /// Linux behavior: None if action_id is not explicitly set. #[inline] pub const fn build(self) -> Option { match self.action_id { From 2cd28014df63c944be0769ce9531a26568ea83e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tyrese=20Luo=20=28=E7=BE=85=E5=81=A5=E5=B3=AF=29?= Date: Tue, 23 Dec 2025 23:12:16 +0800 Subject: [PATCH 10/16] Update and resolve some issue --- crates/authentication/README.md | 92 +++---------------- .../examples/org.robius.authentication.policy | 23 +++++ .../examples/simple_authentication.rs | 4 + crates/authentication/src/lib.rs | 2 + crates/authentication/src/sys/linux/mod.rs | 57 ++++++------ 5 files changed, 67 insertions(+), 111 deletions(-) create mode 100644 crates/authentication/examples/org.robius.authentication.policy diff --git a/crates/authentication/README.md b/crates/authentication/README.md index 5b52e5e..85761de 100644 --- a/crates/authentication/README.md +++ b/crates/authentication/README.md @@ -41,99 +41,29 @@ desktop environment's native authentication prompt (GNOME/KDE/etc). > The prompt is displayed by a polkit authentication agent (GNOME/KDE usually start one automatically). > If no agent is running (headless/SSH), no prompt will appear and auth will fail. -### Add a polkit action policy file +### Write policy file. -#### Development & debugging +You can crate your own application's policy file, also can crate by template policy file. -During development and debugging, you can simplify the process using the following steps: +Template policy file see: [`./examples/org.robius.authentication.policy`](./examples/org.robius.authentication.policy) -1. Place the policy file within the project, for example in resources/com.yourapp.policy. +### Quick Test Mode ⚠️ -2. Install it once on your development machine using sudo: +Add the policy file to actions by manually executing the following command: ```bash -sudo install -Dm644 resources/com.yourapp.policy \ - /usr/share/polkit-1/actions/com.yourapp.policy +sudo install -Dm644 com.yourapp.policy /usr/share/polkit-1/actions/ ``` -3. Verify functionality with pkaction: +Then, ensure your policy file is correctly installed. ```bash -pkaction --action-id com.yourapp.authenticate --verbose +pkaction --action-id ``` -Log back into your desktop session (or restart polkitd), then run your example. - - -Policy file example: - -```xml - - - - Authenticate to use YourApp - Authentication is required - - auth_admin_keep - - - -```` -#### - -> [!TIP] -> Automatic addition during runtime is not recommended and should generally be avoided. -The reason is not technical feasibility, but rather security/distribution policy restrictions: -> -> polkit actions (.policy files) are system-level security policies. By design, they must be written by package managers or administrators during installation. Ordinary applications should not be allowed to modify system security configurations during runtime. -> -> Writing to `/usr/share/...` during runtime requires root privileges; allowing an app to elevate privileges to modify policy files is flagged as a security red flag by many distributions. +> During the test mode, you don't need to worry about the location of the policy file; just ensure it installs correctly. > -> The only recommended “automatic method” is installation-time automation. -This means packaging the `.policy` file alongside your deb/rpm/aur/flatpak package during installation. - -## Example - -```rust -use robius_authentication::{ - AndroidText, BiometricStrength, Context, Policy, PolicyBuilder, Text, WindowsText, -}; - -// Linux ignores policy options like biometrics/password, -// but it's best to keep them in for proper operation on other platforms. -let policy: Policy = PolicyBuilder::new() - // The action ID must match your `.policy`. - .action_id("com.yourapp.authenticate") // optional if using default - .biometrics(Some(BiometricStrength::Strong)) - .password(true) - .companion(true) - .build() - .unwrap(); - -let text = Text { - android: AndroidText { - title: "Title", - subtitle: None, - description: None, - }, - apple: "authenticate", - windows: WindowsText::new("Title", "Description"), -}; - -let callback = |auth_result| { - match auth_result { - Ok(_) => log::info!("Authentication success!"), - Err(_) => log::error!(Authentication failed!"), - } -}; - - -Context::new(()) - .authenticate(text, &policy, callback) - .expect("Authentication failed"); -``` +> You can also store it in advance under the unified packaging configuration folder (like `packaging`)to facilitate automatic installation of the policy file during release mode when users perform installation. -For more details about the prompt text, see the `Text` struct, -which allows you to customize the prompt for each platform. -[`polkit`]: https://www.freedesktop.org/software/polkit/docs/latest/polkit.8.html +### Release Mode \ No newline at end of file diff --git a/crates/authentication/examples/org.robius.authentication.policy b/crates/authentication/examples/org.robius.authentication.policy new file mode 100644 index 0000000..5804597 --- /dev/null +++ b/crates/authentication/examples/org.robius.authentication.policy @@ -0,0 +1,23 @@ + + + + Your vendor name + Your vendor url + + Your icon name + + + + Authenticate to use YourApp + + Authentication is required + + + no + + no + + auth_admin + + + \ No newline at end of file diff --git a/crates/authentication/examples/simple_authentication.rs b/crates/authentication/examples/simple_authentication.rs index 45822dc..1da2c9f 100644 --- a/crates/authentication/examples/simple_authentication.rs +++ b/crates/authentication/examples/simple_authentication.rs @@ -2,7 +2,11 @@ use robius_authentication::{ AndroidText, BiometricStrength, Context, Policy, PolicyBuilder, Text, WindowsText, }; +/// If you want to run this simple demo on linux, please ensure policy file installed correctly +/// and to setting your action id by .action_id() +///s See: README file `Usage on Linux` section. const POLICY: Policy = PolicyBuilder::new() + .action_id("org.robius.authentication") .biometrics(Some(BiometricStrength::Strong)) .password(true) .companion(true) diff --git a/crates/authentication/src/lib.rs b/crates/authentication/src/lib.rs index 53255ca..265efe8 100644 --- a/crates/authentication/src/lib.rs +++ b/crates/authentication/src/lib.rs @@ -141,6 +141,8 @@ impl Context { /// with a Result indicating whether authentication succeeded. /// Note that the callback may be not be called at all, /// but will always be called upon success. + /// + /// On Linux: message is unused. /// /// Thus, authentication failed if this function returns an error /// **OR** if the `callback` is invoked with `Err(_)`. diff --git a/crates/authentication/src/sys/linux/mod.rs b/crates/authentication/src/sys/linux/mod.rs index aad8b89..a9ca74f 100644 --- a/crates/authentication/src/sys/linux/mod.rs +++ b/crates/authentication/src/sys/linux/mod.rs @@ -1,6 +1,5 @@ use std::{collections::HashMap, sync::OnceLock}; -use std::thread; -use zbus::blocking::Connection; +use zbus::{blocking::Connection, fdo, Error as ZbusError}; use zbus_polkit::policykit1::{AuthorityProxyBlocking, AuthorizationResult, CheckAuthorizationFlags, Subject}; use crate::{Error, Result, Text}; @@ -18,7 +17,7 @@ impl Context { pub(crate) fn authenticate( &self, - _message: Text, // _message is unused on Linux, see reasons below: auth.check_authorization(); + _message: Text, policy: &Policy, callback: F, ) -> Result<()> @@ -26,14 +25,8 @@ impl Context { F: Fn(Result<()>) + Send + 'static, { let action_id = policy.action_id; - // Here, we perform a simple and direct thread creation because the current calls are infrequent. - thread::Builder::new() - .name(String::from("robius-authentication-polkit")) - .spawn(move || { - let res = do_polkit_check(action_id); - callback(res); - }) - .map_err(|_| Error::Unavailable)?; + let res = do_polkit_check(action_id); + callback(res); Ok(()) } } @@ -76,16 +69,34 @@ fn do_polkit_check(action_id: &'static str) -> Result<()> { let subject = Subject::new_for_owner(pid, None, None) .map_err(|_| Error::Unavailable)?; + let mut details = HashMap::new(); + details.insert("polkit.message", "Hello"); + + // If details is non-empty then the request will fail with POLKIT_ERROR_FAILED unless the process doing the check itsef is sufficiently authorized (e.g. running as uid 0). - let result = auth + let result: AuthorizationResult = auth .check_authorization( &subject, action_id, - &HashMap::new(), + &details, CheckAuthorizationFlags::AllowUserInteraction.into(), "", ) - .map_err(|e| map_polkit_error(e.to_string()))?; + .map_err(|err| match err { + ZbusError::MethodError(name, _, _) => match name.as_str() { + "org.freedesktop.PolicyKit1.Error.Cancelled" => Error::UserCanceled, + "org.freedesktop.PolicyKit1.Error.NotAuthorized" => Error::Authentication, + "org.freedesktop.PolicyKit1.Error.NotSupported" => Error::Unavailable, + "org.freedesktop.PolicyKit1.Error.NoAgent" => Error::Unavailable, + _ => Error::Authentication, + }, + ZbusError::FDO(fdo_err) => match *fdo_err { + fdo::Error::TimedOut(_) | fdo::Error::NoReply(_) => Error::Unavailable, + _ => Error::Authentication, + }, + ZbusError::InputOutput(_) => Error::Unavailable, + _ => Error::Authentication, + })?; if result.is_authorized { return Ok(()); @@ -103,19 +114,6 @@ fn do_polkit_check(action_id: &'static str) -> Result<()> { Err(Error::Authentication) } -fn map_polkit_error(msg: String) -> Error { - let m = msg.to_lowercase(); - if m.contains("cancel") || m.contains("canceled") || m.contains("cancelled") { - Error::UserCanceled - } else if m.contains("locked") || m.contains("exhaust") || m.contains("too many") { - Error::Exhausted - } else if m.contains("unavailable") || m.contains("no agent") || m.contains("not supported") { - Error::Unavailable - } else { - Error::Authentication - } -} - #[derive(Debug, Clone)] pub struct Policy { pub(crate) action_id: &'static str, @@ -132,7 +130,6 @@ impl PolicyBuilder { Self { action_id: None } } - /// Required on Linux: caller must provide a polkit action id. #[inline] pub const fn action_id(self, id: &'static str) -> Self { Self { action_id: Some(id) } @@ -152,7 +149,7 @@ impl PolicyBuilder { pub const fn build(self) -> Option { match self.action_id { Some(id) => Some(Policy { action_id: id }), - None => None, + None => Some(Policy { action_id: "Use" }), } } -} \ No newline at end of file +} From b32857b65b6cd9124388fa7697ffc47389426240 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tyrese=20Luo=20=28=E7=BE=85=E5=81=A5=E5=B3=AF=29?= Date: Wed, 24 Dec 2025 13:06:30 +0800 Subject: [PATCH 11/16] Update polkit checks asynchronously and harden Linux auth flow --- crates/authentication/src/sys/linux/mod.rs | 32 ++++++++++++++-------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/crates/authentication/src/sys/linux/mod.rs b/crates/authentication/src/sys/linux/mod.rs index a9ca74f..2505f07 100644 --- a/crates/authentication/src/sys/linux/mod.rs +++ b/crates/authentication/src/sys/linux/mod.rs @@ -25,8 +25,16 @@ impl Context { F: Fn(Result<()>) + Send + 'static, { let action_id = policy.action_id; - let res = do_polkit_check(action_id); - callback(res); + // CheckAuthorization may block (D-Bus round-trips and potential user interaction). + // Run it off the caller thread to avoid blocking UI/event-loop threads. + std::thread::Builder::new() + .name("robius-authentication-polkit".into()) + .spawn(move || { + let res = do_polkit_check(action_id); + callback(res); + }) + .map_err(|_| Error::Unavailable)?; + Ok(()) } } @@ -65,20 +73,17 @@ fn do_polkit_check(action_id: &'static str) -> Result<()> { // Get authority proxy (and cache it for future usage). let auth = get_authority_proxy()?; - let pid = std::process::id() as u32; - let subject = Subject::new_for_owner(pid, None, None) + // Use a unix-process subject including pid start-time and real uid. + // This avoids pid reuse ambiguity and matches polkit's recommended subject format. + let subject = Subject::new_for_owner(std::process::id(), None, None) .map_err(|_| Error::Unavailable)?; - let mut details = HashMap::new(); - details.insert("polkit.message", "Hello"); - - // If details is non-empty then the request will fail with POLKIT_ERROR_FAILED unless the process doing the check itsef is sufficiently authorized (e.g. running as uid 0). let result: AuthorizationResult = auth .check_authorization( &subject, action_id, - &details, + &HashMap::new(), CheckAuthorizationFlags::AllowUserInteraction.into(), "", ) @@ -102,7 +107,12 @@ fn do_polkit_check(action_id: &'static str) -> Result<()> { return Ok(()); } - if result.details.keys().any(|k| k.contains("dismissed")) { + if result + .details + .get("polkit.dismissed") + .map(|v| !v.is_empty()) + .unwrap_or(false) + { return Err(Error::UserCanceled); } @@ -149,7 +159,7 @@ impl PolicyBuilder { pub const fn build(self) -> Option { match self.action_id { Some(id) => Some(Policy { action_id: id }), - None => Some(Policy { action_id: "Use" }), + None => None, } } } From 337a39e85218301dd8793c0f4c8aa80fca1f529c Mon Sep 17 00:00:00 2001 From: Tyrese Luo Date: Sun, 28 Dec 2025 22:10:00 +0800 Subject: [PATCH 12/16] Improve Linux polkit docs, examples, and error handling - Document Release mode policy installation with cargo-packager - Clarify async auth flow in the CLI example - Tighten polkit error mapping/logging and internal visibility --- crates/authentication/README.md | 25 ++++++- .../examples/org.robius.authentication.policy | 1 + .../examples/simple_authentication.rs | 71 ++++++++++++++----- crates/authentication/src/sys/linux/mod.rs | 53 +++++++------- 4 files changed, 105 insertions(+), 45 deletions(-) diff --git a/crates/authentication/README.md b/crates/authentication/README.md index 85761de..2f13ca4 100644 --- a/crates/authentication/README.md +++ b/crates/authentication/README.md @@ -66,4 +66,27 @@ pkaction --action-id > You can also store it in advance under the unified packaging configuration folder (like `packaging`)to facilitate automatic installation of the policy file during release mode when users perform installation. -### Release Mode \ No newline at end of file +### Release Mode + +> The official polkit documentation explicitly states: Mechanisms should install action XML files to [/usr/share/polkit-1/actions](https://www.freedesktop.org/software/polkit/docs/latest/polkit.8.html). + + +As long as your packaging tool provides the capability to automatically install *.policy files under /usr/share/polkit-1/actions/. + +See the example below for use `cargo-packager`. + + +#### Use `cargo-packager` + +```toml +# https://docs.crabnebula.dev/packager/configuration/#debianconfig +[package.metadata.packager.deb] +depends = "./dist/depends_deb.txt" +desktop_template = "./packaging/robrix.desktop" +section = "utils" + +[package.metadata.packager.deb.files] +"./packaging/org.robius.authentication.policy" = "/usr/share/polkit-1/actions/org.robius.authentication.policy" +``` + +When you are packaging, `cargo-packager` automatically installs files to their target direactory. \ No newline at end of file diff --git a/crates/authentication/examples/org.robius.authentication.policy b/crates/authentication/examples/org.robius.authentication.policy index 5804597..8615f6e 100644 --- a/crates/authentication/examples/org.robius.authentication.policy +++ b/crates/authentication/examples/org.robius.authentication.policy @@ -1,3 +1,4 @@ + diff --git a/crates/authentication/examples/simple_authentication.rs b/crates/authentication/examples/simple_authentication.rs index 1da2c9f..c5a36fa 100644 --- a/crates/authentication/examples/simple_authentication.rs +++ b/crates/authentication/examples/simple_authentication.rs @@ -1,11 +1,44 @@ +//! # Recommended usage +//! +//! `Context::authenticate()` is asynchronous: it returns immediately after *starting* +//! authentication and delivers the final result via the callback. +//! +//! **In GUI applications**, you typically do not need any additional synchronization. +//! The application's event loop keeps the process alive while the user interacts with +//! the authentication prompt. +//! +//! ```no_run +//! let context = Context::new(()); +//! context.authenticate(TEXT, &POLICY, |result| { +//! match result { +//! Ok(()) => println!("Authentication successful"), +//! Err(e) => eprintln!("Authentication failed: {:?}", e), +//! } +//! }); +//! ``` +//! +//! # Why this example uses `mpsc::channel()` +//! +//! This file is a CLI-style demo. Without an event loop, `main()` would exit before the +//! authentication completes (especially on Linux where the polkit check runs in the +//! background). We therefore use an `mpsc::channel()` to wait for the callback. +//! +//! # Linux notes +//! +//! - Ensure the polkit policy file is installed for the chosen `action_id` +//! (see README: "Usage on Linux"). +//! - Ensure a polkit authentication agent is running in the current desktop session, +//! otherwise no prompt will appear and the request will fail with `NoAgent`/`Unavailable`. + +use std::sync::mpsc; + use robius_authentication::{ AndroidText, BiometricStrength, Context, Policy, PolicyBuilder, Text, WindowsText, }; -/// If you want to run this simple demo on linux, please ensure policy file installed correctly -/// and to setting your action id by .action_id() -///s See: README file `Usage on Linux` section. const POLICY: Policy = PolicyBuilder::new() + // On Linux You need to setting action_id + // See: ./org.robius.authentication.policy file settings and (README: "Usage on Linux"). .action_id("org.robius.authentication") .biometrics(Some(BiometricStrength::Strong)) .password(true) @@ -26,19 +59,23 @@ const TEXT: Text = Text { fn main() { let context = Context::new(()); - let res = context.authenticate( - TEXT, - &POLICY, - |result| match result { - Ok(_) => println!("Authentication successful"), - Err(e) => println!("Authentication failed: {:?}", e), - }, - ); - - // Note: if `res` is `Ok`, the authentication did not necessarily succeed. - // The callback will be called with the result of the authentication. - // If `res` is `Err`, it indicates an error in the authentication policy or context setup. + let (tx, rx) = mpsc::channel(); + + // Start authentication. `res` only indicates whether the request was successfully + // initiated; the final result is delivered via the callback. + let res = context.authenticate(TEXT, &POLICY, move |result| { + let _ = tx.send(result); + }); + if let Err(e) = res { - eprintln!("Authentication failed: {:?}", e); + eprintln!("Failed to start authentication: {:?}", e); + return; + } + + // Block until the callback produces a result. + match rx.recv() { + Ok(Ok(_)) => println!("Authentication successful"), + Ok(Err(e)) => println!("Authentication failed: {:?}", e), + Err(e) => eprintln!("Failed to receive auth result: {:?}", e), } -} +} \ No newline at end of file diff --git a/crates/authentication/src/sys/linux/mod.rs b/crates/authentication/src/sys/linux/mod.rs index 2505f07..f13a062 100644 --- a/crates/authentication/src/sys/linux/mod.rs +++ b/crates/authentication/src/sys/linux/mod.rs @@ -34,7 +34,6 @@ impl Context { callback(res); }) .map_err(|_| Error::Unavailable)?; - Ok(()) } } @@ -57,10 +56,7 @@ pub(crate) fn get_system_connection() -> Result { pub(crate) fn get_authority_proxy() -> Result> { let r: &Result> = AUTHORITY_PROXY.get_or_init(|| { let conn = get_system_connection()?; - AuthorityProxyBlocking::new(&conn).map_err(|e| { - let _ = e; - Error::Unavailable - }) + AuthorityProxyBlocking::new(&conn).map_err(|_| Error::Unavailable) }); match r { @@ -72,35 +68,40 @@ pub(crate) fn get_authority_proxy() -> Result> { fn do_polkit_check(action_id: &'static str) -> Result<()> { // Get authority proxy (and cache it for future usage). let auth = get_authority_proxy()?; + let details = HashMap::new(); // Use a unix-process subject including pid start-time and real uid. // This avoids pid reuse ambiguity and matches polkit's recommended subject format. let subject = Subject::new_for_owner(std::process::id(), None, None) .map_err(|_| Error::Unavailable)?; - // If details is non-empty then the request will fail with POLKIT_ERROR_FAILED unless the process doing the check itsef is sufficiently authorized (e.g. running as uid 0). + // If details is non-empty then the request will fail with POLKIT_ERROR_FAILED + // unless the process doing the check itsef is sufficiently authorized (e.g. running as uid 0). let result: AuthorizationResult = auth .check_authorization( &subject, action_id, - &HashMap::new(), + &details, CheckAuthorizationFlags::AllowUserInteraction.into(), "", ) - .map_err(|err| match err { - ZbusError::MethodError(name, _, _) => match name.as_str() { - "org.freedesktop.PolicyKit1.Error.Cancelled" => Error::UserCanceled, - "org.freedesktop.PolicyKit1.Error.NotAuthorized" => Error::Authentication, - "org.freedesktop.PolicyKit1.Error.NotSupported" => Error::Unavailable, - "org.freedesktop.PolicyKit1.Error.NoAgent" => Error::Unavailable, - _ => Error::Authentication, - }, - ZbusError::FDO(fdo_err) => match *fdo_err { - fdo::Error::TimedOut(_) | fdo::Error::NoReply(_) => Error::Unavailable, + .map_err(|err| { + eprintln!("polkit check_authorization error: {:?}", err); + match err { + ZbusError::MethodError(name, _, _) => match name.as_str() { + "org.freedesktop.PolicyKit1.Error.Cancelled" => Error::UserCanceled, + "org.freedesktop.PolicyKit1.Error.NotAuthorized" => Error::Authentication, + "org.freedesktop.PolicyKit1.Error.NotSupported" => Error::Unavailable, + "org.freedesktop.PolicyKit1.Error.NoAgent" => Error::Unavailable, + _ => Error::Authentication, + }, + ZbusError::FDO(fdo_err) => match *fdo_err { + fdo::Error::TimedOut(_) | fdo::Error::NoReply(_) => Error::Unavailable, + _ => Error::Authentication, + }, + ZbusError::InputOutput(_) => Error::Unavailable, _ => Error::Authentication, - }, - ZbusError::InputOutput(_) => Error::Unavailable, - _ => Error::Authentication, + } })?; if result.is_authorized { @@ -116,21 +117,19 @@ fn do_polkit_check(action_id: &'static str) -> Result<()> { return Err(Error::UserCanceled); } - if result.is_challenge { - // No agent available or UI interaction not possible even though we requested it. - return Err(Error::Unavailable); - } + // If we're not authorized, treat as authentication failure. + // "NoAgent" is handled as an error (org.freedesktop.PolicyKit1.Error.NoAgent). Err(Error::Authentication) } -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct Policy { pub(crate) action_id: &'static str, } -#[derive(Debug, Clone)] -pub struct PolicyBuilder { +#[derive(Debug)] +pub(crate) struct PolicyBuilder { action_id: Option<&'static str>, } From 27c0b2f12ed672c1fe3fba33250cf5216e78608d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tyrese=20Luo=20=28=E7=BE=85=E5=81=A5=E5=B3=AF=29?= Date: Tue, 30 Dec 2025 10:41:36 +0800 Subject: [PATCH 13/16] Update and fix some documents issues --- crates/authentication/README.md | 26 +++++++++++++------ .../examples/org.robius.authentication.policy | 2 +- crates/authentication/src/lib.rs | 6 ++--- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/crates/authentication/README.md b/crates/authentication/README.md index 2f13ca4..a3575f2 100644 --- a/crates/authentication/README.md +++ b/crates/authentication/README.md @@ -43,28 +43,37 @@ desktop environment's native authentication prompt (GNOME/KDE/etc). ### Write policy file. -You can crate your own application's policy file, also can crate by template policy file. +You can create your application's own policy file from scratch, or also create one from a template policy file. -Template policy file see: [`./examples/org.robius.authentication.policy`](./examples/org.robius.authentication.policy) +See here for an example template policy file: [`./examples/org.robius.authentication.policy`](./examples/org.robius.authentication.policy) + +> [!NOTE] +> +> A polkit policy file (`*.policy`) is an XML file that defines one or more authorization **actions** for your application. +polkit uses these definitions to determine whether a user is allowed to perform privileged operations. +> +> The “actions” directory (/usr/share/polkit-1/actions/) contains .policy files that define polkit authorization actions. ### Quick Test Mode ⚠️ -Add the policy file to actions by manually executing the following command: +#### Step 1. Install your policy file (`*.policy`) + +Install the policy file into the polkit `actions` directory, which is used to define authorization actions for the application. + +Manually execute the following command: ```bash sudo install -Dm644 com.yourapp.policy /usr/share/polkit-1/actions/ ``` +`polkit` loads policy definitions from /usr/share/polkit-1/actions/. -Then, ensure your policy file is correctly installed. +#### Step 2. Ensure your policy file was correctly installed. ```bash pkaction --action-id ``` > During the test mode, you don't need to worry about the location of the policy file; just ensure it installs correctly. -> -> You can also store it in advance under the unified packaging configuration folder (like `packaging`)to facilitate automatic installation of the policy file during release mode when users perform installation. - ### Release Mode @@ -73,8 +82,9 @@ pkaction --action-id As long as your packaging tool provides the capability to automatically install *.policy files under /usr/share/polkit-1/actions/. -See the example below for use `cargo-packager`. +This document provides example for when you are using `cargo-packager`. +See the example below for use `cargo-packager`. #### Use `cargo-packager` diff --git a/crates/authentication/examples/org.robius.authentication.policy b/crates/authentication/examples/org.robius.authentication.policy index 8615f6e..3fee48a 100644 --- a/crates/authentication/examples/org.robius.authentication.policy +++ b/crates/authentication/examples/org.robius.authentication.policy @@ -8,7 +8,7 @@ Your icon name - + Authenticate to use YourApp Authentication is required diff --git a/crates/authentication/src/lib.rs b/crates/authentication/src/lib.rs index 265efe8..15fcee2 100644 --- a/crates/authentication/src/lib.rs +++ b/crates/authentication/src/lib.rs @@ -141,7 +141,7 @@ impl Context { /// with a Result indicating whether authentication succeeded. /// Note that the callback may be not be called at all, /// but will always be called upon success. - /// + /// /// On Linux: message is unused. /// /// Thus, authentication failed if this function returns an error @@ -181,8 +181,8 @@ pub enum BiometricStrength { /// Enabling all options is the safest way to ensure that the authentication prompt /// will be displayed correctly on all platforms. /// -/// On Linux, a polkit action id MUST be explicitly provided. -/// If not set, build() returns None. None if action_id is not explicitly set. +/// On Linux, a polkit action ID *MUST* be explicitly provided. +/// If not set, `build()` will return None. #[derive(Debug)] pub struct PolicyBuilder { inner: sys::PolicyBuilder, From 493eec700f570f6807bc28bc0f00fd665548e260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tyrese=20Luo=20=28=E7=BE=85=E5=81=A5=E5=B3=AF=29?= Date: Tue, 30 Dec 2025 13:16:54 +0800 Subject: [PATCH 14/16] Update authentication(linux) validate polkit action IDs - replace action_id with action_ids allowlist - add InvalidActionId and Linux validation - update examples and policy template --- .../examples/org.robius.authentication.policy | 15 ++++- .../examples/simple_authentication.rs | 33 ++++++---- crates/authentication/src/error.rs | 2 + crates/authentication/src/lib.rs | 66 +++++++++++-------- crates/authentication/src/sys/android/mod.rs | 9 ++- crates/authentication/src/sys/apple.rs | 9 ++- crates/authentication/src/sys/linux/mod.rs | 42 ++++++++---- crates/authentication/src/sys/unsupported.rs | 9 ++- crates/authentication/src/sys/windows/mod.rs | 9 ++- 9 files changed, 137 insertions(+), 57 deletions(-) diff --git a/crates/authentication/examples/org.robius.authentication.policy b/crates/authentication/examples/org.robius.authentication.policy index 3fee48a..1c22f8c 100644 --- a/crates/authentication/examples/org.robius.authentication.policy +++ b/crates/authentication/examples/org.robius.authentication.policy @@ -1,5 +1,5 @@ - + Your vendor name @@ -21,4 +21,17 @@ auth_admin + + + + Authenticate to change settings + Authentication is required to change settings. + + no + no + auth_admin + + + + \ No newline at end of file diff --git a/crates/authentication/examples/simple_authentication.rs b/crates/authentication/examples/simple_authentication.rs index c5a36fa..ecb3fb5 100644 --- a/crates/authentication/examples/simple_authentication.rs +++ b/crates/authentication/examples/simple_authentication.rs @@ -33,19 +33,9 @@ use std::sync::mpsc; use robius_authentication::{ - AndroidText, BiometricStrength, Context, Policy, PolicyBuilder, Text, WindowsText, + AndroidText, BiometricStrength, Context, PolicyBuilder, Text, WindowsText, }; -const POLICY: Policy = PolicyBuilder::new() - // On Linux You need to setting action_id - // See: ./org.robius.authentication.policy file settings and (README: "Usage on Linux"). - .action_id("org.robius.authentication") - .biometrics(Some(BiometricStrength::Strong)) - .password(true) - .companion(true) - .build() - .unwrap(); - const TEXT: Text = Text { android: AndroidText { title: "Title", @@ -58,12 +48,29 @@ const TEXT: Text = Text { fn main() { let context = Context::new(()); + let mut policy = PolicyBuilder::new() + // On Linux You need to set action_ids. + // See: ./org.robius.authentication.policy file settings and (README: "Usage on Linux"). + .action_ids([ + "org.robius.authentication", + "org.robius.authentication.settings", + ]) + .biometrics(Some(BiometricStrength::Strong)) + .password(true) + .companion(true) + .build() + .unwrap(); + + if let Err(e) = policy.set_action_id("org.robius.authentication.settings") { + eprintln!("Invalid action_id: {:?}", e); + return; + } let (tx, rx) = mpsc::channel(); // Start authentication. `res` only indicates whether the request was successfully // initiated; the final result is delivered via the callback. - let res = context.authenticate(TEXT, &POLICY, move |result| { + let res = context.authenticate(TEXT, &policy, move |result| { let _ = tx.send(result); }); @@ -78,4 +85,4 @@ fn main() { Ok(Err(e)) => println!("Authentication failed: {:?}", e), Err(e) => eprintln!("Failed to receive auth result: {:?}", e), } -} \ No newline at end of file +} diff --git a/crates/authentication/src/error.rs b/crates/authentication/src/error.rs index d6f7aee..f8eb013 100644 --- a/crates/authentication/src/error.rs +++ b/crates/authentication/src/error.rs @@ -19,6 +19,8 @@ pub enum Error { Unavailable, /// The user canceled authentication. UserCanceled, + /// The provided action ID is not in the policy's allowed list. + InvalidActionId, // Apple-specific errors /// The app canceled authentication. diff --git a/crates/authentication/src/lib.rs b/crates/authentication/src/lib.rs index 15fcee2..acd9e79 100644 --- a/crates/authentication/src/lib.rs +++ b/crates/authentication/src/lib.rs @@ -46,21 +46,11 @@ //! .expect("authentication failed"); //! ``` //! -//! The `Policy` and `Text` structs can also be constructed at compile-time to -//! avoid run-time unwraps: +//! The `Text` struct can also be constructed at compile-time to avoid run-time unwraps: //! ``` //! #![feature(const_option)] //! -//! use robius_authentication::{ -//! AndroidText, BiometricStrength, Policy, PolicyBuilder, Text, WindowsText, -//! }; -//! -//! const POLICY: Policy = PolicyBuilder::new() -//! .biometrics(Some(BiometricStrength::Strong)) -//! .password(true) -//! .companion(true) -//! .build() -//! .unwrap(); +//! use robius_authentication::{AndroidText, Text, WindowsText}; //! //! const TEXT: Text = Text { //! android: AndroidText { @@ -181,8 +171,8 @@ pub enum BiometricStrength { /// Enabling all options is the safest way to ensure that the authentication prompt /// will be displayed correctly on all platforms. /// -/// On Linux, a polkit action ID *MUST* be explicitly provided. -/// If not set, `build()` will return None. +/// On Linux, at least one polkit action ID *MUST* be explicitly provided. +/// If not set (or empty), `build()` will return None. #[derive(Debug)] pub struct PolicyBuilder { inner: sys::PolicyBuilder, @@ -204,31 +194,40 @@ impl PolicyBuilder { } } - /// Sets the identifier of the action that requires authorization. + /// Sets the list of action identifiers that require authorization. /// - /// On Linux systems, this typically maps to a polkit `action_id`, which - /// represents a specific privileged operation (such as modifying system - /// settings or powering off the device). The authentication backend uses - /// this identifier to determine whether the action is permitted and whether + /// On Linux systems, these map to polkit `action_id`s, which represent + /// specific privileged operations (such as modifying system settings or + /// powering off the device). The authentication backend uses the current + /// action ID to determine whether the action is permitted and whether /// user authentication is required. /// + /// The first entry is used as the default action ID. To switch between + /// multiple action IDs at runtime on Linux, build a policy once and call + /// [`Policy::set_action_id`] before each authentication. The setter + /// validates against this list. + /// /// This only has an effect on linux. #[inline] #[must_use] - pub const fn action_id(self, id: &'static str) -> Self { + pub fn action_ids(self, ids: I) -> Self + where + I: IntoIterator, + S: Into, + { + let ids = ids.into_iter().map(Into::into).collect::>(); Self { - inner: self.inner.action_id(id), + inner: self.inner.action_ids(ids), } } - /// Configures biometric authentication with the given strength. /// /// The strength only has an effect on Android, see [`BiometricStrength`] /// for more details. #[inline] #[must_use] - pub const fn biometrics(self, strength: Option) -> Self { + pub fn biometrics(self, strength: Option) -> Self { Self { inner: self.inner.biometrics(strength), } @@ -237,7 +236,7 @@ impl PolicyBuilder { /// Sets whether the policy supports passwords. #[inline] #[must_use] - pub const fn password(self, password: bool) -> Self { + pub fn password(self, password: bool) -> Self { Self { inner: self.inner.password(password), } @@ -248,7 +247,7 @@ impl PolicyBuilder { /// This only has an effect on iOS and macOS. #[inline] #[must_use] - pub const fn companion(self, companion: bool) -> Self { + pub fn companion(self, companion: bool) -> Self { Self { inner: self.inner.companion(companion), } @@ -259,7 +258,7 @@ impl PolicyBuilder { /// This only has an effect on Apple watchOS. #[inline] #[must_use] - pub const fn wrist_detection(self, wrist_detection: bool) -> Self { + pub fn wrist_detection(self, wrist_detection: bool) -> Self { Self { inner: self.inner.wrist_detection(wrist_detection), } @@ -271,7 +270,7 @@ impl PolicyBuilder { /// current target. #[inline] #[must_use] - pub const fn build(self) -> Option { + pub fn build(self) -> Option { Some(Policy { inner: match self.inner.build() { Some(inner) => inner, @@ -286,3 +285,16 @@ impl PolicyBuilder { pub struct Policy { inner: sys::Policy, } + +impl Policy { + /// Sets the polkit action ID used on Linux. + /// + /// Returns [`Error::InvalidActionId`] if the ID is not in the allowed list + /// provided via [`PolicyBuilder::action_ids`]. + /// + /// This is a no-op on non-Linux platforms. + #[inline] + pub fn set_action_id(&mut self, id: impl Into) -> Result<()> { + self.inner.set_action_id(id.into()) + } +} diff --git a/crates/authentication/src/sys/android/mod.rs b/crates/authentication/src/sys/android/mod.rs index 94a55b0..be868a4 100644 --- a/crates/authentication/src/sys/android/mod.rs +++ b/crates/authentication/src/sys/android/mod.rs @@ -97,6 +97,13 @@ pub(crate) struct Policy { password: bool, } +impl Policy { + #[inline] + pub(crate) fn set_action_id(&mut self, _: String) -> Result<()> { + Ok(()) + } +} + #[derive(Debug)] pub(crate) struct PolicyBuilder { biometrics: Option, @@ -127,7 +134,7 @@ impl PolicyBuilder { self } - pub(crate) const fn action_id(self, _: &'static str) -> Self { + pub(crate) fn action_ids(self, _: Vec) -> Self { self } diff --git a/crates/authentication/src/sys/apple.rs b/crates/authentication/src/sys/apple.rs index bbf5173..9d062ce 100644 --- a/crates/authentication/src/sys/apple.rs +++ b/crates/authentication/src/sys/apple.rs @@ -89,6 +89,13 @@ pub(crate) struct Policy { inner: LAPolicy, } +impl Policy { + #[inline] + pub(crate) fn set_action_id(&mut self, _: String) -> Result<()> { + Ok(()) + } +} + #[derive(Debug)] pub(crate) struct PolicyBuilder { _biometrics: bool, @@ -135,7 +142,7 @@ impl PolicyBuilder { } } - pub(crate) const fn action_id(self, _: &'static str) -> Self { + pub(crate) fn action_ids(self, _: Vec) -> Self { self } diff --git a/crates/authentication/src/sys/linux/mod.rs b/crates/authentication/src/sys/linux/mod.rs index f13a062..9e0fede 100644 --- a/crates/authentication/src/sys/linux/mod.rs +++ b/crates/authentication/src/sys/linux/mod.rs @@ -24,13 +24,13 @@ impl Context { where F: Fn(Result<()>) + Send + 'static, { - let action_id = policy.action_id; + let action_id = policy.action_id.clone(); // CheckAuthorization may block (D-Bus round-trips and potential user interaction). // Run it off the caller thread to avoid blocking UI/event-loop threads. std::thread::Builder::new() .name("robius-authentication-polkit".into()) .spawn(move || { - let res = do_polkit_check(action_id); + let res = do_polkit_check(&action_id); callback(res); }) .map_err(|_| Error::Unavailable)?; @@ -65,7 +65,7 @@ pub(crate) fn get_authority_proxy() -> Result> { } } -fn do_polkit_check(action_id: &'static str) -> Result<()> { +fn do_polkit_check(action_id: &str) -> Result<()> { // Get authority proxy (and cache it for future usage). let auth = get_authority_proxy()?; let details = HashMap::new(); @@ -125,23 +125,36 @@ fn do_polkit_check(action_id: &'static str) -> Result<()> { #[derive(Debug)] pub struct Policy { - pub(crate) action_id: &'static str, + pub(crate) action_id: String, + allowed_action_ids: Vec, +} + +impl Policy { + #[inline] + pub(crate) fn set_action_id(&mut self, id: String) -> Result<()> { + if self.allowed_action_ids.iter().any(|allowed| allowed == &id) { + self.action_id = id; + Ok(()) + } else { + Err(Error::InvalidActionId) + } + } } #[derive(Debug)] pub(crate) struct PolicyBuilder { - action_id: Option<&'static str>, + action_ids: Option>, } impl PolicyBuilder { #[inline] pub const fn new() -> Self { - Self { action_id: None } + Self { action_ids: None } } #[inline] - pub const fn action_id(self, id: &'static str) -> Self { - Self { action_id: Some(id) } + pub fn action_ids(self, ids: Vec) -> Self { + Self { action_ids: Some(ids) } } // The following are no-ops on Linux but kept for cross-platform API. @@ -155,10 +168,15 @@ impl PolicyBuilder { pub const fn wrist_detection(self, _: bool) -> Self { self } #[inline] - pub const fn build(self) -> Option { - match self.action_id { - Some(id) => Some(Policy { action_id: id }), - None => None, + pub fn build(self) -> Option { + let action_ids = self.action_ids?; + if action_ids.is_empty() { + return None; } + let action_id = action_ids[0].clone(); + Some(Policy { + action_id, + allowed_action_ids: action_ids, + }) } } diff --git a/crates/authentication/src/sys/unsupported.rs b/crates/authentication/src/sys/unsupported.rs index 2a7b880..a9064e0 100644 --- a/crates/authentication/src/sys/unsupported.rs +++ b/crates/authentication/src/sys/unsupported.rs @@ -29,6 +29,13 @@ impl Context { #[derive(Debug)] pub(crate) struct Policy; +impl Policy { + #[inline] + pub(crate) fn set_action_id(&mut self, _: String) -> Result<()> { + Ok(()) + } +} + #[derive(Debug)] pub(crate) struct PolicyBuilder; @@ -53,7 +60,7 @@ impl PolicyBuilder { Self } - pub(crate) const fn action_id(self, _: &'static str) -> Self { + pub(crate) fn action_ids(self, _: Vec) -> Self { self } diff --git a/crates/authentication/src/sys/windows/mod.rs b/crates/authentication/src/sys/windows/mod.rs index e76fda8..61a1c0f 100644 --- a/crates/authentication/src/sys/windows/mod.rs +++ b/crates/authentication/src/sys/windows/mod.rs @@ -69,6 +69,13 @@ impl Context { #[derive(Debug)] pub(crate) struct Policy; +impl Policy { + #[inline] + pub(crate) fn set_action_id(&mut self, _: String) -> Result<()> { + Ok(()) + } +} + #[derive(Debug)] pub(crate) struct PolicyBuilder { valid: bool, @@ -103,7 +110,7 @@ impl PolicyBuilder { self } - pub(crate) const fn action_id(self, _: &'static str) -> Self { + pub(crate) fn action_ids(self, _: Vec) -> Self { self } From c1b092f67df5afc272984fea0262ab285b7efbf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tyrese=20Luo=20=28=E7=BE=85=E5=81=A5=E5=B3=AF=29?= Date: Tue, 30 Dec 2025 13:35:49 +0800 Subject: [PATCH 15/16] Authentication/linux: avoid caching dbus connection and polkit proxy --- crates/authentication/src/sys/linux/mod.rs | 35 +++++++--------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/crates/authentication/src/sys/linux/mod.rs b/crates/authentication/src/sys/linux/mod.rs index 9e0fede..eb53392 100644 --- a/crates/authentication/src/sys/linux/mod.rs +++ b/crates/authentication/src/sys/linux/mod.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, sync::OnceLock}; +use std::collections::HashMap; use zbus::{blocking::Connection, fdo, Error as ZbusError}; use zbus_polkit::policykit1::{AuthorityProxyBlocking, AuthorizationResult, CheckAuthorizationFlags, Subject}; @@ -38,36 +38,21 @@ impl Context { } } -static SYSTEM_CONNECTION: OnceLock> = OnceLock::new(); // Cached system bus connection -static AUTHORITY_PROXY: OnceLock>> = OnceLock::new(); // Cached authority proxy - pub(crate) fn get_system_connection() -> Result { - let r: &Result = SYSTEM_CONNECTION.get_or_init(|| { - Connection::system() - .map_err(|_| Error::Unavailable) - }); - - match r { - Ok(conn) => Ok(conn.clone()), - Err(e) => Err(e.clone()), - } + Connection::system().map_err(|_| Error::Unavailable) } -pub(crate) fn get_authority_proxy() -> Result> { - let r: &Result> = AUTHORITY_PROXY.get_or_init(|| { - let conn = get_system_connection()?; - AuthorityProxyBlocking::new(&conn).map_err(|_| Error::Unavailable) - }); - - match r { - Ok(p) => Ok(p.clone()), - Err(e) => Err(e.clone()), - } +pub(crate) fn get_authority_proxy( + conn: &Connection, +) -> Result> { + AuthorityProxyBlocking::new(conn).map_err(|_| Error::Unavailable) } fn do_polkit_check(action_id: &str) -> Result<()> { - // Get authority proxy (and cache it for future usage). - let auth = get_authority_proxy()?; + // Create a fresh system connection and proxy per request to avoid stale handles + // after sleep/resume or bus restarts. + let conn = get_system_connection()?; + let auth = get_authority_proxy(&conn)?; let details = HashMap::new(); // Use a unix-process subject including pid start-time and real uid. From 9e437aa6c5512b1f199290cad6926467ab7dd68c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tyrese=20Luo=20=28=E7=BE=85=E5=81=A5=E5=B3=AF=29?= Date: Tue, 30 Dec 2025 13:45:07 +0800 Subject: [PATCH 16/16] Authentication: explain why Linux ignores message text --- crates/authentication/src/lib.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/authentication/src/lib.rs b/crates/authentication/src/lib.rs index acd9e79..d7fc53f 100644 --- a/crates/authentication/src/lib.rs +++ b/crates/authentication/src/lib.rs @@ -132,7 +132,13 @@ impl Context { /// Note that the callback may be not be called at all, /// but will always be called upon success. /// - /// On Linux: message is unused. + /// On Linux: `message` is unused because polkit looks up the prompt text from the + /// action definition in the installed `.policy` file (its ``/``). + /// The client only supplies an action ID to `CheckAuthorization`, and there is no + /// stable, cross-agent way to override that UI text at runtime. + /// If you need multiple prompt strings, define multiple actions in the policy file + /// and select the desired one via `PolicyBuilder::action_ids` and + /// `Policy::set_action_id` before each authentication. /// /// Thus, authentication failed if this function returns an error /// **OR** if the `callback` is invoked with `Err(_)`.