refactor: use secret agent for managing passwords
This commit is contained in:
parent
a2f5a6daca
commit
d9b0a6944a
10 changed files with 1611 additions and 377 deletions
275
Cargo.lock
generated
275
Cargo.lock
generated
|
|
@ -121,6 +121,17 @@ version = "2.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher 0.4.4",
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.12"
|
||||
|
|
@ -151,9 +162,9 @@ checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
|
|||
|
||||
[[package]]
|
||||
name = "aligned"
|
||||
version = "0.4.2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "377e4c0ba83e4431b10df45c1d4666f178ea9c552cac93e60c3a88bf32785923"
|
||||
checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685"
|
||||
dependencies = [
|
||||
"as-slice",
|
||||
]
|
||||
|
|
@ -846,6 +857,15 @@ dependencies = [
|
|||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-padding"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block2"
|
||||
version = "0.5.1"
|
||||
|
|
@ -884,7 +904,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "32fa6a061124e37baba002e496d203e23ba3d7b73750be82dbfbc92913048a5b"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"cipher",
|
||||
"cipher 0.2.5",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
|
|
@ -899,15 +919,6 @@ dependencies = [
|
|||
"zbus 5.12.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "branches"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f11502672c5570f77f6bdf573332483f8475bab6a7fda00f1fae8ddb5a6245c0"
|
||||
dependencies = [
|
||||
"rustc_version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "brotli-decompressor"
|
||||
version = "5.0.0"
|
||||
|
|
@ -946,9 +957,9 @@ checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64"
|
|||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.19.0"
|
||||
version = "3.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
|
||||
|
||||
[[package]]
|
||||
name = "by_address"
|
||||
|
|
@ -1056,10 +1067,19 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.49"
|
||||
name = "cbc"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215"
|
||||
checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
|
||||
dependencies = [
|
||||
"cipher 0.4.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
|
|
@ -1095,9 +1115,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cfg-expr"
|
||||
version = "0.20.4"
|
||||
version = "0.20.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9acd0bdbbf4b2612d09f52ba61da432140cb10930354079d0d53fafc12968726"
|
||||
checksum = "21be0e1ce6cdb2ee7fff840f922fb04ead349e5cfb1e750b769132d44ce04720"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
"target-lexicon",
|
||||
|
|
@ -1144,6 +1164,16 @@ dependencies = [
|
|||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"inout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.8.1"
|
||||
|
|
@ -1463,7 +1493,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "cosmic-comp-config"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/cosmic-comp#141fa472efb33427fdac2f817ef85763b2fbc5d0"
|
||||
source = "git+https://github.com/pop-os/cosmic-comp#b1a4c3194a24a5c86fc5321442694466d4db3d86"
|
||||
dependencies = [
|
||||
"cosmic-config",
|
||||
"input",
|
||||
|
|
@ -1475,7 +1505,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "cosmic-config"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#aabc8dcda530a6ac70617dd578cea55910af53c8"
|
||||
source = "git+https://github.com/pop-os/libcosmic#fa26e0e2413cf5cd4c88201be8eb6ddd14917042"
|
||||
dependencies = [
|
||||
"atomicwrites",
|
||||
"cosmic-config-derive",
|
||||
|
|
@ -1496,7 +1526,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "cosmic-config-derive"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#aabc8dcda530a6ac70617dd578cea55910af53c8"
|
||||
source = "git+https://github.com/pop-os/libcosmic#fa26e0e2413cf5cd4c88201be8eb6ddd14917042"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
|
|
@ -1601,23 +1631,21 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "cosmic-randr"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/cosmic-randr#f5923d1ef58b87ef103abe1a0e44460236d8fa36"
|
||||
source = "git+https://github.com/pop-os/cosmic-randr#741089cf5e3aa7d5e48042101c1d4cc813b13637"
|
||||
dependencies = [
|
||||
"cosmic-protocols",
|
||||
"futures-lite 2.6.1",
|
||||
"indexmap 2.12.1",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"wayland-client",
|
||||
"wayland-protocols-wlr",
|
||||
"xutex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmic-randr-shell"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/cosmic-randr#f5923d1ef58b87ef103abe1a0e44460236d8fa36"
|
||||
source = "git+https://github.com/pop-os/cosmic-randr#741089cf5e3aa7d5e48042101c1d4cc813b13637"
|
||||
dependencies = [
|
||||
"kdl",
|
||||
"slotmap",
|
||||
|
|
@ -1677,6 +1705,7 @@ dependencies = [
|
|||
"locale1",
|
||||
"locales-rs",
|
||||
"mime 0.3.17",
|
||||
"nm-secret-agent-manager",
|
||||
"notify 6.1.1",
|
||||
"num-traits",
|
||||
"pwhash",
|
||||
|
|
@ -1799,10 +1828,13 @@ dependencies = [
|
|||
name = "cosmic-settings-network-manager-subscription"
|
||||
version = "1.0.0-beta6"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"cosmic-dbus-networkmanager",
|
||||
"futures",
|
||||
"iced_futures",
|
||||
"itertools 0.14.0",
|
||||
"nm-secret-agent-manager",
|
||||
"secret-service",
|
||||
"secure-string",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
|
|
@ -1895,7 +1927,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "cosmic-theme"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#aabc8dcda530a6ac70617dd578cea55910af53c8"
|
||||
source = "git+https://github.com/pop-os/libcosmic#fa26e0e2413cf5cd4c88201be8eb6ddd14917042"
|
||||
dependencies = [
|
||||
"almost",
|
||||
"cosmic-config",
|
||||
|
|
@ -1954,15 +1986,6 @@ dependencies = [
|
|||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
|
|
@ -2202,6 +2225,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
|||
dependencies = [
|
||||
"block-buffer 0.10.4",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2783,9 +2807,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fs-err"
|
||||
version = "3.2.0"
|
||||
version = "3.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62d91fd049c123429b018c47887d3f75a265540dd3c30ba9cb7bae9197edb03a"
|
||||
checksum = "824f08d01d0f496b3eca4f001a13cf17690a6ee930043d20817f547455fd98f8"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"tokio",
|
||||
|
|
@ -3215,6 +3239,15 @@ version = "0.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
|
||||
|
||||
[[package]]
|
||||
name = "hkdf"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
|
||||
dependencies = [
|
||||
"hmac 0.12.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.10.1"
|
||||
|
|
@ -3225,6 +3258,15 @@ dependencies = [
|
|||
"digest 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||
dependencies = [
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hostname-validator"
|
||||
version = "1.1.1"
|
||||
|
|
@ -3333,7 +3375,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced"
|
||||
version = "0.14.0-dev"
|
||||
source = "git+https://github.com/pop-os/libcosmic#aabc8dcda530a6ac70617dd578cea55910af53c8"
|
||||
source = "git+https://github.com/pop-os/libcosmic#fa26e0e2413cf5cd4c88201be8eb6ddd14917042"
|
||||
dependencies = [
|
||||
"dnd",
|
||||
"iced_accessibility",
|
||||
|
|
@ -3351,7 +3393,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_accessibility"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#aabc8dcda530a6ac70617dd578cea55910af53c8"
|
||||
source = "git+https://github.com/pop-os/libcosmic#fa26e0e2413cf5cd4c88201be8eb6ddd14917042"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"accesskit_winit",
|
||||
|
|
@ -3360,7 +3402,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_core"
|
||||
version = "0.14.0-dev"
|
||||
source = "git+https://github.com/pop-os/libcosmic#aabc8dcda530a6ac70617dd578cea55910af53c8"
|
||||
source = "git+https://github.com/pop-os/libcosmic#fa26e0e2413cf5cd4c88201be8eb6ddd14917042"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bytes",
|
||||
|
|
@ -3385,7 +3427,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_futures"
|
||||
version = "0.14.0-dev"
|
||||
source = "git+https://github.com/pop-os/libcosmic#aabc8dcda530a6ac70617dd578cea55910af53c8"
|
||||
source = "git+https://github.com/pop-os/libcosmic#fa26e0e2413cf5cd4c88201be8eb6ddd14917042"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"iced_core",
|
||||
|
|
@ -3411,7 +3453,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_graphics"
|
||||
version = "0.14.0-dev"
|
||||
source = "git+https://github.com/pop-os/libcosmic#aabc8dcda530a6ac70617dd578cea55910af53c8"
|
||||
source = "git+https://github.com/pop-os/libcosmic#fa26e0e2413cf5cd4c88201be8eb6ddd14917042"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bytemuck",
|
||||
|
|
@ -3433,7 +3475,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_renderer"
|
||||
version = "0.14.0-dev"
|
||||
source = "git+https://github.com/pop-os/libcosmic#aabc8dcda530a6ac70617dd578cea55910af53c8"
|
||||
source = "git+https://github.com/pop-os/libcosmic#fa26e0e2413cf5cd4c88201be8eb6ddd14917042"
|
||||
dependencies = [
|
||||
"iced_graphics",
|
||||
"iced_tiny_skia",
|
||||
|
|
@ -3445,7 +3487,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_runtime"
|
||||
version = "0.14.0-dev"
|
||||
source = "git+https://github.com/pop-os/libcosmic#aabc8dcda530a6ac70617dd578cea55910af53c8"
|
||||
source = "git+https://github.com/pop-os/libcosmic#fa26e0e2413cf5cd4c88201be8eb6ddd14917042"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"cosmic-client-toolkit",
|
||||
|
|
@ -3461,7 +3503,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_tiny_skia"
|
||||
version = "0.14.0-dev"
|
||||
source = "git+https://github.com/pop-os/libcosmic#aabc8dcda530a6ac70617dd578cea55910af53c8"
|
||||
source = "git+https://github.com/pop-os/libcosmic#fa26e0e2413cf5cd4c88201be8eb6ddd14917042"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"cosmic-text",
|
||||
|
|
@ -3477,7 +3519,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_wgpu"
|
||||
version = "0.14.0-dev"
|
||||
source = "git+https://github.com/pop-os/libcosmic#aabc8dcda530a6ac70617dd578cea55910af53c8"
|
||||
source = "git+https://github.com/pop-os/libcosmic#fa26e0e2413cf5cd4c88201be8eb6ddd14917042"
|
||||
dependencies = [
|
||||
"as-raw-xcb-connection",
|
||||
"bitflags 2.10.0",
|
||||
|
|
@ -3508,7 +3550,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_widget"
|
||||
version = "0.14.0-dev"
|
||||
source = "git+https://github.com/pop-os/libcosmic#aabc8dcda530a6ac70617dd578cea55910af53c8"
|
||||
source = "git+https://github.com/pop-os/libcosmic#fa26e0e2413cf5cd4c88201be8eb6ddd14917042"
|
||||
dependencies = [
|
||||
"cosmic-client-toolkit",
|
||||
"dnd",
|
||||
|
|
@ -3529,7 +3571,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_winit"
|
||||
version = "0.14.0-dev"
|
||||
source = "git+https://github.com/pop-os/libcosmic#aabc8dcda530a6ac70617dd578cea55910af53c8"
|
||||
source = "git+https://github.com/pop-os/libcosmic#fa26e0e2413cf5cd4c88201be8eb6ddd14917042"
|
||||
dependencies = [
|
||||
"cosmic-client-toolkit",
|
||||
"dnd",
|
||||
|
|
@ -3999,7 +4041,7 @@ dependencies = [
|
|||
"rgb",
|
||||
"tiff",
|
||||
"zune-core 0.5.0",
|
||||
"zune-jpeg 0.5.6",
|
||||
"zune-jpeg 0.5.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -4102,6 +4144,16 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
|
||||
dependencies = [
|
||||
"block-padding",
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "input"
|
||||
version = "0.9.1"
|
||||
|
|
@ -4549,7 +4601,7 @@ checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
|
|||
[[package]]
|
||||
name = "libcosmic"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#aabc8dcda530a6ac70617dd578cea55910af53c8"
|
||||
source = "git+https://github.com/pop-os/libcosmic#fa26e0e2413cf5cd4c88201be8eb6ddd14917042"
|
||||
dependencies = [
|
||||
"apply",
|
||||
"ashpd 0.12.0",
|
||||
|
|
@ -4622,13 +4674,13 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
|
|||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.10"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
|
||||
checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"libc",
|
||||
"redox_syscall 0.5.18",
|
||||
"redox_syscall 0.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -4987,9 +5039,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "moxcms"
|
||||
version = "0.7.10"
|
||||
version = "0.7.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80986bbbcf925ebd3be54c26613d861255284584501595cf418320c078945608"
|
||||
checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"pxfm",
|
||||
|
|
@ -5092,6 +5144,14 @@ dependencies = [
|
|||
"memoffset 0.9.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nm-secret-agent-manager"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/dbus-settings-bindings//?branch=nm-secret-agent#f9153724b3d56f6c8051dc6697055a85c6d85d30"
|
||||
dependencies = [
|
||||
"zbus 5.12.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
|
|
@ -6058,7 +6118,7 @@ version = "3.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
|
||||
dependencies = [
|
||||
"toml_edit 0.23.9",
|
||||
"toml_edit 0.23.10+spec-1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -6153,7 +6213,7 @@ checksum = "419a3ad8fa9f9d445e69d9b185a24878ae6e6f55c96e4512f4a0e28cd3bc5c56"
|
|||
dependencies = [
|
||||
"blowfish",
|
||||
"byteorder",
|
||||
"hmac",
|
||||
"hmac 0.10.1",
|
||||
"md-5",
|
||||
"rand 0.8.5",
|
||||
"sha-1",
|
||||
|
|
@ -6290,9 +6350,9 @@ checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde"
|
|||
|
||||
[[package]]
|
||||
name = "rangemap"
|
||||
version = "1.7.0"
|
||||
version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbbbbea733ec66275512d0b9694f34102e7d5406fdbe2ad8d21b28dce92887c"
|
||||
checksum = "973443cf09a9c8656b574a866ab68dfa19f0867d0340648c7d2f6a71b8a8ea68"
|
||||
|
||||
[[package]]
|
||||
name = "rav1e"
|
||||
|
|
@ -6409,6 +6469,15 @@ dependencies = [
|
|||
"bitflags 2.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.5.2"
|
||||
|
|
@ -6595,15 +6664,6 @@ version = "2.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.37.28"
|
||||
|
|
@ -6730,6 +6790,25 @@ dependencies = [
|
|||
"tiny-skia",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "secret-service"
|
||||
version = "5.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a62d7f86047af0077255a29494136b9aaaf697c76ff70b8e49cded4e2623c14"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"cbc",
|
||||
"futures-util",
|
||||
"generic-array",
|
||||
"getrandom 0.2.16",
|
||||
"hkdf",
|
||||
"num",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"sha2 0.10.9",
|
||||
"zbus 5.12.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "secure-string"
|
||||
version = "0.3.0"
|
||||
|
|
@ -6746,12 +6825,6 @@ version = "1.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16c2f82143577edb4921b71ede051dac62ca3c16084e918bf7b40c96ae10eb33"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
|
|
@ -6821,9 +6894,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "1.0.3"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392"
|
||||
checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
|
@ -7298,7 +7371,7 @@ dependencies = [
|
|||
"cfg-expr",
|
||||
"heck 0.5.0",
|
||||
"pkg-config",
|
||||
"toml 0.9.8",
|
||||
"toml 0.9.10+spec-1.1.0",
|
||||
"version-compare",
|
||||
]
|
||||
|
||||
|
|
@ -7565,14 +7638,14 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.9.8"
|
||||
version = "0.9.10+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8"
|
||||
checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48"
|
||||
dependencies = [
|
||||
"indexmap 2.12.1",
|
||||
"serde_core",
|
||||
"serde_spanned",
|
||||
"toml_datetime 0.7.3",
|
||||
"toml_datetime 0.7.5+spec-1.1.0",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow 0.7.14",
|
||||
|
|
@ -7586,9 +7659,9 @@ checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
|||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.7.3"
|
||||
version = "0.7.5+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533"
|
||||
checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
|
@ -7606,36 +7679,36 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.23.9"
|
||||
version = "0.23.10+spec-1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832"
|
||||
checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269"
|
||||
dependencies = [
|
||||
"indexmap 2.12.1",
|
||||
"toml_datetime 0.7.3",
|
||||
"toml_datetime 0.7.5+spec-1.1.0",
|
||||
"toml_parser",
|
||||
"winnow 0.7.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.4"
|
||||
version = "1.0.6+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e"
|
||||
checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44"
|
||||
dependencies = [
|
||||
"winnow 0.7.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_writer"
|
||||
version = "1.0.4"
|
||||
version = "1.0.6+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2"
|
||||
checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.43"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647"
|
||||
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
|
|
@ -7656,9 +7729,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.35"
|
||||
version = "0.1.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c"
|
||||
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
|
|
@ -9161,16 +9234,6 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
|
||||
|
||||
[[package]]
|
||||
name = "xutex"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f7b2794adabee656fae931dc27d457c4e8a83cfe96ad1e1c73de770b8401b57"
|
||||
dependencies = [
|
||||
"branches",
|
||||
"crossbeam-queue",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "y4m"
|
||||
version = "0.8.0"
|
||||
|
|
@ -9467,9 +9530,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "zune-jpeg"
|
||||
version = "0.5.6"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f520eebad972262a1dde0ec455bce4f8b298b1e5154513de58c114c4c54303e8"
|
||||
checksum = "51d915729b0e7d5fe35c2f294c5dc10b30207cc637920e5b59077bfa3da63f28"
|
||||
dependencies = [
|
||||
"zune-core 0.5.0",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -70,9 +70,10 @@ cosmic-client-toolkit = { git = "https://github.com/pop-os/cosmic-protocols//",
|
|||
# cosmic-theme = { path = "../libcosmic/cosmic-theme" }
|
||||
# iced_futures = { path = "../libcosmic/iced/futures" }
|
||||
|
||||
# [patch.'https://github.com/pop-os/dbus-settings-bindings']
|
||||
[patch.'https://github.com/pop-os/dbus-settings-bindings']
|
||||
# cosmic-dbus-networkmanager = { path = "../dbus-settings-bindings/networkmanager" }
|
||||
# upower_dbus = { path = "../dbus-settings-bindings/upower" }
|
||||
nm-secret-agent-manager = { git = "https://github.com/pop-os/dbus-settings-bindings//", branch = "nm-secret-agent" }
|
||||
|
||||
[patch."https://github.com/smithay/client-toolkit.git"]
|
||||
sctk = { package = "smithay-client-toolkit", version = "0.20.0" }
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ cosmic-bg-config.workspace = true
|
|||
cosmic-comp-config = { workspace = true, optional = true }
|
||||
cosmic-config.workspace = true
|
||||
cosmic-dbus-networkmanager = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true }
|
||||
nm-secret-agent-manager = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true }
|
||||
cosmic-idle-config.workspace = true
|
||||
cosmic-panel-config = { workspace = true, optional = true }
|
||||
cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", optional = true }
|
||||
|
|
@ -164,6 +165,7 @@ page-networking = [
|
|||
"dep:cosmic-settings-network-manager-subscription",
|
||||
"xdg-portal",
|
||||
"dep:cosmic-dbus-networkmanager",
|
||||
"dep:nm-secret-agent-manager",
|
||||
"dep:zbus",
|
||||
]
|
||||
page-power = ["dep:upower_dbus", "dep:zbus"]
|
||||
|
|
|
|||
|
|
@ -16,8 +16,11 @@ use cosmic_dbus_networkmanager::{
|
|||
use cosmic_settings_network_manager_subscription as network_manager;
|
||||
use cosmic_settings_page::{self as page, Section, section};
|
||||
use futures::StreamExt;
|
||||
use secure_string::SecureString;
|
||||
use slotmap::SlotMap;
|
||||
|
||||
pub type SecretSender = Arc<tokio::sync::Mutex<Option<tokio::sync::oneshot::Sender<SecureString>>>>;
|
||||
|
||||
static NM_CONNECTION_EDITOR: &str = "nm-connection-editor";
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
// Copyright 2024 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
mod nmcli;
|
||||
pub mod nmcli;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Context;
|
||||
|
|
@ -13,6 +14,7 @@ use cosmic::{
|
|||
iced_core::text::Wrapping,
|
||||
widget::{self, icon},
|
||||
};
|
||||
use cosmic_settings_network_manager_subscription::nm_secret_agent::{self, PasswordFlag};
|
||||
use cosmic_settings_network_manager_subscription::{
|
||||
self as network_manager, NetworkManagerState, UUID, current_networks::ActiveConnectionInfo,
|
||||
};
|
||||
|
|
@ -20,6 +22,9 @@ use cosmic_settings_page::{self as page, Section, section};
|
|||
use futures::{FutureExt, StreamExt};
|
||||
use indexmap::IndexMap;
|
||||
use secure_string::SecureString;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::pages::networking::SecretSender;
|
||||
|
||||
pub type ConnectionId = Arc<str>;
|
||||
pub type InterfaceId = String;
|
||||
|
|
@ -36,6 +41,8 @@ pub enum Message {
|
|||
CancelDialog,
|
||||
/// Connect to a VPN with the given username and password
|
||||
ConnectWithPassword,
|
||||
/// Connect to a VPN with the given username and password
|
||||
RetryWithPassword,
|
||||
/// Deactivate a connection.
|
||||
Deactivate(ConnectionId),
|
||||
/// An error occurred.
|
||||
|
|
@ -46,6 +53,8 @@ pub enum Message {
|
|||
KnownConnections(IndexMap<UUID, ConnectionSettings>),
|
||||
/// An update from the network manager daemon
|
||||
NetworkManager(network_manager::Event),
|
||||
/// An update from the secret agent
|
||||
SecretAgent(network_manager::nm_secret_agent::Event),
|
||||
/// Successfully connected to the system dbus.
|
||||
NetworkManagerConnect(zbus::Connection),
|
||||
/// Updates the password text input
|
||||
|
|
@ -146,31 +155,17 @@ enum ConnectionType {
|
|||
Password,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
enum PasswordFlag {
|
||||
/// The system is responsible for providing and storing this secret.
|
||||
None = 0,
|
||||
/// A user-session secret agent is responsible for providing and storing
|
||||
/// this secret; when it is required, agents will be asked to provide it.
|
||||
AgentOwned = 1,
|
||||
/// This secret should not be saved but should be requested from the user
|
||||
/// each time it is required. This flag should be used for One-Time-Pad
|
||||
/// secrets, PIN codes from hardware tokens, or if the user simply does not
|
||||
/// want to save the secret.
|
||||
NotSaved = 2,
|
||||
/// in some situations it cannot be automatically determined that a secret is required or not. This flag hints that the secret is not required and should not be requested from the user.
|
||||
NotRequired = 4,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum VpnDialog {
|
||||
Error(ErrorKind, String),
|
||||
Password {
|
||||
id: String,
|
||||
uuid: Arc<str>,
|
||||
username: String,
|
||||
username: Option<String>,
|
||||
password: SecureString,
|
||||
description: Option<String>,
|
||||
password_hidden: bool,
|
||||
tx: SecretSender,
|
||||
error: Option<(ErrorKind, String)>,
|
||||
},
|
||||
RemoveProfile(ConnectionId),
|
||||
|
|
@ -189,6 +184,7 @@ pub struct NmState {
|
|||
pub struct Page {
|
||||
entity: page::Entity,
|
||||
nm_task: Option<tokio::sync::oneshot::Sender<()>>,
|
||||
secret_tx: Option<tokio::sync::mpsc::Sender<nm_secret_agent::Request>>,
|
||||
nm_state: Option<NmState>,
|
||||
dialog: Option<VpnDialog>,
|
||||
view_more_popup: Option<ConnectionId>,
|
||||
|
|
@ -242,10 +238,13 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
password,
|
||||
password_hidden,
|
||||
error,
|
||||
description,
|
||||
..
|
||||
} => {
|
||||
let username = widget::text_input(fl!("username"), username.as_str())
|
||||
.on_input(Message::UsernameUpdate);
|
||||
let username = username.as_ref().map(|username| {
|
||||
widget::text_input(fl!("username"), username.as_str())
|
||||
.on_input(Message::UsernameUpdate)
|
||||
});
|
||||
|
||||
let password = widget::text_input::secure_input(
|
||||
fl!("password"),
|
||||
|
|
@ -254,8 +253,14 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
*password_hidden,
|
||||
)
|
||||
.on_input(|input| Message::PasswordUpdate(SecureString::from(input)))
|
||||
.on_submit(|_| Message::ConnectWithPassword);
|
||||
let (err_kind, error) = if let Some(err) = error.as_ref() {
|
||||
.on_submit(|_| {
|
||||
if error.is_some() {
|
||||
Message::RetryWithPassword
|
||||
} else {
|
||||
Message::ConnectWithPassword
|
||||
}
|
||||
});
|
||||
let (err_kind, error_text) = if let Some(err) = error.as_ref() {
|
||||
(
|
||||
Some(err.0),
|
||||
Some(widget::text::body(err.1.as_str()).wrapping(Wrapping::Word)),
|
||||
|
|
@ -266,13 +271,17 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
|
||||
let controls = widget::column::with_capacity(2)
|
||||
.spacing(12)
|
||||
.push(username)
|
||||
.push_maybe(username)
|
||||
.push(password)
|
||||
.push_maybe(error)
|
||||
.push_maybe(error_text)
|
||||
.apply(Element::from);
|
||||
|
||||
let primary_action = widget::button::suggested(fl!("connect"))
|
||||
.on_press(Message::ConnectWithPassword);
|
||||
let primary_action =
|
||||
widget::button::suggested(fl!("connect")).on_press(if error.is_some() {
|
||||
Message::RetryWithPassword
|
||||
} else {
|
||||
Message::ConnectWithPassword
|
||||
});
|
||||
|
||||
let secondary_action =
|
||||
widget::button::standard(fl!("cancel")).on_press(Message::CancelDialog);
|
||||
|
|
@ -284,7 +293,11 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
fl!("auth-dialog")
|
||||
})
|
||||
.icon(icon::from_name("network-vpn-symbolic").size(64))
|
||||
.body(fl!("auth-dialog", "vpn-description"))
|
||||
.body(if let Some(description) = description.as_ref() {
|
||||
description.clone()
|
||||
} else {
|
||||
fl!("auth-dialog", "vpn-description")
|
||||
})
|
||||
.control(controls)
|
||||
.primary_action(primary_action)
|
||||
.secondary_action(secondary_action)
|
||||
|
|
@ -347,8 +360,10 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
}
|
||||
|
||||
fn on_enter(&mut self) -> cosmic::Task<crate::pages::Message> {
|
||||
let (tx, rx) = tokio::sync::mpsc::channel(4);
|
||||
self.secret_tx = Some(tx);
|
||||
if self.nm_task.is_none() {
|
||||
return cosmic::task::future(async move {
|
||||
return cosmic::Task::batch([cosmic::task::future(async move {
|
||||
zbus::Connection::system()
|
||||
.await
|
||||
.context("failed to create system dbus connection")
|
||||
|
|
@ -356,10 +371,15 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
|why| Message::Error(ErrorKind::DbusConnection, why.to_string()),
|
||||
Message::NetworkManagerConnect,
|
||||
)
|
||||
});
|
||||
}),
|
||||
cosmic::Task::stream(
|
||||
cosmic_settings_network_manager_subscription::nm_secret_agent::secret_agent_stream("com.system76.CosmicSettings.VPN.NetworkManager.SecretAgent", rx),
|
||||
)
|
||||
.map(|m| crate::pages::Message::Vpn(Message::SecretAgent(m))),
|
||||
]);
|
||||
}
|
||||
|
||||
Task::none()
|
||||
cosmic::Task::none()
|
||||
}
|
||||
|
||||
fn on_leave(&mut self) -> Task<crate::pages::Message> {
|
||||
|
|
@ -401,19 +421,15 @@ impl Page {
|
|||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Message::KnownConnections(connections) => {
|
||||
self.known_connections = connections;
|
||||
}
|
||||
|
||||
Message::UpdateDevices(devices) => {
|
||||
self.update_devices(devices);
|
||||
}
|
||||
|
||||
Message::UpdateState(state) => {
|
||||
self.update_active_conns(state);
|
||||
}
|
||||
|
||||
Message::NetworkManager(
|
||||
network_manager::Event::ActiveConns | network_manager::Event::Devices,
|
||||
) => {
|
||||
|
|
@ -425,7 +441,6 @@ impl Page {
|
|||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Message::NetworkManager(network_manager::Event::Init {
|
||||
conn,
|
||||
sender,
|
||||
|
|
@ -447,21 +462,16 @@ impl Page {
|
|||
update_devices(conn),
|
||||
]);
|
||||
}
|
||||
|
||||
Message::NetworkManager(_event) => (),
|
||||
|
||||
Message::AddNetwork => return add_network(),
|
||||
|
||||
Message::AddWireGuardDevice(device, filename, path) => {
|
||||
self.dialog = Some(VpnDialog::WireGuardName(device, filename, path));
|
||||
}
|
||||
|
||||
Message::WireGuardDeviceInput(input) => {
|
||||
if let Some(VpnDialog::WireGuardName(ref mut device, ..)) = self.dialog {
|
||||
*device = input
|
||||
}
|
||||
}
|
||||
|
||||
Message::WireGuardConfig => {
|
||||
if let Some(VpnDialog::WireGuardName(device, filename, path)) = self.dialog.take() {
|
||||
return cosmic::task::future(async move {
|
||||
|
|
@ -474,7 +484,6 @@ impl Page {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
Message::Activate(uuid) => {
|
||||
self.close_popup_and_apply_updates();
|
||||
|
||||
|
|
@ -502,13 +511,14 @@ impl Page {
|
|||
self.dialog = Some(VpnDialog::Password {
|
||||
id: settings.id.clone(),
|
||||
uuid: uuid.clone(),
|
||||
username: settings.username.clone().unwrap_or_default(),
|
||||
username: settings.username.clone(),
|
||||
password: SecureString::from(""),
|
||||
description: None,
|
||||
password_hidden: true,
|
||||
error: None,
|
||||
tx: Arc::new(Mutex::new(None)),
|
||||
});
|
||||
}
|
||||
|
||||
_ => {
|
||||
let connection_name = settings.id.clone();
|
||||
let username = settings.username.clone();
|
||||
|
|
@ -521,9 +531,12 @@ impl Page {
|
|||
)),
|
||||
id: connection_name.clone(),
|
||||
uuid,
|
||||
username: username.clone().unwrap_or_default(),
|
||||
username: username.clone(),
|
||||
description: None,
|
||||
password: SecureString::from(""),
|
||||
password_hidden: true,
|
||||
// TODO grab from the current dialog
|
||||
tx: Arc::new(Mutex::new(None)),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -533,19 +546,16 @@ impl Page {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Message::Deactivate(uuid) => {
|
||||
self.close_popup_and_apply_updates();
|
||||
if let Some(NmState { ref sender, .. }) = self.nm_state {
|
||||
_ = sender.unbounded_send(network_manager::Request::Deactivate(uuid));
|
||||
}
|
||||
}
|
||||
|
||||
Message::RemoveProfileRequest(uuid) => {
|
||||
self.view_more_popup = None;
|
||||
self.dialog = Some(VpnDialog::RemoveProfile(uuid));
|
||||
}
|
||||
|
||||
Message::RemoveProfile(uuid) => {
|
||||
self.dialog = None;
|
||||
self.close_popup_and_apply_updates();
|
||||
|
|
@ -553,7 +563,6 @@ impl Page {
|
|||
_ = sender.unbounded_send(network_manager::Request::Remove(uuid));
|
||||
}
|
||||
}
|
||||
|
||||
Message::ViewMore(uuid) => {
|
||||
self.view_more_popup = uuid;
|
||||
if self.view_more_popup.is_none() {
|
||||
|
|
@ -576,7 +585,6 @@ impl Page {
|
|||
.await
|
||||
});
|
||||
}
|
||||
|
||||
Message::Refresh => {
|
||||
if let Some(NmState { ref conn, .. }) = self.nm_state {
|
||||
return cosmic::Task::batch(vec![
|
||||
|
|
@ -586,7 +594,6 @@ impl Page {
|
|||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Message::PasswordUpdate(pass) => {
|
||||
if let Some(VpnDialog::Password {
|
||||
ref mut password, ..
|
||||
|
|
@ -595,12 +602,105 @@ impl Page {
|
|||
*password = pass;
|
||||
}
|
||||
}
|
||||
|
||||
Message::ConnectWithPassword => {
|
||||
let Some(dialog) = self.dialog.take() else {
|
||||
return Task::none();
|
||||
};
|
||||
|
||||
if let VpnDialog::Password {
|
||||
id,
|
||||
uuid,
|
||||
username,
|
||||
password,
|
||||
tx,
|
||||
..
|
||||
} = dialog
|
||||
{
|
||||
let username_unwrapped = username.clone().unwrap_or_default();
|
||||
let task = self.activate_with_password(
|
||||
id.clone(),
|
||||
uuid.clone(),
|
||||
username_unwrapped.clone(),
|
||||
password.clone(),
|
||||
);
|
||||
let sec_tx = self.secret_tx.clone();
|
||||
return task
|
||||
.then(move |_| {
|
||||
let sec_tx = sec_tx.clone();
|
||||
let uuid = uuid.clone();
|
||||
let username = username.clone();
|
||||
let password = password.clone();
|
||||
let tx = tx.clone();
|
||||
let id = id.clone();
|
||||
Task::future(async move {
|
||||
let mut guard = tx.lock().await;
|
||||
if let Some(sender) = guard.take() {
|
||||
let _ = sender.send(password);
|
||||
} else {
|
||||
// apply password and username then
|
||||
if let Some(sec_tx) = sec_tx {
|
||||
let (applied_tx, applied_rx) =
|
||||
tokio::sync::oneshot::channel();
|
||||
if let Err(err) = sec_tx
|
||||
.send(nm_secret_agent::Request::SetSecrets {
|
||||
setting_name: "vpn".to_string(),
|
||||
uuid: uuid.to_string(),
|
||||
secrets: HashMap::from_iter([
|
||||
// username and password
|
||||
(
|
||||
"username".to_string(),
|
||||
username.clone().unwrap_or_default().into(),
|
||||
),
|
||||
(
|
||||
"password".to_string(),
|
||||
password.clone().into(),
|
||||
),
|
||||
]),
|
||||
applied_tx,
|
||||
})
|
||||
.await
|
||||
{
|
||||
tracing::error!(%err, "failed to apply secret");
|
||||
}
|
||||
// wait max 1s for the applied signal
|
||||
if let Err(err) = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(1),
|
||||
applied_rx,
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::error!(%err, "failed to apply secret");
|
||||
}
|
||||
}
|
||||
// activate
|
||||
if let Err(why) = nmcli::connect(&id).await {
|
||||
return Message::VpnDialogError(VpnDialog::Password {
|
||||
error: Some((
|
||||
ErrorKind::Connect,
|
||||
format!("failed to connect to VPN: {why}"),
|
||||
)),
|
||||
id: id.clone(),
|
||||
uuid,
|
||||
username: username.clone(),
|
||||
description: None,
|
||||
password,
|
||||
password_hidden: true,
|
||||
tx: Arc::new(Mutex::new(None)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Message::Refresh
|
||||
})
|
||||
})
|
||||
.map(crate::app::Message::from);
|
||||
}
|
||||
}
|
||||
Message::RetryWithPassword => {
|
||||
let Some(dialog) = self.dialog.take() else {
|
||||
return Task::none();
|
||||
};
|
||||
|
||||
if let VpnDialog::Password {
|
||||
id,
|
||||
uuid,
|
||||
|
|
@ -609,18 +709,54 @@ impl Page {
|
|||
..
|
||||
} = dialog
|
||||
{
|
||||
return self
|
||||
.activate_with_password(id, uuid, username, password)
|
||||
let username_unwrapped = username.unwrap_or_default();
|
||||
let sec_tx = self.secret_tx.clone();
|
||||
let task = self.activate_with_password(
|
||||
id.clone(),
|
||||
uuid.clone(),
|
||||
username_unwrapped.clone(),
|
||||
password.clone(),
|
||||
);
|
||||
return task
|
||||
.then(move |_| {
|
||||
let sec_tx = sec_tx.clone();
|
||||
let uuid = uuid.clone();
|
||||
let username = username_unwrapped.clone();
|
||||
let password = password.clone();
|
||||
Task::future(async move {
|
||||
if let Some(sec_tx) = sec_tx {
|
||||
let (applied_tx, applied_rx) = tokio::sync::oneshot::channel();
|
||||
let _ = sec_tx
|
||||
.send(nm_secret_agent::Request::SetSecrets {
|
||||
setting_name: "vpn".to_string(),
|
||||
uuid: uuid.to_string(),
|
||||
secrets: HashMap::from_iter([
|
||||
// username and password
|
||||
("username".to_string(), username.clone().into()),
|
||||
("password".to_string(), password.clone().into()),
|
||||
]),
|
||||
applied_tx,
|
||||
})
|
||||
.await;
|
||||
// wait max 1s for the applied signal
|
||||
let _ = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(1),
|
||||
applied_rx,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
Message::Activate(uuid)
|
||||
})
|
||||
})
|
||||
.map(crate::app::Message::from);
|
||||
}
|
||||
}
|
||||
|
||||
Message::UsernameUpdate(user) => {
|
||||
if let Some(VpnDialog::Password {
|
||||
ref mut username, ..
|
||||
}) = self.dialog
|
||||
{
|
||||
*username = user;
|
||||
*username = Some(user);
|
||||
}
|
||||
}
|
||||
Message::CancelDialog => {
|
||||
|
|
@ -635,19 +771,65 @@ impl Page {
|
|||
*password_hidden = !*password_hidden;
|
||||
}
|
||||
}
|
||||
|
||||
Message::Error(error_kind, why) => {
|
||||
tracing::error!(?error_kind, why);
|
||||
self.dialog = Some(VpnDialog::Error(error_kind, why))
|
||||
}
|
||||
|
||||
Message::NetworkManagerConnect(conn) => {
|
||||
return self.connect(conn.clone());
|
||||
}
|
||||
|
||||
Message::VpnDialogError(vpn_dialog) => {
|
||||
self.dialog = Some(vpn_dialog);
|
||||
}
|
||||
Message::SecretAgent(e) => match e {
|
||||
nm_secret_agent::Event::RequestSecret {
|
||||
uuid: _,
|
||||
name,
|
||||
previous,
|
||||
description,
|
||||
tx,
|
||||
} => {
|
||||
self.dialog = Some(VpnDialog::Password {
|
||||
id: name.clone(),
|
||||
uuid: Arc::from(""),
|
||||
username: None,
|
||||
description: description,
|
||||
password: previous,
|
||||
password_hidden: true,
|
||||
tx,
|
||||
error: None,
|
||||
});
|
||||
}
|
||||
nm_secret_agent::Event::CancelGetSecrets { uuid: _, name: _ } => {
|
||||
self.dialog = self
|
||||
.dialog
|
||||
.take()
|
||||
.filter(|d| !matches!(d, &VpnDialog::Password { .. }));
|
||||
}
|
||||
nm_secret_agent::Event::Failed(error) => {
|
||||
tracing::error!(%error, "secret agent failure");
|
||||
if let Some(VpnDialog::Password {
|
||||
id,
|
||||
uuid,
|
||||
username,
|
||||
password,
|
||||
description,
|
||||
..
|
||||
}) = self.dialog.take()
|
||||
{
|
||||
self.dialog = Some(VpnDialog::Password {
|
||||
error: Some((ErrorKind::DbusConnection, error.to_string())),
|
||||
id,
|
||||
uuid,
|
||||
username,
|
||||
description,
|
||||
password,
|
||||
password_hidden: true,
|
||||
tx: Arc::new(Mutex::new(None)),
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Task::none()
|
||||
|
|
@ -666,20 +848,11 @@ impl Page {
|
|||
error: Some((ErrorKind::WithPassword("username"), why.to_string())),
|
||||
id: connection_name.clone(),
|
||||
uuid,
|
||||
username,
|
||||
password,
|
||||
password_hidden: true,
|
||||
});
|
||||
}
|
||||
|
||||
if let Err(why) = nmcli::set_password_flags_none(&connection_name).await {
|
||||
return Message::VpnDialogError(VpnDialog::Password {
|
||||
error: Some((ErrorKind::WithPassword("password-flags"), why.to_string())),
|
||||
id: connection_name.clone(),
|
||||
uuid,
|
||||
username,
|
||||
username: Some(username),
|
||||
description: None,
|
||||
password,
|
||||
password_hidden: true,
|
||||
tx: Arc::new(Mutex::new(None)),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -688,42 +861,11 @@ impl Page {
|
|||
error: Some((ErrorKind::Config, why.to_string())),
|
||||
id: connection_name.clone(),
|
||||
uuid,
|
||||
username,
|
||||
password,
|
||||
password_hidden: true,
|
||||
});
|
||||
}
|
||||
|
||||
if let Err(why) = nmcli::set_password_flags_none(&connection_name).await {
|
||||
return Message::VpnDialogError(VpnDialog::Password {
|
||||
error: Some((ErrorKind::WithPassword("password-flags"), why.to_string())),
|
||||
id: connection_name.clone(),
|
||||
uuid,
|
||||
username,
|
||||
password,
|
||||
password_hidden: true,
|
||||
});
|
||||
}
|
||||
|
||||
if let Err(why) = nmcli::set_password(&connection_name, password.unsecure()).await {
|
||||
return Message::VpnDialogError(VpnDialog::Password {
|
||||
error: Some((ErrorKind::WithPassword("password"), why.to_string())),
|
||||
id: connection_name.clone(),
|
||||
uuid,
|
||||
username,
|
||||
password,
|
||||
password_hidden: true,
|
||||
});
|
||||
}
|
||||
|
||||
if let Err(why) = nmcli::connect(&connection_name).await {
|
||||
return Message::VpnDialogError(VpnDialog::Password {
|
||||
error: Some((ErrorKind::Connect, why.to_string())),
|
||||
id: connection_name.clone(),
|
||||
uuid,
|
||||
username,
|
||||
username: Some(username),
|
||||
password,
|
||||
description: None,
|
||||
password_hidden: true,
|
||||
tx: Arc::new(Mutex::new(None)),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1065,25 +1207,24 @@ fn connection_settings(conn: zbus::Connection) -> Task<crate::app::Message> {
|
|||
let id = connection.get("id")?.downcast_ref::<String>().ok()?;
|
||||
let uuid = connection.get("uuid")?.downcast_ref::<String>().ok()?;
|
||||
|
||||
let (username, connection_type, password_flag) = vpn
|
||||
let (connection_type, username, password_flag) = vpn
|
||||
.get("data")
|
||||
.and_then(|data| data.downcast_ref::<zbus::zvariant::Dict>().ok())
|
||||
.map(|dict| {
|
||||
let (mut connection_type, mut password_flag) = (None, None);
|
||||
|
||||
let username = dict
|
||||
.get::<String, String>(&String::from("username"))
|
||||
.ok()
|
||||
.flatten()
|
||||
.filter(|value| !value.is_empty());
|
||||
|
||||
if let Some("password") = dict
|
||||
let mut username = vpn
|
||||
.get("user-name")
|
||||
.and_then(|u| u.downcast_ref::<String>().ok());
|
||||
if dict
|
||||
.get::<String, String>(&String::from("connection-type"))
|
||||
.ok()
|
||||
.flatten()
|
||||
.as_deref()
|
||||
// may be "password" or "password-tls"
|
||||
.is_some_and(|p| p.starts_with("password"))
|
||||
{
|
||||
connection_type = Some(ConnectionType::Password);
|
||||
username = Some(username.unwrap_or_default());
|
||||
|
||||
password_flag = dict
|
||||
.get::<String, String>(&String::from("password-flags"))
|
||||
|
|
@ -1098,7 +1239,7 @@ fn connection_settings(conn: zbus::Connection) -> Task<crate::app::Message> {
|
|||
});
|
||||
}
|
||||
|
||||
(username, connection_type, password_flag)
|
||||
(connection_type, username, password_flag)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
|
|
|
|||
|
|
@ -13,21 +13,6 @@ pub async fn set_username(connection_name: &str, username: &str) -> Result<(), S
|
|||
.apply(crate::utils::map_stderr_output)
|
||||
}
|
||||
|
||||
pub async fn set_password_flags_none(connection_name: &str) -> Result<(), String> {
|
||||
tokio::process::Command::new("nmcli")
|
||||
.args([
|
||||
"con",
|
||||
"mod",
|
||||
connection_name,
|
||||
"+vpn.data",
|
||||
"password-flags=0",
|
||||
])
|
||||
.stderr(Stdio::piped())
|
||||
.output()
|
||||
.await
|
||||
.apply(crate::utils::map_stderr_output)
|
||||
}
|
||||
|
||||
pub async fn add_fallback(connection_name: &str) -> Result<(), String> {
|
||||
tokio::process::Command::new("nmcli")
|
||||
.args([
|
||||
|
|
@ -43,21 +28,6 @@ pub async fn add_fallback(connection_name: &str) -> Result<(), String> {
|
|||
.apply(crate::utils::map_stderr_output)
|
||||
}
|
||||
|
||||
pub async fn set_password(connection_name: &str, password: &str) -> Result<(), String> {
|
||||
tokio::process::Command::new("nmcli")
|
||||
.args([
|
||||
"con",
|
||||
"mod",
|
||||
connection_name,
|
||||
"vpn.secrets",
|
||||
&format!("password={password}"),
|
||||
])
|
||||
.stderr(Stdio::piped())
|
||||
.output()
|
||||
.await
|
||||
.apply(crate::utils::map_stderr_output)
|
||||
}
|
||||
|
||||
pub async fn connect(connection_name: &str) -> Result<(), String> {
|
||||
tokio::process::Command::new("nmcli")
|
||||
.args(["con", "up", connection_name])
|
||||
|
|
|
|||
|
|
@ -20,10 +20,14 @@ use cosmic_settings_network_manager_subscription::{
|
|||
available_wifi::{AccessPoint, NetworkType},
|
||||
current_networks::ActiveConnectionInfo,
|
||||
hw_address::HwAddress,
|
||||
nm_secret_agent,
|
||||
};
|
||||
use cosmic_settings_page::{self as page, Section, section};
|
||||
use futures::StreamExt;
|
||||
use secure_string::SecureString;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::pages::networking::SecretSender;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Message {
|
||||
|
|
@ -49,6 +53,8 @@ pub enum Message {
|
|||
Forget(network_manager::SSID),
|
||||
/// An update from the network manager daemon
|
||||
NetworkManager(network_manager::Event),
|
||||
/// An update from the secret agent
|
||||
SecretAgent(network_manager::nm_secret_agent::Event),
|
||||
/// Successfully connected to the system dbus.
|
||||
NetworkManagerConnect(zbus::Connection),
|
||||
/// Request an auth dialog
|
||||
|
|
@ -87,7 +93,7 @@ impl From<Message> for crate::pages::Message {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug)]
|
||||
enum WiFiDialog {
|
||||
Forget(network_manager::SSID),
|
||||
Password {
|
||||
|
|
@ -96,6 +102,7 @@ enum WiFiDialog {
|
|||
identity: Option<String>,
|
||||
password: SecureString,
|
||||
password_hidden: bool,
|
||||
tx: SecretSender,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -103,7 +110,7 @@ enum WiFiDialog {
|
|||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
struct QRCodeDrawer {
|
||||
ssid: network_manager::SSID,
|
||||
password: Option<String>,
|
||||
password: Option<SecureString>,
|
||||
security_type: NetworkType,
|
||||
}
|
||||
|
||||
|
|
@ -111,6 +118,7 @@ struct QRCodeDrawer {
|
|||
pub struct Page {
|
||||
entity: page::Entity,
|
||||
nm_task: Option<tokio::sync::oneshot::Sender<()>>,
|
||||
secret_tx: Option<tokio::sync::mpsc::Sender<nm_secret_agent::Request>>,
|
||||
nm_state: Option<NmState>,
|
||||
/// When defined, displays connections for the specific device.
|
||||
active_device: Option<Arc<network_manager::devices::DeviceInfo>>,
|
||||
|
|
@ -245,7 +253,7 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
));
|
||||
|
||||
if let Some(ref pass) = drawer.password {
|
||||
info_items = info_items.add(widget::settings::item(fl!("password"), pass.as_str()));
|
||||
info_items = info_items.add(widget::settings::item(fl!("password"), pass.unsecure()));
|
||||
}
|
||||
|
||||
let content = column::column()
|
||||
|
|
@ -279,8 +287,10 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
}
|
||||
|
||||
fn on_enter(&mut self) -> cosmic::Task<crate::pages::Message> {
|
||||
let (tx, rx) = tokio::sync::mpsc::channel(4);
|
||||
self.secret_tx = Some(tx);
|
||||
if self.nm_task.is_none() {
|
||||
return cosmic::Task::future(async move {
|
||||
return Task::batch(vec![cosmic::Task::future(async move {
|
||||
zbus::Connection::system()
|
||||
.await
|
||||
.context("failed to create system dbus connection")
|
||||
|
|
@ -289,7 +299,10 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
Message::NetworkManagerConnect,
|
||||
)
|
||||
.apply(crate::pages::Message::WiFi)
|
||||
});
|
||||
}), cosmic::Task::stream(
|
||||
cosmic_settings_network_manager_subscription::nm_secret_agent::secret_agent_stream("com.system76.CosmicSettings.WiFi.NetworkManager.SecretAgent", rx),
|
||||
)
|
||||
.map(|m| crate::pages::Message::WiFi(Message::SecretAgent(m)))]);
|
||||
}
|
||||
|
||||
Task::none()
|
||||
|
|
@ -350,16 +363,30 @@ impl Page {
|
|||
hw_address,
|
||||
password: SecureString::from(""),
|
||||
password_hidden: true,
|
||||
tx: Arc::new(Mutex::new(None)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
network_manager::Request::SelectAccessPoint(
|
||||
ssid,
|
||||
_hw_address,
|
||||
_network_type,
|
||||
hw_address,
|
||||
network_type,
|
||||
_tx,
|
||||
) => {
|
||||
self.connecting.remove(ssid.as_ref());
|
||||
if success || matches!(network_type, NetworkType::Open) {
|
||||
self.connecting.remove(ssid.as_ref());
|
||||
} else {
|
||||
self.dialog = Some(WiFiDialog::Password {
|
||||
ssid: ssid.into(),
|
||||
identity: matches!(network_type, NetworkType::EAP)
|
||||
.then(String::new),
|
||||
hw_address,
|
||||
password: SecureString::from(""),
|
||||
password_hidden: true,
|
||||
tx: Arc::new(Mutex::new(None)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_ => (),
|
||||
|
|
@ -371,11 +398,9 @@ impl Page {
|
|||
return update_devices(conn.clone());
|
||||
}
|
||||
}
|
||||
|
||||
Message::UpdateDevices(devices) => {
|
||||
self.update_devices(devices);
|
||||
}
|
||||
|
||||
Message::UpdateState(state) => {
|
||||
self.update_state(state);
|
||||
|
||||
|
|
@ -383,7 +408,6 @@ impl Page {
|
|||
return connection_settings(conn.clone());
|
||||
}
|
||||
}
|
||||
|
||||
Message::NetworkManager(
|
||||
network_manager::Event::ActiveConns
|
||||
| network_manager::Event::Devices
|
||||
|
|
@ -397,11 +421,9 @@ impl Page {
|
|||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Message::ConnectionSettings(settings) => {
|
||||
self.ssid_to_uuid = settings;
|
||||
}
|
||||
|
||||
Message::NetworkManager(network_manager::Event::Init {
|
||||
conn,
|
||||
sender,
|
||||
|
|
@ -416,7 +438,6 @@ impl Page {
|
|||
|
||||
return update_devices(conn);
|
||||
}
|
||||
|
||||
Message::NetworkManager(network_manager::Event::WiFiCredentials {
|
||||
ssid,
|
||||
password,
|
||||
|
|
@ -431,7 +452,7 @@ impl Page {
|
|||
NetworkType::EAP => "WPA",
|
||||
NetworkType::Open => "",
|
||||
};
|
||||
let escaped_password = escape_wifi_qr_string(pass);
|
||||
let escaped_password = escape_wifi_qr_string(pass.unsecure());
|
||||
format!(
|
||||
"WIFI:T:{};S:{};P:{};;",
|
||||
security, escaped_ssid, escaped_password
|
||||
|
|
@ -451,11 +472,9 @@ impl Page {
|
|||
// Open the context drawer
|
||||
return cosmic::task::message(crate::app::Message::OpenContextDrawer(self.entity));
|
||||
}
|
||||
|
||||
Message::AddNetwork => {
|
||||
tokio::task::spawn(super::nm_add_wifi());
|
||||
}
|
||||
|
||||
Message::Connect(ssid) => {
|
||||
if let Some(nm) = self.nm_state.as_mut() {
|
||||
let Some(ap) = nm
|
||||
|
|
@ -474,10 +493,10 @@ impl Page {
|
|||
ssid,
|
||||
ap.hw_address,
|
||||
ap.network_type,
|
||||
self.secret_tx.clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Message::IdentityUpdate(new_identity) => {
|
||||
if let Some(WiFiDialog::Password {
|
||||
ref mut identity, ..
|
||||
|
|
@ -486,7 +505,6 @@ impl Page {
|
|||
*identity = Some(new_identity);
|
||||
}
|
||||
}
|
||||
|
||||
Message::PasswordRequest(ssid) => {
|
||||
if let Some(nm) = self.nm_state.as_mut() {
|
||||
let Some(ap) = nm
|
||||
|
|
@ -498,16 +516,17 @@ impl Page {
|
|||
else {
|
||||
return Task::none();
|
||||
};
|
||||
|
||||
self.dialog = Some(WiFiDialog::Password {
|
||||
ssid,
|
||||
identity: matches!(ap.network_type, NetworkType::EAP).then(String::new),
|
||||
hw_address: ap.hw_address,
|
||||
password: SecureString::from(""),
|
||||
password_hidden: true,
|
||||
tx: Arc::new(Mutex::new(None)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Message::PasswordUpdate(pass) => {
|
||||
if let Some(WiFiDialog::Password {
|
||||
ref mut password, ..
|
||||
|
|
@ -516,7 +535,6 @@ impl Page {
|
|||
*password = pass;
|
||||
}
|
||||
}
|
||||
|
||||
Message::ConnectWithPassword => {
|
||||
let Some(dialog) = self.dialog.take() else {
|
||||
return Task::none();
|
||||
|
|
@ -527,22 +545,31 @@ impl Page {
|
|||
identity,
|
||||
password,
|
||||
hw_address,
|
||||
tx,
|
||||
..
|
||||
} = dialog
|
||||
&& let Some(nm) = self.nm_state.as_mut()
|
||||
{
|
||||
self.connecting.insert(ssid.clone());
|
||||
_ = nm
|
||||
.sender
|
||||
.unbounded_send(network_manager::Request::Authenticate {
|
||||
ssid: ssid.to_string(),
|
||||
identity,
|
||||
hw_address,
|
||||
password,
|
||||
});
|
||||
let nm_sender = nm.sender.clone();
|
||||
let secret_tx = self.secret_tx.clone();
|
||||
return Task::future(async move {
|
||||
let mut guard = tx.lock().await;
|
||||
if let Some(tx) = guard.take() {
|
||||
_ = tx.send(password);
|
||||
} else {
|
||||
_ = nm_sender.unbounded_send(network_manager::Request::Authenticate {
|
||||
ssid: ssid.to_string(),
|
||||
identity,
|
||||
hw_address,
|
||||
password,
|
||||
secret_tx,
|
||||
});
|
||||
}
|
||||
})
|
||||
.discard();
|
||||
}
|
||||
}
|
||||
|
||||
Message::TogglePasswordVisibility => {
|
||||
if let Some(WiFiDialog::Password {
|
||||
ref mut password_hidden,
|
||||
|
|
@ -552,23 +579,39 @@ impl Page {
|
|||
*password_hidden = !*password_hidden;
|
||||
}
|
||||
}
|
||||
|
||||
Message::QRCodeRequest(ssid) => {
|
||||
self.view_more_popup = None;
|
||||
if let Some(nm) = self.nm_state.as_mut() {
|
||||
let Some(ap): Option<&AccessPoint> = self.nm_state.as_ref().and_then(|nm| {
|
||||
nm.state
|
||||
.wireless_access_points
|
||||
.iter()
|
||||
.chain(nm.state.known_access_points.iter())
|
||||
.find(|ap| ap.ssid == ssid)
|
||||
}) else {
|
||||
return Task::none();
|
||||
};
|
||||
if let Some(nm) = self.nm_state.as_ref() {
|
||||
let uuid = self
|
||||
.ssid_to_uuid
|
||||
.get(ssid.as_ref())
|
||||
.map(|uuid| uuid.as_ref())
|
||||
.unwrap_or_default();
|
||||
_ = nm
|
||||
.sender
|
||||
.unbounded_send(network_manager::Request::GetWiFiCredentials(ssid));
|
||||
.unbounded_send(network_manager::Request::GetWiFiCredentials(
|
||||
ssid,
|
||||
uuid.into(),
|
||||
ap.network_type,
|
||||
self.secret_tx.clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Message::ViewMore(ssid) => {
|
||||
self.view_more_popup = ssid;
|
||||
if self.view_more_popup.is_none() {
|
||||
self.close_popup_and_apply_updates();
|
||||
}
|
||||
}
|
||||
|
||||
Message::Disconnect(ssid) => {
|
||||
self.close_popup_and_apply_updates();
|
||||
if let Some(nm) = self.nm_state.as_mut() {
|
||||
|
|
@ -577,12 +620,10 @@ impl Page {
|
|||
.unbounded_send(network_manager::Request::Disconnect(ssid));
|
||||
}
|
||||
}
|
||||
|
||||
Message::ForgetRequest(ssid) => {
|
||||
self.dialog = Some(WiFiDialog::Forget(ssid));
|
||||
self.view_more_popup = None;
|
||||
}
|
||||
|
||||
Message::Forget(ssid) => {
|
||||
self.dialog = None;
|
||||
self.close_popup_and_apply_updates();
|
||||
|
|
@ -592,7 +633,6 @@ impl Page {
|
|||
.unbounded_send(network_manager::Request::Forget(ssid));
|
||||
}
|
||||
}
|
||||
|
||||
Message::Settings(ssid) => {
|
||||
self.close_popup_and_apply_updates();
|
||||
|
||||
|
|
@ -602,13 +642,11 @@ impl Page {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
Message::SubmitIdentity => {
|
||||
if self.dialog.is_some() {
|
||||
return focus_next();
|
||||
}
|
||||
}
|
||||
|
||||
Message::WiFiEnable(enable) => {
|
||||
if let Some(nm) = self.nm_state.as_mut() {
|
||||
_ = nm
|
||||
|
|
@ -617,26 +655,92 @@ impl Page {
|
|||
_ = nm.sender.unbounded_send(network_manager::Request::Reload);
|
||||
}
|
||||
}
|
||||
|
||||
Message::CancelDialog => {
|
||||
self.dialog = None;
|
||||
}
|
||||
|
||||
Message::Error(why) => {
|
||||
tracing::error!(why);
|
||||
}
|
||||
|
||||
Message::SelectDevice(device) => {
|
||||
// TODO: Per-device wifi connection handling.
|
||||
self.active_device = Some(device);
|
||||
}
|
||||
|
||||
Message::NetworkManagerConnect(conn) => {
|
||||
return cosmic::task::batch(vec![
|
||||
self.connect(conn.clone()),
|
||||
connection_settings(conn),
|
||||
]);
|
||||
}
|
||||
Message::SecretAgent(event) => match event {
|
||||
nm_secret_agent::Event::RequestSecret {
|
||||
uuid,
|
||||
name,
|
||||
description: _, // TODO do we want to display the description?
|
||||
previous,
|
||||
tx,
|
||||
} => {
|
||||
let ssid = self
|
||||
.ssid_to_uuid
|
||||
.iter()
|
||||
.find_map(|(ssid, conn_uuid)| {
|
||||
if conn_uuid.as_ref() == name.as_str() {
|
||||
Some(network_manager::SSID::from(ssid.as_ref()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let Some(ap): Option<&AccessPoint> = self.nm_state.as_ref().and_then(|nm| {
|
||||
nm.state
|
||||
.wireless_access_points
|
||||
.iter()
|
||||
.chain(nm.state.known_access_points.iter())
|
||||
.find(|ap| ap.ssid == ssid)
|
||||
}) else {
|
||||
tracing::error!(
|
||||
%uuid,
|
||||
%name,
|
||||
"received secret request for unknown connection"
|
||||
);
|
||||
return Task::none();
|
||||
};
|
||||
|
||||
self.dialog = Some(WiFiDialog::Password {
|
||||
ssid,
|
||||
password: previous,
|
||||
password_hidden: true,
|
||||
hw_address: ap.hw_address,
|
||||
identity: matches!(ap.network_type, NetworkType::EAP).then(String::new),
|
||||
tx,
|
||||
});
|
||||
}
|
||||
nm_secret_agent::Event::CancelGetSecrets { uuid: _, name: _ } => {
|
||||
self.dialog = self
|
||||
.dialog
|
||||
.take()
|
||||
.filter(|d| !matches!(d, &WiFiDialog::Password { .. }));
|
||||
}
|
||||
nm_secret_agent::Event::Failed(error) => {
|
||||
tracing::error!(%error, "secret agent failure");
|
||||
if let Some(WiFiDialog::Password {
|
||||
ssid,
|
||||
password,
|
||||
identity,
|
||||
hw_address,
|
||||
..
|
||||
}) = self.dialog.take()
|
||||
{
|
||||
self.dialog = Some(WiFiDialog::Password {
|
||||
password,
|
||||
password_hidden: true,
|
||||
tx: Arc::new(Mutex::new(None)),
|
||||
ssid,
|
||||
identity,
|
||||
hw_address,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Task::none()
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ rust-version.workspace = true
|
|||
publish = true
|
||||
|
||||
[dependencies]
|
||||
cosmic-dbus-networkmanager = { git = "https://github.com/pop-os/dbus-settings-bindings" }
|
||||
cosmic-dbus-networkmanager = { git = "https://github.com/pop-os/dbus-settings-bindings" }
|
||||
secret-service = { version = "5.1.0", features = ["rt-tokio-crypto-rust"] }
|
||||
nm-secret-agent-manager = { git = "https://github.com/pop-os/dbus-settings-bindings" }
|
||||
futures = "0.3.31"
|
||||
iced_futures = { git = "https://github.com/pop-os/libcosmic" }
|
||||
itertools = "0.14.0"
|
||||
|
|
@ -15,4 +17,5 @@ secure-string = "0.3.0"
|
|||
thiserror = "2.0.17"
|
||||
tokio = "1.48.0"
|
||||
tracing = "0.1.41"
|
||||
zbus = "5.12.0"
|
||||
zbus = { version = "5.12.0", features = ["tokio"] }
|
||||
bitflags = "2.10.0"
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ pub mod available_wifi;
|
|||
pub mod current_networks;
|
||||
pub mod devices;
|
||||
pub mod hw_address;
|
||||
pub mod nm_secret_agent;
|
||||
pub mod wireless_enabled;
|
||||
|
||||
use std::{collections::HashMap, fmt::Debug, sync::Arc, time::Duration};
|
||||
|
|
@ -18,11 +19,11 @@ use cosmic_dbus_networkmanager::{
|
|||
active_connection::ActiveConnection,
|
||||
device::SpecificDevice,
|
||||
interface::{
|
||||
active_connection::ActiveConnectionProxy,
|
||||
enums::{self, ActiveConnectionState, DeviceType, NmConnectivityState},
|
||||
settings::connection::ConnectionSettingsProxy,
|
||||
},
|
||||
nm::NetworkManager,
|
||||
settings::NetworkManagerSettings,
|
||||
settings::{NetworkManagerSettings, connection::Connection},
|
||||
};
|
||||
use futures::{
|
||||
FutureExt, SinkExt, StreamExt,
|
||||
|
|
@ -54,6 +55,8 @@ pub enum Error {
|
|||
NoWiFiDevices,
|
||||
#[error("zbus error")]
|
||||
Zbus(#[from] zbus::Error),
|
||||
#[error("missing connection field")]
|
||||
MissingField(&'static str),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -297,8 +300,10 @@ async fn start_listening(
|
|||
identity,
|
||||
password,
|
||||
hw_address,
|
||||
secret_tx,
|
||||
}) => {
|
||||
let nm_state = NetworkManagerState::new(&conn).await.unwrap_or_default();
|
||||
|
||||
let success = nm_state
|
||||
.connect_wifi(
|
||||
&conn,
|
||||
|
|
@ -306,6 +311,12 @@ async fn start_listening(
|
|||
identity.as_deref(),
|
||||
Some(password.unsecure()),
|
||||
hw_address,
|
||||
secret_tx.clone(),
|
||||
if identity.is_some() {
|
||||
NetworkType::EAP
|
||||
} else {
|
||||
NetworkType::PSK
|
||||
},
|
||||
)
|
||||
.await
|
||||
.is_ok();
|
||||
|
|
@ -317,6 +328,7 @@ async fn start_listening(
|
|||
identity: identity.clone(),
|
||||
password: password.clone(),
|
||||
hw_address,
|
||||
secret_tx: secret_tx.clone(),
|
||||
},
|
||||
success,
|
||||
state: NetworkManagerState::new(&conn).await.unwrap_or_default(),
|
||||
|
|
@ -324,19 +336,35 @@ async fn start_listening(
|
|||
.await;
|
||||
}
|
||||
|
||||
Some(Request::SelectAccessPoint(ssid, hw_address, network_type)) => {
|
||||
Some(Request::SelectAccessPoint(ssid, hw_address, network_type, secret_tx)) => {
|
||||
if matches!(network_type, NetworkType::Open) {
|
||||
attempt_wifi_connection(&conn, ssid, hw_address, network_type, output)
|
||||
.await;
|
||||
attempt_wifi_connection(
|
||||
&conn,
|
||||
ssid,
|
||||
hw_address,
|
||||
network_type,
|
||||
output,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
} else {
|
||||
// For secured networks, check if we have saved credentials
|
||||
if !has_saved_wifi_credentials(&conn, &ssid).await {
|
||||
let has_saved = has_saved_wifi_credentials(&conn, &ssid).await;
|
||||
|
||||
if !has_saved {
|
||||
return State::Waiting(conn, rx);
|
||||
}
|
||||
|
||||
// We have saved credentials, attempt connection
|
||||
attempt_wifi_connection(&conn, ssid, hw_address, network_type, output)
|
||||
.await;
|
||||
attempt_wifi_connection(
|
||||
&conn,
|
||||
ssid,
|
||||
hw_address,
|
||||
network_type,
|
||||
output,
|
||||
secret_tx,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -451,7 +479,7 @@ async fn start_listening(
|
|||
.await;
|
||||
}
|
||||
|
||||
Some(Request::GetWiFiCredentials(ssid)) => {
|
||||
Some(Request::GetWiFiCredentials(ssid, uuid, security_type, secret_tx)) => {
|
||||
let s = match NetworkManagerSettings::new(&conn).await {
|
||||
Ok(s) => s,
|
||||
Err(why) => {
|
||||
|
|
@ -460,28 +488,77 @@ async fn start_listening(
|
|||
}
|
||||
};
|
||||
|
||||
// Determine network type - default to PSK for encrypted networks
|
||||
let mut security_type = NetworkType::PSK;
|
||||
match security_type {
|
||||
NetworkType::Open => {
|
||||
_ = output
|
||||
.send(Event::WiFiCredentials {
|
||||
ssid: ssid.clone(),
|
||||
password: None,
|
||||
security_type,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
t => {
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
let setting_name = if matches!(t, NetworkType::PSK) {
|
||||
"802-11-wireless-security"
|
||||
} else {
|
||||
"802-1x"
|
||||
};
|
||||
let pw_key = if matches!(t, NetworkType::PSK) {
|
||||
"psk"
|
||||
} else {
|
||||
"password"
|
||||
};
|
||||
if let Some(secret_tx) = secret_tx {
|
||||
let _ = secret_tx
|
||||
.send(nm_secret_agent::Request::GetSecrets {
|
||||
setting_name: setting_name.to_string(),
|
||||
uuid: uuid.to_string(),
|
||||
resp_tx: tx,
|
||||
})
|
||||
.await;
|
||||
let _ = tokio::time::timeout(Duration::from_secs(10), async move {
|
||||
if let Some(password) =
|
||||
rx.await.ok().and_then(|mut secrets| secrets.remove(pw_key))
|
||||
{
|
||||
_ = output
|
||||
.send(Event::WiFiCredentials {
|
||||
ssid: ssid.clone(),
|
||||
password: Some(password),
|
||||
security_type,
|
||||
})
|
||||
.await;
|
||||
} else {
|
||||
_ = output
|
||||
.send(Event::WiFiCredentials {
|
||||
ssid: ssid.clone(),
|
||||
password: None,
|
||||
security_type,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
let known_conns = s.list_connections().await.unwrap_or_default();
|
||||
for c in known_conns {
|
||||
let settings = c.get_settings().await.ok().unwrap_or_default();
|
||||
let settings_parsed = Settings::new(settings.clone());
|
||||
|
||||
let known_conns = s.list_connections().await.unwrap_or_default();
|
||||
for c in known_conns {
|
||||
let settings = c.get_settings().await.ok().unwrap_or_default();
|
||||
let settings_parsed = Settings::new(settings.clone());
|
||||
|
||||
if let Some(saved_ssid) = settings_parsed
|
||||
.wifi
|
||||
.clone()
|
||||
.and_then(|w| w.ssid)
|
||||
.and_then(|s| String::from_utf8(s).ok())
|
||||
{
|
||||
if saved_ssid == ssid.as_ref() {
|
||||
let password = c
|
||||
.get_secrets("802-11-wireless-security")
|
||||
.await
|
||||
.ok()
|
||||
.and_then(|secrets| {
|
||||
// Look for PSK password
|
||||
secrets
|
||||
if let Some(saved_ssid) = settings_parsed
|
||||
.wifi
|
||||
.clone()
|
||||
.and_then(|w| w.ssid)
|
||||
.and_then(|s| String::from_utf8(s).ok())
|
||||
{
|
||||
if saved_ssid == ssid.as_ref() {
|
||||
let password =
|
||||
c.get_secrets("802-11-wireless-security")
|
||||
.await
|
||||
.ok()
|
||||
.and_then(|secrets| {
|
||||
// Look for PSK password
|
||||
secrets
|
||||
.get("802-11-wireless-security")
|
||||
.and_then(|sec| sec.get("psk"))
|
||||
.and_then(|v| {
|
||||
|
|
@ -498,21 +575,19 @@ async fn start_listening(
|
|||
})
|
||||
.map(|s| s.to_string())
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// If no password found, might be open network
|
||||
if password.is_none() {
|
||||
security_type = NetworkType::Open;
|
||||
_ = output
|
||||
.send(Event::WiFiCredentials {
|
||||
ssid: ssid.clone(),
|
||||
password: password.map(SecureString::from),
|
||||
security_type,
|
||||
})
|
||||
.await;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ = output
|
||||
.send(Event::WiFiCredentials {
|
||||
ssid: ssid.clone(),
|
||||
password,
|
||||
security_type,
|
||||
})
|
||||
.await;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -568,11 +643,19 @@ async fn attempt_wifi_connection(
|
|||
hw_address: HwAddress,
|
||||
network_type: NetworkType,
|
||||
output: &mut futures::channel::mpsc::Sender<Event>,
|
||||
secret_tx: Option<tokio::sync::mpsc::Sender<nm_secret_agent::Request>>,
|
||||
) {
|
||||
let state = NetworkManagerState::new(conn).await.unwrap_or_default();
|
||||
|
||||
let success = if let Err(err) = state
|
||||
.connect_wifi(conn, ssid.as_ref(), None, None, hw_address)
|
||||
.connect_wifi(
|
||||
conn,
|
||||
ssid.as_ref(),
|
||||
None,
|
||||
None,
|
||||
hw_address,
|
||||
secret_tx,
|
||||
network_type,
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::error!("Failed to connect to access point: {:?}", err);
|
||||
|
|
@ -583,7 +666,7 @@ async fn attempt_wifi_connection(
|
|||
|
||||
_ = request_response(
|
||||
conn,
|
||||
Request::SelectAccessPoint(ssid, hw_address, network_type),
|
||||
Request::SelectAccessPoint(ssid, hw_address, network_type, None),
|
||||
success,
|
||||
)
|
||||
.then(|event| output.send(event))
|
||||
|
|
@ -606,15 +689,26 @@ pub enum Request {
|
|||
identity: Option<String>,
|
||||
password: SecureString,
|
||||
hw_address: HwAddress,
|
||||
secret_tx: Option<tokio::sync::mpsc::Sender<nm_secret_agent::Request>>,
|
||||
},
|
||||
/// Get WiFi credentials for a known access point.
|
||||
GetWiFiCredentials(SSID),
|
||||
GetWiFiCredentials(
|
||||
SSID,
|
||||
UUID,
|
||||
NetworkType,
|
||||
Option<tokio::sync::mpsc::Sender<nm_secret_agent::Request>>,
|
||||
),
|
||||
/// Signal to reload the service.
|
||||
Reload,
|
||||
/// Remove a connection profile.
|
||||
Remove(UUID),
|
||||
/// Connect to a known access point.
|
||||
SelectAccessPoint(SSID, HwAddress, NetworkType),
|
||||
SelectAccessPoint(
|
||||
SSID,
|
||||
HwAddress,
|
||||
NetworkType,
|
||||
Option<tokio::sync::mpsc::Sender<nm_secret_agent::Request>>,
|
||||
),
|
||||
/// Toggle airplaine mode.
|
||||
SetAirplaneMode(bool),
|
||||
/// Toggle WiFi enablement.
|
||||
|
|
@ -639,7 +733,7 @@ pub enum Event {
|
|||
ActiveConns,
|
||||
WiFiCredentials {
|
||||
ssid: SSID,
|
||||
password: Option<String>,
|
||||
password: Option<SecureString>,
|
||||
security_type: NetworkType,
|
||||
},
|
||||
}
|
||||
|
|
@ -814,6 +908,8 @@ impl NetworkManagerState {
|
|||
identity: Option<&str>,
|
||||
password: Option<&str>,
|
||||
hw_address: HwAddress,
|
||||
secret_tx: Option<tokio::sync::mpsc::Sender<nm_secret_agent::Request>>,
|
||||
network_type: NetworkType,
|
||||
) -> Result<(), Error> {
|
||||
let nm = NetworkManager::new(conn).await?;
|
||||
|
||||
|
|
@ -849,6 +945,7 @@ impl NetworkManagerState {
|
|||
]),
|
||||
),
|
||||
]);
|
||||
|
||||
if let Some(identity) = identity {
|
||||
conn_settings.insert(
|
||||
"802-1x",
|
||||
|
|
@ -858,9 +955,14 @@ impl NetworkManagerState {
|
|||
("eap", Value::Array(vec!["peap"].into())),
|
||||
// most common default
|
||||
("phase2-auth", Value::Str("mschapv2".into())),
|
||||
("password", Value::Str(password.unwrap_or("").into())),
|
||||
]),
|
||||
);
|
||||
if secret_tx.is_none() {
|
||||
conn_settings
|
||||
.get_mut("802-1x")
|
||||
.unwrap()
|
||||
.insert("password", Value::Str(password.unwrap_or("").into()));
|
||||
}
|
||||
let wireless = conn_settings.get_mut("802-11-wireless").unwrap();
|
||||
wireless.insert("security", Value::Str("802-11-wireless-security".into()));
|
||||
wireless.insert("mode", Value::Str("infrastructure".into()));
|
||||
|
|
@ -869,13 +971,11 @@ impl NetworkManagerState {
|
|||
HashMap::from([("key-mgmt", Value::Str("wpa-eap".into()))]),
|
||||
);
|
||||
} else if let Some(pass) = password {
|
||||
conn_settings.insert(
|
||||
"802-11-wireless-security",
|
||||
HashMap::from([
|
||||
("psk", Value::Str(pass.into())),
|
||||
("key-mgmt", Value::Str("wpa-psk".into())),
|
||||
]),
|
||||
);
|
||||
let entry = conn_settings.entry("802-11-wireless-security").or_default();
|
||||
_ = entry.insert("key-mgmt", Value::Str("wpa-psk".into()));
|
||||
if secret_tx.is_none() {
|
||||
_ = entry.insert("psk", Value::Str(pass.into()));
|
||||
}
|
||||
}
|
||||
|
||||
let devices = nm.devices().await?;
|
||||
|
|
@ -907,30 +1007,98 @@ impl NetworkManagerState {
|
|||
}
|
||||
}
|
||||
|
||||
let active_conn = if let Some(known_conn) = known_conn.as_ref() {
|
||||
// update settings if needed
|
||||
if password.is_some() {
|
||||
let known_conn = if let Some(known_conn) = known_conn {
|
||||
if secret_tx.is_none() {
|
||||
known_conn.update(conn_settings).await?;
|
||||
}
|
||||
|
||||
nm.activate_connection(known_conn, &device).await?
|
||||
known_conn
|
||||
} else {
|
||||
let (_, active_conn) = nm
|
||||
.add_and_activate_connection(conn_settings, device.inner().path(), &ap.path)
|
||||
.await?;
|
||||
let dummy = ActiveConnectionProxy::new(conn, active_conn).await?;
|
||||
let active = ActiveConnectionProxy::builder(conn)
|
||||
.destination(dummy.inner().destination().to_owned())
|
||||
.unwrap()
|
||||
.interface(dummy.inner().interface().to_owned())
|
||||
.unwrap()
|
||||
.path(dummy.inner().path().to_owned())
|
||||
.unwrap()
|
||||
.build()
|
||||
.await
|
||||
.unwrap();
|
||||
ActiveConnection::from(active)
|
||||
let settings = nm.settings().await?;
|
||||
let object_path = settings.add_connection(conn_settings).await?;
|
||||
let known_connection = Connection::from(
|
||||
ConnectionSettingsProxy::builder(settings.inner().connection())
|
||||
.path(object_path)?
|
||||
.build()
|
||||
.await?,
|
||||
);
|
||||
let settings = known_connection.get_settings().await?;
|
||||
let uuid = String::try_from(
|
||||
settings
|
||||
.get("connection")
|
||||
.ok_or_else(|| Error::MissingField("connection"))?
|
||||
.get("uuid")
|
||||
.ok_or_else(|| Error::MissingField("uuid"))?
|
||||
.clone(),
|
||||
)
|
||||
.map_err(|err| zbus::Error::Variant(err))?;
|
||||
|
||||
if let Some((pass, secret_tx)) = password.clone().zip(secret_tx.as_ref()) {
|
||||
let pass = SecureString::from(pass);
|
||||
let (applied_tx, applied_rx) = tokio::sync::oneshot::channel();
|
||||
|
||||
let _ = secret_tx
|
||||
.send(nm_secret_agent::Request::SetSecrets {
|
||||
setting_name: if identity.is_some() {
|
||||
"802-1x".into()
|
||||
} else {
|
||||
"802-11-wireless-security".into()
|
||||
},
|
||||
uuid,
|
||||
secrets: if identity.is_some() {
|
||||
HashMap::from([("password".to_string(), pass)])
|
||||
} else {
|
||||
HashMap::from([("psk".to_string(), pass)])
|
||||
},
|
||||
applied_tx,
|
||||
})
|
||||
.await;
|
||||
if let Err(err) = applied_rx.await {
|
||||
tracing::error!("Failed to set secret. {err:?}");
|
||||
}
|
||||
}
|
||||
|
||||
known_connection
|
||||
};
|
||||
|
||||
if let Some(pass) = password {
|
||||
let pass = SecureString::from(pass);
|
||||
if let Some(secret_tx) = secret_tx.as_ref() {
|
||||
let settings = known_conn.get_settings().await?;
|
||||
let uuid = String::try_from(
|
||||
settings
|
||||
.get("connection")
|
||||
.ok_or_else(|| Error::MissingField("connection"))?
|
||||
.get("uuid")
|
||||
.ok_or_else(|| Error::MissingField("uuid"))?
|
||||
.clone(),
|
||||
)
|
||||
.map_err(|err| zbus::Error::Variant(err))?;
|
||||
let (applied_tx, applied_rx) = tokio::sync::oneshot::channel();
|
||||
let setting_name: String = if identity.is_some() {
|
||||
"802-1x".into()
|
||||
} else {
|
||||
"802-11-wireless-security".into()
|
||||
};
|
||||
let _ = secret_tx
|
||||
.send(nm_secret_agent::Request::SetSecrets {
|
||||
setting_name,
|
||||
uuid,
|
||||
secrets: if identity.is_some() {
|
||||
HashMap::from([("password".to_string(), pass)])
|
||||
} else {
|
||||
HashMap::from([("psk".to_string(), pass)])
|
||||
},
|
||||
applied_tx,
|
||||
})
|
||||
.await;
|
||||
if let Err(err) = applied_rx.await {
|
||||
tracing::error!("Failed to set secret. {err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let active_conn =
|
||||
ActiveConnection::from(nm.activate_connection(&known_conn, &device).await?);
|
||||
let mut changes = active_conn.receive_state_changed().await;
|
||||
_ = tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
|
||||
let mut count = 5;
|
||||
|
|
@ -939,8 +1107,9 @@ impl NetworkManagerState {
|
|||
if let Ok(enums::ActiveConnectionState::Activated) = state {
|
||||
return Ok(());
|
||||
} else if let Ok(enums::ActiveConnectionState::Deactivated) = state {
|
||||
return Err(Error::ConnectionActivate);
|
||||
break;
|
||||
}
|
||||
|
||||
if let Ok(Some(s)) =
|
||||
tokio::time::timeout(Duration::from_secs(20), changes.next()).await
|
||||
{
|
||||
|
|
@ -952,9 +1121,47 @@ impl NetworkManagerState {
|
|||
|
||||
count -= 1;
|
||||
if count <= 0 {
|
||||
return Err(Error::ConnectionActivate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(secret_tx) = secret_tx
|
||||
&& !matches!(network_type, NetworkType::Open)
|
||||
{
|
||||
let settings = known_conn.get_settings().await?;
|
||||
let uuid = String::try_from(
|
||||
settings
|
||||
.get("connection")
|
||||
.ok_or_else(|| Error::MissingField("connection"))?
|
||||
.get("uuid")
|
||||
.ok_or_else(|| Error::MissingField("uuid"))?
|
||||
.clone(),
|
||||
)
|
||||
.map_err(|err| zbus::Error::Variant(err))?;
|
||||
let (applied_tx, applied_rx) = tokio::sync::oneshot::channel();
|
||||
let setting_name: String = if identity.is_some() {
|
||||
"802-1x".into()
|
||||
} else {
|
||||
"802-11-wireless-security".into()
|
||||
};
|
||||
if let Err(err) = secret_tx
|
||||
.send(nm_secret_agent::Request::SetSecrets {
|
||||
setting_name,
|
||||
uuid,
|
||||
secrets: Default::default(),
|
||||
applied_tx,
|
||||
})
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
"Failed to reset access point secrets after failed activation: {err:?}"
|
||||
);
|
||||
} else if let Err(err) = applied_rx.await {
|
||||
tracing::error!(
|
||||
"Failed to reset access point secrets after failed activation: {err:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
return Err(Error::ConnectionActivate);
|
||||
}
|
||||
|
||||
Err(Error::NoWiFiDevices)
|
||||
|
|
|
|||
740
subscriptions/network-manager/src/nm_secret_agent.rs
Normal file
740
subscriptions/network-manager/src/nm_secret_agent.rs
Normal file
|
|
@ -0,0 +1,740 @@
|
|||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fmt::Debug,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use bitflags::bitflags;
|
||||
use cosmic_dbus_networkmanager::interface::settings::connection::ConnectionSettingsProxy;
|
||||
use futures::{SinkExt, Stream};
|
||||
use secure_string::SecureString;
|
||||
use tokio::sync::oneshot;
|
||||
use zbus::{
|
||||
ObjectServer, fdo,
|
||||
zvariant::{OwnedValue, Str},
|
||||
};
|
||||
|
||||
pub type SecretSender = Arc<tokio::sync::Mutex<Option<tokio::sync::oneshot::Sender<SecureString>>>>;
|
||||
|
||||
pub const SECRET_ID: &'static str = "com.system76.CosmicSettings.NetworkManager";
|
||||
pub const DBUS_PATH: &str = "/org/freedesktop/NetworkManager/SecretAgent";
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct GetSecretsFlags: u32 {
|
||||
/// No special behavior.
|
||||
/// By default no user interaction is allowed and secrets must come
|
||||
/// from persistent storage, otherwise an error is returned.
|
||||
const NONE = 0x0;
|
||||
|
||||
/// Allows interaction with the user (eg. prompt via UI).
|
||||
const ALLOW_INTERACTION = 0x1;
|
||||
|
||||
/// Explicitly request new secrets from the user.
|
||||
/// Implies ALLOW_INTERACTION.
|
||||
const REQUEST_NEW = 0x2;
|
||||
|
||||
/// Request was initiated by a user action (via D-Bus).
|
||||
const USER_REQUESTED = 0x4;
|
||||
|
||||
/// Internal flag, not part of the public D-Bus API.
|
||||
const ONLY_SYSTEM = 0x8000_0000;
|
||||
|
||||
/// Internal flag, not part of the public D-Bus API.
|
||||
const NO_ERRORS = 0x4000_0000;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Clone, Debug)]
|
||||
pub enum Error {
|
||||
#[error("zbus error")]
|
||||
Zbus(#[from] zbus::Error),
|
||||
#[error("listening for secret agent closed")]
|
||||
RecvError(#[from] oneshot::error::RecvError),
|
||||
#[error("secret service error")]
|
||||
SecretService(#[from] Arc<secret_service::Error>),
|
||||
#[error("no password found for identifier: {0}")]
|
||||
NoPasswordForIdentifier(String),
|
||||
#[error("utf8 error")]
|
||||
Utf8Error(#[from] std::string::FromUtf8Error),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum PasswordFlag {
|
||||
/// The system is responsible for providing and storing this secret.
|
||||
None = 0,
|
||||
/// A user-session secret agent is responsible for providing and storing
|
||||
/// this secret; when it is required, agents will be asked to provide it.
|
||||
AgentOwned = 1,
|
||||
/// This secret should not be saved but should be requested from the user
|
||||
/// each time it is required. This flag should be used for One-Time-Pad
|
||||
/// secrets, PIN codes from hardware tokens, or if the user simply does not
|
||||
/// want to save the secret.
|
||||
NotSaved = 2,
|
||||
/// in some situations it cannot be automatically determined that a secret is required or not. This flag hints that the secret is not required and should not be requested from the user.
|
||||
NotRequired = 4,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SecretHint {
|
||||
pub key: String,
|
||||
pub message: Option<String>,
|
||||
}
|
||||
|
||||
fn parse_hints(hints: Vec<String>) -> Vec<SecretHint> {
|
||||
hints
|
||||
.into_iter()
|
||||
// fold message hints into previous hints
|
||||
.fold(Vec::new(), |mut acc, hint| {
|
||||
if let Some((key, msg)) = hint.split_once(':') {
|
||||
if let Some(last) = acc.last_mut() {
|
||||
last.message = Some(format!("{}: {}", key, msg));
|
||||
}
|
||||
} else {
|
||||
acc.push(SecretHint {
|
||||
key: hint,
|
||||
message: None,
|
||||
});
|
||||
}
|
||||
acc
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Event {
|
||||
RequestSecret {
|
||||
uuid: String,
|
||||
name: String,
|
||||
description: Option<String>,
|
||||
previous: SecureString,
|
||||
tx: SecretSender,
|
||||
},
|
||||
CancelGetSecrets {
|
||||
uuid: String,
|
||||
name: String,
|
||||
},
|
||||
Failed(Error),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Request {
|
||||
SetSecrets {
|
||||
setting_name: String,
|
||||
uuid: String,
|
||||
secrets: HashMap<String, SecureString>,
|
||||
applied_tx: oneshot::Sender<()>,
|
||||
},
|
||||
GetSecrets {
|
||||
setting_name: String,
|
||||
uuid: String,
|
||||
resp_tx: oneshot::Sender<HashMap<String, SecureString>>,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn secret_agent_stream(
|
||||
identifier: impl AsRef<str>,
|
||||
rx: tokio::sync::mpsc::Receiver<Request>,
|
||||
) -> impl Stream<Item = Event> {
|
||||
iced_futures::stream::channel(4, move |mut msg_tx| async move {
|
||||
if let Err(e) = secret_agent_stream_impl(identifier.as_ref(), msg_tx.clone(), rx).await {
|
||||
let _ = msg_tx.send(Event::Failed(e)).await;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async fn secret_agent_stream_impl(
|
||||
identifier: &str,
|
||||
msg_tx: futures::channel::mpsc::Sender<Event>,
|
||||
mut rx: tokio::sync::mpsc::Receiver<Request>,
|
||||
) -> Result<(), Error> {
|
||||
// register the secret agent with NetworkManager
|
||||
let proxy =
|
||||
nm_secret_agent_manager::AgentManagerProxy::builder(&zbus::Connection::system().await?)
|
||||
.path("/org/freedesktop/NetworkManager/AgentManager")?
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
let _ = ObjectServer::at(
|
||||
proxy.inner().connection().object_server(),
|
||||
DBUS_PATH,
|
||||
SettingsSecretAgent { tx: msg_tx },
|
||||
)
|
||||
.await?;
|
||||
|
||||
proxy.register_with_capabilities(identifier, 1).await?;
|
||||
|
||||
while let Some(request) = rx.recv().await {
|
||||
match request {
|
||||
Request::SetSecrets {
|
||||
setting_name,
|
||||
uuid,
|
||||
secrets,
|
||||
applied_tx,
|
||||
} => {
|
||||
let ss = secret_service::SecretService::connect(secret_service::EncryptionType::Dh)
|
||||
.await
|
||||
.map_err(|e| Arc::new(e))?;
|
||||
let collection = ss.get_default_collection().await.map_err(|e| Arc::new(e))?;
|
||||
|
||||
if secrets.is_empty() {
|
||||
let mut attributes = std::collections::HashMap::new();
|
||||
attributes.insert("application", SECRET_ID);
|
||||
attributes.insert("uuid", &uuid);
|
||||
let search_items = collection
|
||||
.search_items(attributes)
|
||||
.await
|
||||
.map_err(|e| Arc::new(e))?;
|
||||
|
||||
for item in &search_items {
|
||||
item.delete().await.map_err(|e| Arc::new(e))?;
|
||||
}
|
||||
let _ = applied_tx.send(());
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
for (name, secret) in &secrets {
|
||||
let mut attributes = std::collections::HashMap::new();
|
||||
attributes.insert("application", SECRET_ID);
|
||||
attributes.insert("uuid", &uuid);
|
||||
attributes.insert("setting_name", &setting_name);
|
||||
attributes.insert("name", name);
|
||||
let _item = collection
|
||||
.create_item(
|
||||
"NetworkManager Secret",
|
||||
attributes,
|
||||
secret.unsecure().as_bytes(),
|
||||
true,
|
||||
"text/plain",
|
||||
)
|
||||
.await
|
||||
.map_err(|e| Arc::new(e))?;
|
||||
}
|
||||
let _ = applied_tx.send(());
|
||||
}
|
||||
Request::GetSecrets {
|
||||
setting_name,
|
||||
uuid,
|
||||
resp_tx,
|
||||
} => {
|
||||
let ss = secret_service::SecretService::connect(secret_service::EncryptionType::Dh)
|
||||
.await
|
||||
.map_err(|e| Arc::new(e))?;
|
||||
let collection = ss.get_default_collection().await.map_err(|e| Arc::new(e))?;
|
||||
|
||||
let mut attributes = std::collections::HashMap::new();
|
||||
attributes.insert("application", SECRET_ID);
|
||||
attributes.insert("uuid", &uuid);
|
||||
attributes.insert("setting_name", &setting_name);
|
||||
|
||||
let search_items = collection
|
||||
.search_items(attributes)
|
||||
.await
|
||||
.map_err(|e| Arc::new(e))?;
|
||||
|
||||
let mut secrets = HashMap::new();
|
||||
for item in &search_items {
|
||||
let name = item
|
||||
.get_attributes()
|
||||
.await
|
||||
.map_err(|e| Arc::new(e))?
|
||||
.get("name")
|
||||
.cloned()
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
let secret = item.get_secret().await.map_err(|e| Arc::new(e))?;
|
||||
let secret: String = String::from_utf8(secret)?.into();
|
||||
secrets.insert(name, SecureString::from(secret));
|
||||
}
|
||||
let _ = resp_tx.send(secrets);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_secret_flag(value: &str) -> PasswordFlag {
|
||||
match value {
|
||||
"0" => PasswordFlag::None,
|
||||
"1" => PasswordFlag::AgentOwned,
|
||||
"2" => PasswordFlag::NotSaved,
|
||||
"4" => PasswordFlag::NotRequired,
|
||||
_ => PasswordFlag::AgentOwned,
|
||||
}
|
||||
}
|
||||
|
||||
fn setting_has_always_ask(setting: zbus::zvariant::Dict) -> bool {
|
||||
for (key, value) in setting.iter() {
|
||||
let Ok(key) = key.downcast_ref::<zbus::zvariant::Str>() else {
|
||||
continue;
|
||||
};
|
||||
let Ok(value) = value.downcast_ref::<zbus::zvariant::Str>() else {
|
||||
continue;
|
||||
};
|
||||
// we only care about "<secret>-flags"
|
||||
if !key.ends_with("-flags") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if parse_secret_flag(value.as_str()) == PasswordFlag::NotSaved {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn has_always_ask(setting: Option<zbus::zvariant::Dict>) -> bool {
|
||||
setting.map(setting_has_always_ask).unwrap_or(false)
|
||||
}
|
||||
|
||||
fn is_connection_always_ask(connection: &HashMap<String, HashMap<String, OwnedValue>>) -> bool {
|
||||
let conn_setting = match connection.get("connection") {
|
||||
Some(s) => s,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
let conn_type = match conn_setting
|
||||
.get("type")
|
||||
.and_then(|v| v.downcast_ref::<String>().ok())
|
||||
{
|
||||
Some(t) => t,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
// Primary setting (vpn, wifi, ethernet, etc)
|
||||
if has_always_ask(
|
||||
connection
|
||||
.get(&conn_type)
|
||||
.and_then(|d| d.get("data"))
|
||||
.and_then(|data| data.downcast_ref::<zbus::zvariant::Dict>().ok()),
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
match conn_type.as_str() {
|
||||
"802-11-wireless" => {
|
||||
if has_always_ask(
|
||||
connection
|
||||
.get("802-11-wireless-security")
|
||||
.and_then(|d| d.get("data"))
|
||||
.and_then(|data| data.downcast_ref::<zbus::zvariant::Dict>().ok()),
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if has_always_ask(
|
||||
connection
|
||||
.get("802-1x")
|
||||
.and_then(|d| d.get("data"))
|
||||
.and_then(|data| data.downcast_ref::<zbus::zvariant::Dict>().ok()),
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
"802-3-ethernet" => {
|
||||
if has_always_ask(
|
||||
connection
|
||||
.get("pppoe")
|
||||
.and_then(|d| d.get("data"))
|
||||
.and_then(|data| data.downcast_ref::<zbus::zvariant::Dict>().ok()),
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if has_always_ask(
|
||||
connection
|
||||
.get("802-1x")
|
||||
.and_then(|d| d.get("data"))
|
||||
.and_then(|data| data.downcast_ref::<zbus::zvariant::Dict>().ok()),
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SettingsSecretAgent {
|
||||
tx: futures::channel::mpsc::Sender<Event>,
|
||||
}
|
||||
|
||||
#[zbus::interface(name = "org.freedesktop.NetworkManager.SecretAgent")]
|
||||
impl SettingsSecretAgent {
|
||||
/// CancelGetSecrets method
|
||||
async fn cancel_get_secrets(
|
||||
&mut self,
|
||||
connection_path: zbus::zvariant::ObjectPath<'_>,
|
||||
setting_name: String,
|
||||
) -> fdo::Result<()> {
|
||||
let conn = ConnectionSettingsProxy::builder(
|
||||
&zbus::Connection::system()
|
||||
.await
|
||||
.or_else(|_| Err(fdo::Error::Failed("failed to get uuid".to_string())))?,
|
||||
)
|
||||
.path(connection_path)?
|
||||
.build()
|
||||
.await
|
||||
.map_err(|e| fdo::Error::Failed(e.to_string()))?;
|
||||
|
||||
let uuid = conn
|
||||
.get_settings()
|
||||
.await
|
||||
.map_err(|e| fdo::Error::Failed(e.to_string()))?
|
||||
.get("connection")
|
||||
.and_then(|m| m.get("uuid"))
|
||||
.and_then(|v| v.downcast_ref::<String>().ok())
|
||||
.ok_or_else(|| fdo::Error::Failed("failed to get uuid".to_string()))?
|
||||
.to_string();
|
||||
if let Err(e) = self
|
||||
.tx
|
||||
.clone()
|
||||
.send(Event::CancelGetSecrets {
|
||||
uuid,
|
||||
name: setting_name,
|
||||
})
|
||||
.await
|
||||
&& e.is_disconnected()
|
||||
{
|
||||
return Err(fdo::Error::Failed(
|
||||
"failed to send cancel message".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// DeleteSecrets method
|
||||
async fn delete_secrets(
|
||||
&self,
|
||||
connection: HashMap<String, HashMap<String, zbus::zvariant::OwnedValue>>,
|
||||
connection_path: zbus::zvariant::ObjectPath<'_>,
|
||||
) -> fdo::Result<()> {
|
||||
match self.delete_secrets_inner(connection, connection_path).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(fdo::Error::Failed(err.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
/// GetSecrets method
|
||||
async fn get_secrets(
|
||||
&mut self,
|
||||
connection: HashMap<String, HashMap<String, zbus::zvariant::OwnedValue>>,
|
||||
connection_path: zbus::zvariant::ObjectPath<'_>,
|
||||
setting_name: String,
|
||||
hints: Vec<String>,
|
||||
flags: u32,
|
||||
) -> HashMap<String, HashMap<String, zbus::zvariant::OwnedValue>> {
|
||||
match self
|
||||
.get_secrets_inner(connection, connection_path, setting_name, hints, flags)
|
||||
.await
|
||||
{
|
||||
Ok(result) => result,
|
||||
Err(_) => HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// SaveSecrets method
|
||||
async fn save_secrets(
|
||||
&self,
|
||||
connection: HashMap<String, HashMap<String, zbus::zvariant::OwnedValue>>,
|
||||
_connection_path: zbus::zvariant::ObjectPath<'_>,
|
||||
) -> fdo::Result<()> {
|
||||
match self.save_secrets_inner(connection).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(fdo::Error::Failed(err.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SettingsSecretAgent {
|
||||
pub async fn get_secrets_inner(
|
||||
&mut self,
|
||||
connection: HashMap<String, HashMap<String, zbus::zvariant::OwnedValue>>,
|
||||
connection_path: zbus::zvariant::ObjectPath<'_>,
|
||||
setting_name: String,
|
||||
hints: Vec<String>,
|
||||
flags: u32,
|
||||
) -> Result<HashMap<String, HashMap<String, zbus::zvariant::OwnedValue>>, Error> {
|
||||
let flags = GetSecretsFlags::from_bits_truncate(flags);
|
||||
|
||||
let ss = secret_service::SecretService::connect(secret_service::EncryptionType::Dh)
|
||||
.await
|
||||
.map_err(|e| Arc::new(e))?;
|
||||
|
||||
let collection = ss.get_default_collection().await.map_err(|e| Arc::new(e))?;
|
||||
|
||||
let conn_uuid = connection
|
||||
.get("connection")
|
||||
.and_then(|m| m.get("uuid"))
|
||||
.and_then(|v| v.downcast_ref::<String>().ok())
|
||||
.ok_or_else(|| Error::NoPasswordForIdentifier(setting_name.clone()))?
|
||||
.to_string();
|
||||
|
||||
let conn =
|
||||
ConnectionSettingsProxy::builder(&zbus::Connection::system().await.or_else(|_| {
|
||||
Err(Error::Zbus(
|
||||
fdo::Error::Failed("failed to get uuid".to_string()).into(),
|
||||
))
|
||||
})?)
|
||||
.path(connection_path)?
|
||||
.build()
|
||||
.await
|
||||
.map_err(|e| Error::Zbus(fdo::Error::Failed(e.to_string()).into()))?;
|
||||
let settings = conn.get_settings().await?;
|
||||
let is_vpn = settings
|
||||
.get("connection")
|
||||
.and_then(|m| m.get("type"))
|
||||
.and_then(|v| v.downcast_ref::<String>().ok())
|
||||
.map_or(false, |t| t == "vpn");
|
||||
let is_always_ask = is_connection_always_ask(&settings);
|
||||
|
||||
let mut setting_attributes = std::collections::HashMap::new();
|
||||
setting_attributes.insert("application", SECRET_ID);
|
||||
setting_attributes.insert("uuid", &conn_uuid);
|
||||
setting_attributes.insert("setting_name", &setting_name);
|
||||
|
||||
let mut search_items = collection
|
||||
.search_items(setting_attributes.clone())
|
||||
.await
|
||||
.map_err(|e| Arc::new(e))?;
|
||||
let mut result = HashMap::new();
|
||||
let mut setting = HashMap::new();
|
||||
|
||||
if hints.is_empty() {
|
||||
for item in &search_items {
|
||||
let name = item
|
||||
.get_attributes()
|
||||
.await
|
||||
.map_err(|e| Arc::new(e))?
|
||||
.get("name")
|
||||
.cloned()
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
let secret = item.get_secret().await.map_err(|e| Arc::new(e))?;
|
||||
let secret: String = String::from_utf8(secret)?.into();
|
||||
setting.insert(name, zbus::zvariant::OwnedValue::from(Str::from(secret)));
|
||||
}
|
||||
result.insert(setting_name, setting);
|
||||
return Ok(result);
|
||||
} else {
|
||||
let hints = parse_hints(hints);
|
||||
let mut requested = HashSet::new();
|
||||
|
||||
for SecretHint { key, message } in &hints {
|
||||
if requested.contains(key) {
|
||||
continue;
|
||||
}
|
||||
requested.insert(key);
|
||||
if flags.contains(GetSecretsFlags::REQUEST_NEW)
|
||||
&& flags.contains(GetSecretsFlags::ALLOW_INTERACTION)
|
||||
|| is_always_ask
|
||||
{
|
||||
// request the secret via the message channel
|
||||
let (resp_tx, resp_rx) = oneshot::channel();
|
||||
// msg begins after ":"
|
||||
let actual_hint = message.as_ref().map(|m| {
|
||||
m.split_once(":")
|
||||
.map(|(_, msg)| msg.trim().to_string())
|
||||
.unwrap_or(m.clone())
|
||||
});
|
||||
if let Err(e) = self
|
||||
.tx
|
||||
.clone()
|
||||
.send(Event::RequestSecret {
|
||||
uuid: conn_uuid.clone(),
|
||||
name: setting_name.clone(),
|
||||
description: actual_hint.clone(),
|
||||
previous: String::new().into(),
|
||||
tx: Arc::new(tokio::sync::Mutex::new(Some(resp_tx))),
|
||||
})
|
||||
.await
|
||||
&& e.is_disconnected()
|
||||
{
|
||||
continue;
|
||||
} else {
|
||||
if let Ok(secret) = resp_rx.await {
|
||||
let mut named_attribute = setting_attributes.clone();
|
||||
named_attribute.insert("name", key);
|
||||
let _item = collection
|
||||
.create_item(
|
||||
"NetworkManager Secret",
|
||||
named_attribute,
|
||||
secret.unsecure().as_bytes(),
|
||||
true,
|
||||
"text/plain",
|
||||
)
|
||||
.await
|
||||
.map_err(|e| Arc::new(e))?;
|
||||
|
||||
setting.insert(
|
||||
key.clone(),
|
||||
zbus::zvariant::OwnedValue::from(Str::from(secret.unsecure())),
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if !is_always_ask {
|
||||
let mut pos = None;
|
||||
let mut pos_with_message = None;
|
||||
for item in &search_items {
|
||||
let attributes = item.get_attributes().await.map_err(|e| Arc::new(e))?;
|
||||
if let Some(value) = attributes.get("name") {
|
||||
if value == key {
|
||||
if let Some(saved_message) = attributes.get("message") {
|
||||
if message.as_ref().is_some_and(|msg| msg == saved_message) {
|
||||
pos_with_message = Some(item);
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
pos = Some(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(item) = pos_with_message.or(pos) {
|
||||
let secret = item.get_secret().await.map_err(|e| Arc::new(e))?;
|
||||
let secret: String = String::from_utf8(secret)?.into();
|
||||
if is_vpn {
|
||||
// ask anyway, but offer the previous one as a hint
|
||||
let (resp_tx, resp_rx) = oneshot::channel();
|
||||
let actual_hint = message.as_ref().map(|m| {
|
||||
m.split_once(":")
|
||||
.map(|(_, msg)| msg.trim().to_string())
|
||||
.unwrap_or(m.clone())
|
||||
});
|
||||
if let Err(e) = self
|
||||
.tx
|
||||
.clone()
|
||||
.send(Event::RequestSecret {
|
||||
uuid: conn_uuid.clone(),
|
||||
name: setting_name.clone(),
|
||||
description: actual_hint.clone(),
|
||||
previous: SecureString::from(secret.clone()),
|
||||
tx: Arc::new(tokio::sync::Mutex::new(Some(resp_tx))),
|
||||
})
|
||||
.await
|
||||
&& e.is_disconnected()
|
||||
{
|
||||
continue;
|
||||
} else {
|
||||
if let Ok(secret) = resp_rx.await {
|
||||
let mut named_attribute = setting_attributes.clone();
|
||||
named_attribute.insert("name", key);
|
||||
let _item = collection
|
||||
.create_item(
|
||||
"NetworkManager Secret",
|
||||
named_attribute,
|
||||
secret.unsecure().as_bytes(),
|
||||
true,
|
||||
"text/plain",
|
||||
)
|
||||
.await
|
||||
.map_err(|e| Arc::new(e))?;
|
||||
|
||||
setting.insert(
|
||||
key.clone(),
|
||||
zbus::zvariant::OwnedValue::from(Str::from(
|
||||
secret.unsecure(),
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setting.insert(
|
||||
key.clone(),
|
||||
zbus::zvariant::OwnedValue::from(Str::from(secret)),
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// can't find the secret, and we can't request it, so we just skip it
|
||||
continue;
|
||||
}
|
||||
}
|
||||
result.insert(setting_name, setting);
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete_secrets_inner(
|
||||
&self,
|
||||
connection: HashMap<String, HashMap<String, zbus::zvariant::OwnedValue>>,
|
||||
_connection_path: zbus::zvariant::ObjectPath<'_>,
|
||||
) -> Result<(), Error> {
|
||||
let ss = secret_service::SecretService::connect(secret_service::EncryptionType::Dh)
|
||||
.await
|
||||
.map_err(|e| Arc::new(e))?;
|
||||
let collection = ss.get_default_collection().await.map_err(|e| Arc::new(e))?;
|
||||
|
||||
let conn_uuid = connection
|
||||
.get("connection")
|
||||
.and_then(|m| m.get("uuid"))
|
||||
.and_then(|v| v.downcast_ref::<String>().ok())
|
||||
.ok_or_else(|| Error::NoPasswordForIdentifier("unknown".to_string()))?
|
||||
.to_string();
|
||||
|
||||
let mut attributes = std::collections::HashMap::new();
|
||||
attributes.insert("application", SECRET_ID);
|
||||
attributes.insert("uuid", &conn_uuid);
|
||||
|
||||
let search_items = collection
|
||||
.search_items(attributes)
|
||||
.await
|
||||
.map_err(|e| Arc::new(e))?;
|
||||
|
||||
for item in &search_items {
|
||||
item.delete().await.map_err(|e| Arc::new(e))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn save_secrets_inner(
|
||||
&self,
|
||||
connection: HashMap<String, HashMap<String, zbus::zvariant::OwnedValue>>,
|
||||
) -> Result<(), Error> {
|
||||
let ss = secret_service::SecretService::connect(secret_service::EncryptionType::Dh)
|
||||
.await
|
||||
.map_err(|e| Arc::new(e))?;
|
||||
let collection = ss.get_default_collection().await.map_err(|e| Arc::new(e))?;
|
||||
let conn_uuid = connection
|
||||
.get("connection")
|
||||
.and_then(|m| m.get("uuid"))
|
||||
.and_then(|v| v.downcast_ref::<String>().ok())
|
||||
.ok_or_else(|| Error::NoPasswordForIdentifier("unknown".to_string()))?
|
||||
.to_string();
|
||||
|
||||
let secret: Option<(String, String)> = connection
|
||||
.get("802-11-wireless-security")
|
||||
.and_then(|m| m.get("psk"))
|
||||
.and_then(|v| v.downcast_ref::<String>().ok())
|
||||
.map(|password| ("psk".to_string(), password.clone()))
|
||||
.or_else(|| {
|
||||
connection
|
||||
.get("802-1x")
|
||||
.and_then(|s| s.get("password"))
|
||||
.and_then(|v| v.downcast_ref::<String>().ok())
|
||||
.map(|password| ("802-1x-password".to_string(), password.clone()))
|
||||
});
|
||||
if let Some((name, secret)) = secret {
|
||||
let mut attributes = std::collections::HashMap::new();
|
||||
attributes.insert("application", SECRET_ID);
|
||||
attributes.insert("uuid", &conn_uuid);
|
||||
attributes.insert("setting_name", &name);
|
||||
let _item = collection
|
||||
.create_item(
|
||||
"NetworkManager Secret",
|
||||
attributes,
|
||||
secret.as_bytes(),
|
||||
true,
|
||||
"text/plain",
|
||||
)
|
||||
.await
|
||||
.map_err(|e| Arc::new(e))?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::NoPasswordForIdentifier("unknown".to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue