mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-24 13:07:00 +01:00
WIP - Improve Auth Proto to Support Webauthn (#333)
This is a rewrite of the "on the wire" json for auth. This is a breaking change required to allow webauthn to work given limitations within Webauthn as a standard and how mixed credentials are challenged for.
This commit is contained in:
parent
a008ca3cf1
commit
ebdb57bbe7
355
Cargo.lock
generated
355
Cargo.lock
generated
|
@ -62,9 +62,9 @@ checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ahash"
|
name = "ahash"
|
||||||
version = "0.4.6"
|
version = "0.4.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f6789e291be47ace86a60303502173d84af8327e3627ecf334356ee0f87a164c"
|
checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"const-random",
|
"const-random",
|
||||||
]
|
]
|
||||||
|
@ -80,9 +80,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.34"
|
version = "1.0.36"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bf8dcb5b4bbaa28653b647d8c77bd4ed40183b48882e130c1f1ffb73de069fd7"
|
checksum = "68803225a7b13e47191bab76f2687382b60d259e8cf37f6e1893658b84bb9479"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayref"
|
name = "arrayref"
|
||||||
|
@ -146,10 +146,12 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-h1"
|
name = "async-h1"
|
||||||
version = "2.1.4"
|
version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3fd9a5f3dbb5065856974e08c2ac24e6f81da6e39d2328de1c03a9a2b34ffb01"
|
checksum = "e5c68a75f812ff0f299e142c06dd0c34e3295a594d935e61eeb6c77041d1d4dc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"async-channel",
|
||||||
|
"async-dup",
|
||||||
"async-std",
|
"async-std",
|
||||||
"byte-pool",
|
"byte-pool",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
@ -157,7 +159,7 @@ dependencies = [
|
||||||
"httparse",
|
"httparse",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
"pin-project-lite 0.1.11",
|
"pin-project 1.0.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -189,6 +191,22 @@ dependencies = [
|
||||||
"event-listener",
|
"event-listener",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-process"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4c8cea09c1fb10a317d1b5af8024eeba256d6554763e85ecd90ff8df31c7bbda"
|
||||||
|
dependencies = [
|
||||||
|
"async-io",
|
||||||
|
"blocking",
|
||||||
|
"cfg-if 0.1.10",
|
||||||
|
"event-listener",
|
||||||
|
"futures-lite",
|
||||||
|
"once_cell",
|
||||||
|
"signal-hook",
|
||||||
|
"winapi 0.3.9",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-session"
|
name = "async-session"
|
||||||
version = "2.0.1"
|
version = "2.0.1"
|
||||||
|
@ -226,13 +244,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-std"
|
name = "async-std"
|
||||||
version = "1.7.0"
|
version = "1.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a7e82538bc65a25dbdff70e4c5439d52f068048ab97cdea0acd73f131594caa1"
|
checksum = "8f9f84f1280a2b436a2c77c2582602732b6c2f4321d5494d6e799e6c367859a8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"async-channel",
|
||||||
"async-global-executor",
|
"async-global-executor",
|
||||||
"async-io",
|
"async-io",
|
||||||
"async-mutex",
|
"async-mutex",
|
||||||
|
"async-process",
|
||||||
"blocking",
|
"blocking",
|
||||||
"crossbeam-utils 0.8.1",
|
"crossbeam-utils 0.8.1",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
|
@ -245,7 +265,7 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"pin-project-lite 0.1.11",
|
"pin-project-lite 0.2.0",
|
||||||
"pin-utils",
|
"pin-utils",
|
||||||
"slab",
|
"slab",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
|
@ -259,9 +279,9 @@ checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-tls"
|
name = "async-tls"
|
||||||
version = "0.10.0"
|
version = "0.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d85a97c4a0ecce878efd3f945f119c78a646d8975340bca0398f9bb05c30cc52"
|
checksum = "2f23d769dbf1838d5df5156e7b1ad404f4c463d1ac2c6aeb6cd943630f8a8400"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
|
@ -366,9 +386,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit-vec"
|
name = "bit-vec"
|
||||||
version = "0.6.2"
|
version = "0.6.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5f0dc55f2d8a1a85650ac47858bb001b4c0dd73d79e3c455a842925e68d29cd3"
|
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
|
@ -376,17 +396,6 @@ version = "1.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "blake2b_simd"
|
|
||||||
version = "0.5.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587"
|
|
||||||
dependencies = [
|
|
||||||
"arrayref",
|
|
||||||
"arrayvec",
|
|
||||||
"constant_time_eq",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "blake3"
|
name = "blake3"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
|
@ -515,9 +524,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.65"
|
version = "1.0.66"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "95752358c8f7552394baf48cd82695b345628ad3f170d607de3ca03b8dacca15"
|
checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
|
@ -537,11 +546,13 @@ version = "0.4.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
|
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"num-integer",
|
"num-integer",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"serde",
|
"serde",
|
||||||
"time 0.1.44",
|
"time 0.1.44",
|
||||||
|
"wasm-bindgen",
|
||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -565,22 +576,13 @@ dependencies = [
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cloudabi"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "concread"
|
name = "concread"
|
||||||
version = "0.2.6"
|
version = "0.2.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "14fe52c39ed4e846fb3e6ad4bfe46224ef24db64ff7c5f496d2501c88c270b14"
|
checksum = "14fe52c39ed4e846fb3e6ad4bfe46224ef24db64ff7c5f496d2501c88c270b14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.4.6",
|
"ahash 0.4.7",
|
||||||
"crossbeam",
|
"crossbeam",
|
||||||
"crossbeam-epoch 0.8.2",
|
"crossbeam-epoch 0.8.2",
|
||||||
"crossbeam-utils 0.7.2",
|
"crossbeam-utils 0.7.2",
|
||||||
|
@ -599,21 +601,11 @@ dependencies = [
|
||||||
"cache-padded",
|
"cache-padded",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "console_error_panic_hook"
|
|
||||||
version = "0.1.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b8d976903543e0c48546a91908f21588a680a8c8f984df9a5d69feccb2b2a211"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if 0.1.10",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "const-random"
|
name = "const-random"
|
||||||
version = "0.1.12"
|
version = "0.1.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "486d435a7351580347279f374cb8a3c16937485441db80181357b7c4d70f17ed"
|
checksum = "f590d95d011aa80b063ffe3253422ed5aa462af4e9867d43ce8337562bac77c4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"const-random-macro",
|
"const-random-macro",
|
||||||
"proc-macro-hack",
|
"proc-macro-hack",
|
||||||
|
@ -621,9 +613,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "const-random-macro"
|
name = "const-random-macro"
|
||||||
version = "0.1.12"
|
version = "0.1.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49a84d8ff70e3ec52311109b019c27672b4c1929e4cf7c18bcf0cd9fb5e230be"
|
checksum = "615f6e27d000a2bffbc7f2f6a8669179378fa27ee4d0a509e985dfc0a7defb40"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.2.0",
|
"getrandom 0.2.0",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
@ -633,9 +625,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "const_fn"
|
name = "const_fn"
|
||||||
version = "0.4.3"
|
version = "0.4.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c478836e029dcef17fb47c89023448c64f781a046e0300e257ad8225ae59afab"
|
checksum = "cd51eab21ab4fd6a3bf889e2d0958c0a6e3a61ad04260325e919e652a2a62826"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "constant_time_eq"
|
name = "constant_time_eq"
|
||||||
|
@ -698,6 +690,12 @@ version = "0.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
|
checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpuid-bool"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "criterion"
|
name = "criterion"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
|
@ -709,7 +707,7 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"criterion-plot",
|
"criterion-plot",
|
||||||
"csv",
|
"csv",
|
||||||
"itertools 0.9.0",
|
"itertools",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"oorandom",
|
"oorandom",
|
||||||
|
@ -731,7 +729,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e022feadec601fba1649cfa83586381a4ad31c6bf3a9ab7d408118b05dd9889d"
|
checksum = "e022feadec601fba1649cfa83586381a4ad31c6bf3a9ab7d408118b05dd9889d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cast",
|
"cast",
|
||||||
"itertools 0.9.0",
|
"itertools",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1004,20 +1002,20 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dirs"
|
name = "dirs-next"
|
||||||
version = "2.0.2"
|
version = "2.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
|
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 0.1.10",
|
"cfg-if 1.0.0",
|
||||||
"dirs-sys",
|
"dirs-sys-next",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dirs-sys"
|
name = "dirs-sys-next"
|
||||||
version = "0.3.5"
|
version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a"
|
checksum = "99de365f605554ae33f115102a02057d4fc18b01f3284d6870be0938743cfe7d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"redox_users",
|
"redox_users",
|
||||||
|
@ -1093,9 +1091,9 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fancy-regex"
|
name = "fancy-regex"
|
||||||
version = "0.3.5"
|
version = "0.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ae91abf6555234338687bb47913978d275539235fcb77ba9863b779090b42b14"
|
checksum = "36996e5f56f32ca51a937f325094fa450b32df871af1a89be331b7145b931bfc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bit-set",
|
"bit-set",
|
||||||
"regex",
|
"regex",
|
||||||
|
@ -1129,7 +1127,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fernet"
|
name = "fernet"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
source = "git+https://github.com/mozilla-services/fernet-rs.git#401fde478c63e868f126ff7c92abad1f96107ca4"
|
source = "git+https://github.com/mozilla-services/fernet-rs.git#ec7f9091e0761c0dfe92ad77321ca161b929dbe4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.12.3",
|
"base64 0.12.3",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
|
@ -1235,16 +1233,16 @@ checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-lite"
|
name = "futures-lite"
|
||||||
version = "1.11.2"
|
version = "1.11.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5e6c079abfac3ab269e2927ec048dabc89d009ebfdda6b8ee86624f30c689658"
|
checksum = "b4481d0cd0de1d204a4fa55e7d45f07b1d958abcb06714b3446438e2eff695fb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
"memchr",
|
"memchr",
|
||||||
"parking",
|
"parking",
|
||||||
"pin-project-lite 0.1.11",
|
"pin-project-lite 0.2.0",
|
||||||
"waker-fn",
|
"waker-fn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1402,9 +1400,9 @@ checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.3.1"
|
version = "0.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
|
checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
@ -1450,9 +1448,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "0.2.1"
|
version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9"
|
checksum = "84129d298a6d57d246960ff8eb831ca4af3f96d29e2e28848dae275408658e26"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"fnv",
|
"fnv",
|
||||||
|
@ -1482,9 +1480,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http-types"
|
name = "http-types"
|
||||||
version = "2.8.0"
|
version = "2.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6f316f6a06306570e899238d3b85375f350cfceda60ec47807c4164d6e169e58"
|
checksum = "f2ab8d0085fb82859c9adf050bd53992297ecdd03a665a230dfa50c8c964bf3d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-channel",
|
"async-channel",
|
||||||
|
@ -1520,7 +1518,7 @@ version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
|
checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quick-error",
|
"quick-error 1.2.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1591,9 +1589,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.6.0"
|
version = "1.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2"
|
checksum = "4fb1fa934250de4de8aef298d81c729a7d33d8c239daa3a7575e6b92bfc7313b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"hashbrown 0.9.1",
|
"hashbrown 0.9.1",
|
||||||
|
@ -1629,15 +1627,6 @@ version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135"
|
checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itertools"
|
|
||||||
version = "0.8.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484"
|
|
||||||
dependencies = [
|
|
||||||
"either",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
@ -1848,9 +1837,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.80"
|
version = "0.2.81"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
|
checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libnss"
|
name = "libnss"
|
||||||
|
@ -1979,9 +1968,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "0.6.22"
|
version = "0.6.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430"
|
checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 0.1.10",
|
"cfg-if 0.1.10",
|
||||||
"fuchsia-zircon",
|
"fuchsia-zircon",
|
||||||
|
@ -2071,9 +2060,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "net2"
|
name = "net2"
|
||||||
version = "0.2.36"
|
version = "0.2.37"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d7cf75f38f16cb05ea017784dc6dbfd354f76c223dba37701734c4f5a9337d02"
|
checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 0.1.10",
|
"cfg-if 0.1.10",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -2229,12 +2218,12 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl"
|
name = "openssl"
|
||||||
version = "0.10.30"
|
version = "0.10.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8d575eff3665419f9b83678ff2815858ad9d11567e082f5ac1814baba4e2bcb4"
|
checksum = "038d43985d1ddca7a9900630d8cd031b56e4794eecc2e9ea39dd17aa04399a70"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"cfg-if 0.1.10",
|
"cfg-if 1.0.0",
|
||||||
"foreign-types",
|
"foreign-types",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -2249,9 +2238,9 @@ checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-sys"
|
name = "openssl-sys"
|
||||||
version = "0.9.58"
|
version = "0.9.60"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de"
|
checksum = "921fc71883267538946025deffb622905ecad223c28efbfdef9bb59a0175f3e6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"cc",
|
"cc",
|
||||||
|
@ -2289,12 +2278,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot_core"
|
name = "parking_lot_core"
|
||||||
version = "0.8.0"
|
version = "0.8.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b"
|
checksum = "9ccb628cad4f84851442432c60ad8e1f607e29752d0bf072cbd0baf28aa34272"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 0.1.10",
|
"cfg-if 1.0.0",
|
||||||
"cloudabi",
|
|
||||||
"instant",
|
"instant",
|
||||||
"libc",
|
"libc",
|
||||||
"redox_syscall",
|
"redox_syscall",
|
||||||
|
@ -2418,11 +2406,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "polyval"
|
name = "polyval"
|
||||||
version = "0.4.2"
|
version = "0.4.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b3fd900a291ceb8b99799cc8cd3d1d3403a51721e015bc533528b2ceafcc443c"
|
checksum = "b4fd92d8e0c06d08525d2e2643cc2b5c80c69ae8eb12c18272d501cd7079ccc0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cpuid-bool 0.2.0",
|
||||||
"universal-hash",
|
"universal-hash",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2497,10 +2485,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quick-error"
|
||||||
version = "1.0.7"
|
version = "2.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
|
checksum = "3ac73b1112776fc109b2e61909bc46c7e1bf0d7f690ffb1676553acce16d5cda"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
@ -2606,7 +2600,6 @@ checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.1.15",
|
"getrandom 0.1.15",
|
||||||
"redox_syscall",
|
"redox_syscall",
|
||||||
"rust-argon2",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2647,9 +2640,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.10.9"
|
version = "0.10.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fb15d6255c792356a0f578d8a645c677904dc02e862bebe2ecc18e0c01b9a0ce"
|
checksum = "0718f81a8e14c4dbb3b34cf23dc6aaf9ab8a0dfec160c534b3dbca1aaa21f47c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.13.0",
|
"base64 0.13.0",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
@ -2680,16 +2673,15 @@ dependencies = [
|
||||||
"url",
|
"url",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
"wasm-bindgen-test",
|
|
||||||
"web-sys",
|
"web-sys",
|
||||||
"winreg",
|
"winreg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ring"
|
name = "ring"
|
||||||
version = "0.16.18"
|
version = "0.16.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "70017ed5c555d79ee3538fc63ca09c70ad8f317dcadc1adc2c496b60c22bb24f"
|
checksum = "024a1e66fea74c66c66624ee5622a7ff0e4b73a13b4f5c326ddb50c708944226"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -2738,18 +2730,6 @@ dependencies = [
|
||||||
"time 0.1.44",
|
"time 0.1.44",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rust-argon2"
|
|
||||||
version = "0.8.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb"
|
|
||||||
dependencies = [
|
|
||||||
"base64 0.13.0",
|
|
||||||
"blake2b_simd",
|
|
||||||
"constant_time_eq",
|
|
||||||
"crossbeam-utils 0.8.1",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc_version"
|
name = "rustc_version"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
|
@ -2761,11 +2741,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.18.1"
|
version = "0.19.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d1126dcf58e93cee7d098dbda643b5f92ed724f1f6a63007c1116eed6700c81"
|
checksum = "064fd21ff87c6e87ed4506e68beb42459caa4a0e2eb144932e6776768556980b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.12.3",
|
"base64 0.13.0",
|
||||||
"log",
|
"log",
|
||||||
"ring",
|
"ring",
|
||||||
"sct",
|
"sct",
|
||||||
|
@ -2806,12 +2786,6 @@ dependencies = [
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "scoped-tls"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
@ -2868,9 +2842,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.117"
|
version = "1.0.118"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
|
checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
@ -2896,9 +2870,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.117"
|
version = "1.0.118"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
|
checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -2907,9 +2881,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.59"
|
version = "1.0.60"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95"
|
checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
|
@ -2918,9 +2892,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_qs"
|
name = "serde_qs"
|
||||||
version = "0.7.0"
|
version = "0.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9408a61dabe404c76cec504ec510f7d92f41dc0a9362a0db8ab73d141cfbf93f"
|
checksum = "5af82de3c6549b001bec34961ff2d6a54339a87bab37ce901b693401f27de6cb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
|
@ -2966,18 +2940,28 @@ checksum = "6e7aab86fe2149bad8c507606bdb3f4ef5e7b2380eb92350f56122cca72a42a8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block-buffer 0.9.0",
|
"block-buffer 0.9.0",
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"cpuid-bool",
|
"cpuid-bool 0.1.2",
|
||||||
"digest 0.9.0",
|
"digest 0.9.0",
|
||||||
"opaque-debug 0.3.0",
|
"opaque-debug 0.3.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shellexpand"
|
name = "shellexpand"
|
||||||
version = "2.0.0"
|
version = "2.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a2b22262a9aaf9464d356f656fea420634f78c881c5eebd5ef5e66d8b9bc603"
|
checksum = "83bdb7831b2d85ddf4a7b148aa19d0587eddbe8671a436b7bd1182eaad0f2829"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dirs",
|
"dirs-next",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook"
|
||||||
|
version = "0.1.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "604508c1418b99dfe1925ca9224829bb2a8a9a04dda655cc01fcad46f4ab05ed"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"signal-hook-registry",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3006,22 +2990,21 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.5.0"
|
version = "1.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7acad6f34eb9e8a259d3283d1e8c1d34d7415943d4895f65cc73813c7396fc85"
|
checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.3.17"
|
version = "0.3.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2c29947abdee2a218277abeca306f25789c938e500ea5a9d4b12a5a504466902"
|
checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"libc",
|
"libc",
|
||||||
"redox_syscall",
|
|
||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3138,15 +3121,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "subtle"
|
name = "subtle"
|
||||||
version = "2.3.0"
|
version = "2.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "343f3f510c2915908f155e94f17220b19ccfacf2a64a2a5d8004f2c3e311e7fd"
|
checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.53"
|
version = "1.0.55"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8833e20724c24de12bbaba5ad230ea61c3eafb05b881c7c9d3cfe8638b187e68"
|
checksum = "a571a711dddd09019ccc628e1b17fe87c59b09d513c06c026877aa708334f37a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -3251,9 +3234,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tide-rustls"
|
name = "tide-rustls"
|
||||||
version = "0.1.4"
|
version = "0.1.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b34fba7cb60c3c465c82ff5d215f341774f8f11b0e45691a7134af4238e395a"
|
checksum = "8b2faeed43463ab96a5362256554787c10752f1173c9ffaf7b553842ef12b6c5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-dup",
|
"async-dup",
|
||||||
"async-h1",
|
"async-h1",
|
||||||
|
@ -3349,9 +3332,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "0.2.23"
|
version = "0.2.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a6d7ad61edd59bfcc7e80dababf0f4aed2e6d5e0ba1659356ae889752dfc12ff"
|
checksum = "099837d3464c16a808060bb3f02263b412f6fafcb5d01c533d309985fbeebe48"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"fnv",
|
"fnv",
|
||||||
|
@ -3418,9 +3401,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.5.7"
|
version = "0.5.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645"
|
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
@ -3570,9 +3553,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcpkg"
|
name = "vcpkg"
|
||||||
version = "0.2.10"
|
version = "0.2.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c"
|
checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vec-arena"
|
name = "vec-arena"
|
||||||
|
@ -3699,30 +3682,6 @@ version = "0.2.69"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7e7811dd7f9398f14cc76efd356f98f03aa30419dea46aa810d71e819fc97158"
|
checksum = "7e7811dd7f9398f14cc76efd356f98f03aa30419dea46aa810d71e819fc97158"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-test"
|
|
||||||
version = "0.3.19"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0355fa0c1f9b792a09b6dcb6a8be24d51e71e6d74972f9eb4a44c4c004d24a25"
|
|
||||||
dependencies = [
|
|
||||||
"console_error_panic_hook",
|
|
||||||
"js-sys",
|
|
||||||
"scoped-tls",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"wasm-bindgen-futures",
|
|
||||||
"wasm-bindgen-test-macro",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-test-macro"
|
|
||||||
version = "0.3.19"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "27e07b46b98024c2ba2f9e83a10c2ef0515f057f2da299c1762a2017de80438b"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.46"
|
version = "0.3.46"
|
||||||
|
@ -3770,9 +3729,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webpki"
|
name = "webpki"
|
||||||
version = "0.21.3"
|
version = "0.21.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ab146130f5f790d45f82aeeb09e55a256573373ec64409fc19a6fb82fb1032ae"
|
checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ring",
|
"ring",
|
||||||
"untrusted",
|
"untrusted",
|
||||||
|
@ -3780,9 +3739,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webpki-roots"
|
name = "webpki-roots"
|
||||||
version = "0.20.0"
|
version = "0.21.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0f20dea7535251981a9670857150d571846545088359b28e4951d350bdaf179f"
|
checksum = "82015b7e0b8bad8185994674a13a93306bea76cf5a16c5a181382fd3a5ec2376"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"webpki",
|
"webpki",
|
||||||
]
|
]
|
||||||
|
@ -3860,9 +3819,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zeroize"
|
name = "zeroize"
|
||||||
version = "1.1.1"
|
version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "05f33972566adbd2d3588b0491eb94b98b43695c4ef897903470ede4f3f5a28a"
|
checksum = "81a974bcdd357f0dca4d41677db03436324d45a4c9ed2d0b873a5a360ce41c36"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zeroize_derive",
|
"zeroize_derive",
|
||||||
]
|
]
|
||||||
|
@ -3881,16 +3840,16 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zxcvbn"
|
name = "zxcvbn"
|
||||||
version = "2.0.1"
|
version = "2.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a7b69cd8a6484379ef04457ba1c00aaadad166c693b1b6a625b01bcc694b212b"
|
checksum = "a5f9db3a05b2ee81dcda4602487314ab654eca316f517be2e2e64175658f0dd0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"derive_builder",
|
"derive_builder",
|
||||||
"fancy-regex",
|
"fancy-regex",
|
||||||
"itertools 0.8.2",
|
"itertools",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"quick-error",
|
"quick-error 2.0.0",
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
|
|
|
@ -47,8 +47,8 @@ New Design (late 2020 - future)
|
||||||
===============================
|
===============================
|
||||||
|
|
||||||
A clearer configuration of credentials and how they function for the account is needed. This means
|
A clearer configuration of credentials and how they function for the account is needed. This means
|
||||||
changing how the current credential memory representation works. The database format does *not* need
|
changing how the current credential memory representation works. The database format does need to
|
||||||
to change, but *may* be extended.
|
change.
|
||||||
|
|
||||||
Currently Credentials can have *any* combination of factors.
|
Currently Credentials can have *any* combination of factors.
|
||||||
|
|
||||||
|
@ -75,5 +75,125 @@ This will simplify the state machines in authsession, as well as allowing better
|
||||||
UI decisions in clients for how we want to interact with possible credentials
|
UI decisions in clients for how we want to interact with possible credentials
|
||||||
on the client system.
|
on the client system.
|
||||||
|
|
||||||
|
Credentials In Memory
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
To support this the representation of credentials in memory must change. The current struct (2020-12)
|
||||||
|
is:
|
||||||
|
|
||||||
|
pub struct Credential {
|
||||||
|
pub(crate) password: Option<Password>,
|
||||||
|
pub(crate) webauthn: Option<Map<String, WebauthnCredential>>,
|
||||||
|
pub(crate) totp: Option<TOTP>,
|
||||||
|
pub(crate) claims: Vec<String>,
|
||||||
|
pub(crate) uuid: Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
There are numerous issues with this design. It does not express clearly the combination
|
||||||
|
of credentials that are valid in the credential, nor their capabilities and usage. A situation
|
||||||
|
such as `Password && (TOTP || Webauthn no verification)` OR `Password && Webauthn no verification`
|
||||||
|
are now ambiguous with this representation. It also does not clearly allow us to see where is the
|
||||||
|
correct entry/starting point for the authentication session.
|
||||||
|
|
||||||
|
An improved design will have Credential become an enum representing the valid authentication methods
|
||||||
|
|
||||||
|
pub enum Credential {
|
||||||
|
Anonymous,
|
||||||
|
Password(),
|
||||||
|
GeneratedPassword(),
|
||||||
|
PasswordMFA(),
|
||||||
|
PasswordWebauthn(),
|
||||||
|
Webauthn(),
|
||||||
|
WebauthnVerified(),
|
||||||
|
PasswordWebauthnVerified(),
|
||||||
|
}
|
||||||
|
|
||||||
|
This allows a clearer set of logic flows in the credential and it's handling, as well as defined
|
||||||
|
transforms between credential states and type level guarantees of the consistency of the credential.
|
||||||
|
|
||||||
|
Database Credentials
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Database Credentials are currently stored as:
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct DbCredV1 {
|
||||||
|
pub password: Option<DbPasswordV1>,
|
||||||
|
pub webauthn: Option<Vec<DbWebauthnV1>>,
|
||||||
|
pub totp: Option<DbTotpV1>,
|
||||||
|
pub claims: Vec<String>,
|
||||||
|
pub uuid: Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
This will be extended with an enum to represent the correct type/policy to deserialise into:
|
||||||
|
|
||||||
|
pub struct DbCredV1 {
|
||||||
|
pub type_: DbCredTypeV1,
|
||||||
|
}
|
||||||
|
|
||||||
|
An in place upgrade will be required to add this type to all existing credentials in the
|
||||||
|
database.
|
||||||
|
|
||||||
|
Protocol
|
||||||
|
--------
|
||||||
|
|
||||||
|
The current design of the step/response is as follows.
|
||||||
|
|
||||||
|
pub enum AuthStep {
|
||||||
|
Init(String),
|
||||||
|
Creds(Vec<AuthCredential>),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum AuthState {
|
||||||
|
Success(String),
|
||||||
|
Denied(String),
|
||||||
|
Continue(Vec<AuthAllowed>),
|
||||||
|
}
|
||||||
|
|
||||||
|
This will be extended to include the selection criteria to choose which method to use and be presented
|
||||||
|
with. Additionally, only one AuthCredential can be presented by the server at a time, and the server
|
||||||
|
may respond with many choices for the next step. This allows the server to propose TOTP *OR* Webauthn
|
||||||
|
but the client must choose which (It can not supply both, creating an AND situation or ambiguity around
|
||||||
|
the correct way to handle these).
|
||||||
|
|
||||||
|
pub enum AuthMech {
|
||||||
|
Anonymous,
|
||||||
|
Password,
|
||||||
|
// This covers PasswordWebauthn as well.
|
||||||
|
PasswordMFA,
|
||||||
|
Webauthn,
|
||||||
|
WebauthnVerified,
|
||||||
|
PasswordWebauthnVerified,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum AuthStep {
|
||||||
|
Init(String), // server responds with AuthState::Choose|Denied
|
||||||
|
Begin(AuthMech), // server responds with AuthState::Continue|Success|Denied
|
||||||
|
Cred(AuthCredential), // server response with AuthState::Continue|Success|Denied
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum AuthState {
|
||||||
|
Choose(Vec<AuthMech>),
|
||||||
|
Continue(Vec<AuthAllowed>),
|
||||||
|
Success(String),
|
||||||
|
Denied(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
A key reason to have this "Choose" step is related to an issue in the design and construction of
|
||||||
|
Webauthn Challenges. For more details see: https://fy.blackhats.net.au/blog/html/2020/11/21/webauthn_userverificationpolicy_curiosities.html
|
||||||
|
|
||||||
|
AuthSession
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Once these other changes are made, AuthSession will need to be simplified but it's core state machines
|
||||||
|
will remain mostly unchanged. This set of changes will likely result in the AuthSession being much
|
||||||
|
clearer due to the enforcement of credential presentation order instead of the current design that
|
||||||
|
may allow "all in one" submissions.
|
||||||
|
|
||||||
|
Other Benefits
|
||||||
|
==============
|
||||||
|
|
||||||
|
* During an MFA authentication, the Password if incorrect can be re-prompted for a number of times subsequent to the TOTP/Webauthn having been found valid, improving user experience.
|
||||||
|
* Allows Webauthn Verified credentials to be used with clearer expression of the claims of the device associated to the credential.
|
||||||
|
* Policy will be simpler to enforce on credentials due to them more clearly stating their design and layouts.
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ use crate::{ClientError, KanidmClientBuilder, APPLICATION_JSON, KOPID};
|
||||||
use reqwest::header::CONTENT_TYPE;
|
use reqwest::header::CONTENT_TYPE;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use std::collections::BTreeSet as Set;
|
||||||
|
|
||||||
use kanidm_proto::v1::*;
|
use kanidm_proto::v1::*;
|
||||||
|
|
||||||
|
@ -189,13 +190,39 @@ impl KanidmAsyncClient {
|
||||||
.map_err(|e| ClientError::JSONDecode(e, opid))
|
.map_err(|e| ClientError::JSONDecode(e, opid))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn auth_step_init(&self, ident: &str) -> Result<AuthState, ClientError> {
|
pub async fn auth_step_init(&self, ident: &str) -> Result<Set<AuthMech>, ClientError> {
|
||||||
let auth_init = AuthRequest {
|
let auth_init = AuthRequest {
|
||||||
step: AuthStep::Init(ident.to_string()),
|
step: AuthStep::Init(ident.to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_init).await;
|
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_init).await;
|
||||||
r.map(|v| v.state)
|
r.map(|v| {
|
||||||
|
debug!("Authentication Session ID -> {:?}", v.sessionid);
|
||||||
|
v.state
|
||||||
|
})
|
||||||
|
.and_then(|state| match state {
|
||||||
|
AuthState::Choose(mechs) => Ok(mechs),
|
||||||
|
_ => Err(ClientError::AuthenticationFailed),
|
||||||
|
})
|
||||||
|
.map(|mechs| mechs.into_iter().collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn auth_step_begin(&self, mech: AuthMech) -> Result<Vec<AuthAllowed>, ClientError> {
|
||||||
|
let auth_begin = AuthRequest {
|
||||||
|
step: AuthStep::Begin(mech),
|
||||||
|
};
|
||||||
|
|
||||||
|
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_begin).await;
|
||||||
|
r.map(|v| {
|
||||||
|
debug!("Authentication Session ID -> {:?}", v.sessionid);
|
||||||
|
v.state
|
||||||
|
})
|
||||||
|
.and_then(|state| match state {
|
||||||
|
AuthState::Continue(allowed) => Ok(allowed),
|
||||||
|
_ => Err(ClientError::AuthenticationFailed),
|
||||||
|
})
|
||||||
|
// For converting to a Set
|
||||||
|
// .map(|allowed| allowed.into_iter().collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn auth_simple_password(
|
pub async fn auth_simple_password(
|
||||||
|
@ -203,13 +230,23 @@ impl KanidmAsyncClient {
|
||||||
ident: &str,
|
ident: &str,
|
||||||
password: &str,
|
password: &str,
|
||||||
) -> Result<(), ClientError> {
|
) -> Result<(), ClientError> {
|
||||||
let _state = match self.auth_step_init(ident).await {
|
let mechs = match self.auth_step_init(ident).await {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !mechs.contains(&AuthMech::Password) {
|
||||||
|
debug!("Password mech not presented");
|
||||||
|
return Err(ClientError::AuthenticationFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
let _state = match self.auth_step_begin(AuthMech::Password).await {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => return Err(e),
|
Err(e) => return Err(e),
|
||||||
};
|
};
|
||||||
|
|
||||||
let auth_req = AuthRequest {
|
let auth_req = AuthRequest {
|
||||||
step: AuthStep::Creds(vec![AuthCredential::Password(password.to_string())]),
|
step: AuthStep::Cred(AuthCredential::Password(password.to_string())),
|
||||||
};
|
};
|
||||||
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_req).await;
|
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_req).await;
|
||||||
|
|
||||||
|
@ -225,15 +262,23 @@ impl KanidmAsyncClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn auth_anonymous(&mut self) -> Result<(), ClientError> {
|
pub async fn auth_anonymous(&mut self) -> Result<(), ClientError> {
|
||||||
// TODO #251: Check state for auth continue contains anonymous.
|
let mechs = match self.auth_step_init("anonymous").await {
|
||||||
// #251 will remove the need for this check.
|
Ok(s) => s,
|
||||||
let _state = match self.auth_step_init("anonymous").await {
|
Err(e) => return Err(e),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !mechs.contains(&AuthMech::Anonymous) {
|
||||||
|
debug!("Anonymous mech not presented");
|
||||||
|
return Err(ClientError::AuthenticationFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
let _state = match self.auth_step_begin(AuthMech::Anonymous).await {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => return Err(e),
|
Err(e) => return Err(e),
|
||||||
};
|
};
|
||||||
|
|
||||||
let auth_anon = AuthRequest {
|
let auth_anon = AuthRequest {
|
||||||
step: AuthStep::Creds(vec![AuthCredential::Anonymous]),
|
step: AuthStep::Cred(AuthCredential::Anonymous),
|
||||||
};
|
};
|
||||||
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_anon).await;
|
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_anon).await;
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ use serde::Serialize;
|
||||||
use serde_derive::Deserialize;
|
use serde_derive::Deserialize;
|
||||||
use serde_json::error::Error as SerdeJsonError;
|
use serde_json::error::Error as SerdeJsonError;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use std::collections::BTreeSet as Set;
|
||||||
use std::fs::{metadata, File, Metadata};
|
use std::fs::{metadata, File, Metadata};
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::os::unix::fs::MetadataExt;
|
use std::os::unix::fs::MetadataExt;
|
||||||
|
@ -32,11 +33,11 @@ use webauthn_rs::proto::{
|
||||||
// use users::{get_current_uid, get_effective_uid};
|
// use users::{get_current_uid, get_effective_uid};
|
||||||
|
|
||||||
use kanidm_proto::v1::{
|
use kanidm_proto::v1::{
|
||||||
AccountUnixExtend, AuthAllowed, AuthCredential, AuthRequest, AuthResponse, AuthState, AuthStep,
|
AccountUnixExtend, AuthAllowed, AuthCredential, AuthMech, AuthRequest, AuthResponse, AuthState,
|
||||||
CreateRequest, DeleteRequest, Entry, Filter, GroupUnixExtend, ModifyList, ModifyRequest,
|
AuthStep, CreateRequest, DeleteRequest, Entry, Filter, GroupUnixExtend, ModifyList,
|
||||||
OperationError, OperationResponse, RadiusAuthToken, SearchRequest, SearchResponse,
|
ModifyRequest, OperationError, OperationResponse, RadiusAuthToken, SearchRequest,
|
||||||
SetCredentialRequest, SetCredentialResponse, SingleStringRequest, TOTPSecret, UnixGroupToken,
|
SearchResponse, SetCredentialRequest, SetCredentialResponse, SingleStringRequest, TOTPSecret,
|
||||||
UnixUserToken, UserAuthToken, WhoamiResponse,
|
UnixGroupToken, UnixUserToken, UserAuthToken, WhoamiResponse,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod asynchronous;
|
pub mod asynchronous;
|
||||||
|
@ -555,14 +556,23 @@ impl KanidmClient {
|
||||||
|
|
||||||
// auth
|
// auth
|
||||||
pub fn auth_anonymous(&mut self) -> Result<(), ClientError> {
|
pub fn auth_anonymous(&mut self) -> Result<(), ClientError> {
|
||||||
// TODO #251: Check state for auth continue contains anonymous.
|
let mechs = match self.auth_step_init("anonymous") {
|
||||||
let _state = match self.auth_step_init("anonymous") {
|
Ok(s) => s,
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !mechs.contains(&AuthMech::Anonymous) {
|
||||||
|
debug!("Anonymous mech not presented");
|
||||||
|
return Err(ClientError::AuthenticationFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
let _state = match self.auth_step_begin(AuthMech::Anonymous) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => return Err(e),
|
Err(e) => return Err(e),
|
||||||
};
|
};
|
||||||
|
|
||||||
let auth_anon = AuthRequest {
|
let auth_anon = AuthRequest {
|
||||||
step: AuthStep::Creds(vec![AuthCredential::Anonymous]),
|
step: AuthStep::Cred(AuthCredential::Anonymous),
|
||||||
};
|
};
|
||||||
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_anon);
|
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_anon);
|
||||||
|
|
||||||
|
@ -579,13 +589,23 @@ impl KanidmClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn auth_simple_password(&mut self, ident: &str, password: &str) -> Result<(), ClientError> {
|
pub fn auth_simple_password(&mut self, ident: &str, password: &str) -> Result<(), ClientError> {
|
||||||
let _state = match self.auth_step_init(ident) {
|
let mechs = match self.auth_step_init(ident) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !mechs.contains(&AuthMech::Password) {
|
||||||
|
debug!("Password mech not presented");
|
||||||
|
return Err(ClientError::AuthenticationFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
let _state = match self.auth_step_begin(AuthMech::Password) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => return Err(e),
|
Err(e) => return Err(e),
|
||||||
};
|
};
|
||||||
|
|
||||||
let auth_req = AuthRequest {
|
let auth_req = AuthRequest {
|
||||||
step: AuthStep::Creds(vec![AuthCredential::Password(password.to_string())]),
|
step: AuthStep::Cred(AuthCredential::Password(password.to_string())),
|
||||||
};
|
};
|
||||||
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_req);
|
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_req);
|
||||||
|
|
||||||
|
@ -607,19 +627,52 @@ impl KanidmClient {
|
||||||
password: &str,
|
password: &str,
|
||||||
totp: u32,
|
totp: u32,
|
||||||
) -> Result<(), ClientError> {
|
) -> Result<(), ClientError> {
|
||||||
let _state = match self.auth_step_init(ident) {
|
let mechs = match self.auth_step_init(ident) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => return Err(e),
|
Err(e) => return Err(e),
|
||||||
};
|
};
|
||||||
|
|
||||||
let auth_req = AuthRequest {
|
if !mechs.contains(&AuthMech::PasswordMFA) {
|
||||||
step: AuthStep::Creds(vec![
|
debug!("PasswordMFA mech not presented");
|
||||||
AuthCredential::TOTP(totp),
|
return Err(ClientError::AuthenticationFailed);
|
||||||
AuthCredential::Password(password.to_string()),
|
}
|
||||||
]),
|
|
||||||
};
|
|
||||||
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_req);
|
|
||||||
|
|
||||||
|
let state = match self.auth_step_begin(AuthMech::PasswordMFA) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !state.contains(&AuthAllowed::TOTP) {
|
||||||
|
debug!("TOTP step not offered.");
|
||||||
|
return Err(ClientError::AuthenticationFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
let auth_req = AuthRequest {
|
||||||
|
step: AuthStep::Cred(AuthCredential::TOTP(totp)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_req);
|
||||||
|
let r = r?;
|
||||||
|
|
||||||
|
// Should need to continue.
|
||||||
|
match r.state {
|
||||||
|
AuthState::Continue(allowed) => {
|
||||||
|
if !allowed.contains(&AuthAllowed::Password) {
|
||||||
|
debug!("Password step not offered.");
|
||||||
|
return Err(ClientError::AuthenticationFailed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
debug!("Invalid AuthState presented.");
|
||||||
|
return Err(ClientError::AuthenticationFailed);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let auth_req = AuthRequest {
|
||||||
|
step: AuthStep::Cred(AuthCredential::Password(password.to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_req);
|
||||||
let r = r?;
|
let r = r?;
|
||||||
|
|
||||||
match r.state {
|
match r.state {
|
||||||
|
@ -636,27 +689,31 @@ impl KanidmClient {
|
||||||
&mut self,
|
&mut self,
|
||||||
ident: &str,
|
ident: &str,
|
||||||
) -> Result<RequestChallengeResponse, ClientError> {
|
) -> Result<RequestChallengeResponse, ClientError> {
|
||||||
let state = match self.auth_step_init(ident) {
|
let mechs = match self.auth_step_init(ident) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => return Err(e),
|
Err(e) => return Err(e),
|
||||||
};
|
};
|
||||||
|
|
||||||
match state {
|
if !mechs.contains(&AuthMech::Webauthn) {
|
||||||
AuthState::Continue(mut proc) => {
|
debug!("Webauthn mech not presented");
|
||||||
// get the webauthn chal out of the state.
|
return Err(ClientError::AuthenticationFailed);
|
||||||
let chal = proc.pop();
|
}
|
||||||
match chal {
|
|
||||||
|
let mut state = match self.auth_step_begin(AuthMech::Webauthn) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
};
|
||||||
|
|
||||||
|
// State is now a set of auth continues.
|
||||||
|
match state.pop() {
|
||||||
Some(AuthAllowed::Webauthn(r)) => Ok(r),
|
Some(AuthAllowed::Webauthn(r)) => Ok(r),
|
||||||
_ => Err(ClientError::AuthenticationFailed),
|
_ => Err(ClientError::AuthenticationFailed),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => Err(ClientError::AuthenticationFailed),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn auth_webauthn_complete(&mut self, pkc: PublicKeyCredential) -> Result<(), ClientError> {
|
pub fn auth_webauthn_complete(&mut self, pkc: PublicKeyCredential) -> Result<(), ClientError> {
|
||||||
let auth_req = AuthRequest {
|
let auth_req = AuthRequest {
|
||||||
step: AuthStep::Creds(vec![AuthCredential::Webauthn(pkc)]),
|
step: AuthStep::Cred(AuthCredential::Webauthn(pkc)),
|
||||||
};
|
};
|
||||||
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_req);
|
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_req);
|
||||||
|
|
||||||
|
@ -709,13 +766,39 @@ impl KanidmClient {
|
||||||
r.map(|_| true)
|
r.map(|_| true)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn auth_step_init(&self, ident: &str) -> Result<AuthState, ClientError> {
|
pub fn auth_step_init(&self, ident: &str) -> Result<Set<AuthMech>, ClientError> {
|
||||||
let auth_init = AuthRequest {
|
let auth_init = AuthRequest {
|
||||||
step: AuthStep::Init(ident.to_string()),
|
step: AuthStep::Init(ident.to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_init);
|
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_init);
|
||||||
r.map(|v| v.state)
|
r.map(|v| {
|
||||||
|
debug!("Authentication Session ID -> {:?}", v.sessionid);
|
||||||
|
v.state
|
||||||
|
})
|
||||||
|
.and_then(|state| match state {
|
||||||
|
AuthState::Choose(mechs) => Ok(mechs),
|
||||||
|
_ => Err(ClientError::AuthenticationFailed),
|
||||||
|
})
|
||||||
|
.map(|mechs| mechs.into_iter().collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn auth_step_begin(&self, mech: AuthMech) -> Result<Vec<AuthAllowed>, ClientError> {
|
||||||
|
let auth_begin = AuthRequest {
|
||||||
|
step: AuthStep::Begin(mech),
|
||||||
|
};
|
||||||
|
|
||||||
|
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_begin);
|
||||||
|
r.map(|v| {
|
||||||
|
debug!("Authentication Session ID -> {:?}", v.sessionid);
|
||||||
|
v.state
|
||||||
|
})
|
||||||
|
.and_then(|state| match state {
|
||||||
|
AuthState::Continue(allowed) => Ok(allowed),
|
||||||
|
_ => Err(ClientError::AuthenticationFailed),
|
||||||
|
})
|
||||||
|
// For converting to a Set
|
||||||
|
// .map(|allowed| allowed.into_iter().collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== GROUPS
|
// ===== GROUPS
|
||||||
|
|
|
@ -2,6 +2,7 @@ use std::collections::BTreeMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
// use zxcvbn::feedback;
|
// use zxcvbn::feedback;
|
||||||
|
use std::cmp::Ordering;
|
||||||
use webauthn_rs::proto::{
|
use webauthn_rs::proto::{
|
||||||
CreationChallengeResponse, PublicKeyCredential, RegisterPublicKeyCredential,
|
CreationChallengeResponse, PublicKeyCredential, RegisterPublicKeyCredential,
|
||||||
RequestChallengeResponse,
|
RequestChallengeResponse,
|
||||||
|
@ -426,6 +427,7 @@ impl ModifyRequest {
|
||||||
// On loginSuccess, we send a cookie, and that allows the token to be
|
// On loginSuccess, we send a cookie, and that allows the token to be
|
||||||
// generated. The cookie can be shared between servers.
|
// generated. The cookie can be shared between servers.
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum AuthCredential {
|
pub enum AuthCredential {
|
||||||
Anonymous,
|
Anonymous,
|
||||||
Password(String),
|
Password(String),
|
||||||
|
@ -444,17 +446,32 @@ impl fmt::Debug for AuthCredential {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Eq, PartialOrd, Ord)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum AuthMech {
|
||||||
|
Anonymous,
|
||||||
|
Password,
|
||||||
|
PasswordMFA,
|
||||||
|
Webauthn,
|
||||||
|
// WebauthnVerified,
|
||||||
|
// PasswordWebauthnVerified
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for AuthMech {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
std::mem::discriminant(self) == std::mem::discriminant(other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum AuthStep {
|
pub enum AuthStep {
|
||||||
// name
|
// name
|
||||||
Init(String),
|
Init(String),
|
||||||
/*
|
// We want to talk to you like this.
|
||||||
Step(
|
Begin(AuthMech),
|
||||||
Type(params ....)
|
// Step
|
||||||
),
|
Cred(AuthCredential),
|
||||||
*/
|
|
||||||
Creds(Vec<AuthCredential>),
|
|
||||||
// Should we have a "finalise" type to attempt to finish based on
|
// Should we have a "finalise" type to attempt to finish based on
|
||||||
// what we have given?
|
// what we have given?
|
||||||
}
|
}
|
||||||
|
@ -482,17 +499,47 @@ impl PartialEq for AuthAllowed {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Eq for AuthAllowed {}
|
||||||
|
|
||||||
|
impl Ord for AuthAllowed {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
if self.eq(other) {
|
||||||
|
Ordering::Equal
|
||||||
|
} else {
|
||||||
|
// Relies on the fact that match is executed in order!
|
||||||
|
match (self, other) {
|
||||||
|
(AuthAllowed::Anonymous, _) => Ordering::Less,
|
||||||
|
(_, AuthAllowed::Anonymous) => Ordering::Greater,
|
||||||
|
(AuthAllowed::Password, _) => Ordering::Less,
|
||||||
|
(_, AuthAllowed::Password) => Ordering::Greater,
|
||||||
|
(AuthAllowed::TOTP, _) => Ordering::Less,
|
||||||
|
(_, AuthAllowed::TOTP) => Ordering::Greater,
|
||||||
|
(AuthAllowed::Webauthn(_), _) => Ordering::Less,
|
||||||
|
// Unreachable
|
||||||
|
// (_, AuthAllowed::Webauthn(_)) => Ordering::Greater,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for AuthAllowed {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum AuthState {
|
pub enum AuthState {
|
||||||
// Everything is good, your bearer header has been issued and is within
|
// You need to select how you want to talk to me.
|
||||||
// the result.
|
Choose(Vec<AuthMech>),
|
||||||
// Success(UserAuthToken),
|
// Continue to auth, allowed mechanisms/challenges listed.
|
||||||
Success(String),
|
Continue(Vec<AuthAllowed>),
|
||||||
// Something was bad, your session is terminated and no cookie.
|
// Something was bad, your session is terminated and no cookie.
|
||||||
Denied(String),
|
Denied(String),
|
||||||
// Continue to auth, allowed mechanisms listed.
|
// Everything is good, your bearer header has been issued and is within
|
||||||
Continue(Vec<AuthAllowed>),
|
// the result.
|
||||||
|
Success(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
|
27
kanidm_rlm_python/README.md
Normal file
27
kanidm_rlm_python/README.md
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
Testing Process
|
||||||
|
===============
|
||||||
|
|
||||||
|
cd kanidmd
|
||||||
|
cargo run -- recover_account -c ./server.toml -n admin
|
||||||
|
cargo run -- server -c ./server.toml
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
cd kanidm_tools
|
||||||
|
cargo run -- login -D admin
|
||||||
|
cargo run -- account list -D admin
|
||||||
|
cargo run -- account create -D admin radius_service_account radius_service_account
|
||||||
|
cargo run -- group add_members -D admin idm_radius_servers radius_service_account
|
||||||
|
cargo run -- account credential set_password radius_service_account -D admin
|
||||||
|
cargo run -- account radius generate_secret admin -D admin
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
cd kanidm_rlm_python/
|
||||||
|
KANIDM_RLM_CONFIG=./test_data/config.ini python3 kanidmradius.py test
|
||||||
|
KANIDM_RLM_CONFIG=./test_data/config.ini python3 kanidmradius.py admin
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -16,9 +16,10 @@ else:
|
||||||
# Setup the config too
|
# Setup the config too
|
||||||
print(os.getcwd())
|
print(os.getcwd())
|
||||||
|
|
||||||
|
CONFIG_PATH = os.environ.get('KANIDM_RLM_CONFIG', '/data/config.ini')
|
||||||
|
|
||||||
CONFIG = configparser.ConfigParser()
|
CONFIG = configparser.ConfigParser()
|
||||||
CONFIG.read('/data/config.ini')
|
CONFIG.read(CONFIG_PATH)
|
||||||
# CONFIG.read('/tmp/config.ini')
|
|
||||||
|
|
||||||
GROUPS = [
|
GROUPS = [
|
||||||
{
|
{
|
||||||
|
@ -50,7 +51,19 @@ def _authenticate(s, acct, pw):
|
||||||
print(r.json())
|
print(r.json())
|
||||||
raise Exception("AuthInitFailed")
|
raise Exception("AuthInitFailed")
|
||||||
|
|
||||||
cred_auth = {"step": { "creds": [{"Password": pw}]}}
|
# {'sessionid': '00000000-5fe5-46e1-06b6-b830dd035a10', 'state': {'choose': ['password']}}
|
||||||
|
if 'password' not in r.json().get('state', {'choose': None}).get('choose', None):
|
||||||
|
print("invalid auth mech presented %s" % r.json())
|
||||||
|
raise Exception("AuthMechUnknown")
|
||||||
|
|
||||||
|
begin_auth = {"step": {"begin": "password"}}
|
||||||
|
|
||||||
|
r = s.post(AUTH_URL, json=begin_auth, verify=CA, timeout=TIMEOUT)
|
||||||
|
if r.status_code != 200:
|
||||||
|
print(r.json())
|
||||||
|
raise Exception("AuthBeginFailed")
|
||||||
|
|
||||||
|
cred_auth = {"step": { "cred": {"password": pw}}}
|
||||||
r = s.post(AUTH_URL, json=cred_auth, verify=CA, timeout=TIMEOUT)
|
r = s.post(AUTH_URL, json=cred_auth, verify=CA, timeout=TIMEOUT)
|
||||||
response = r.json()
|
response = r.json()
|
||||||
if r.status_code != 200:
|
if r.status_code != 200:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[kanidm_client]
|
[kanidm_client]
|
||||||
url = https://172.17.0.2:8080
|
url = https://localhost:8443
|
||||||
strict = false
|
strict = false
|
||||||
ca = /data/ca.crt
|
ca = /data/ca.crt
|
||||||
user = radius_service_account
|
user = radius_service_account
|
||||||
|
|
|
@ -39,10 +39,30 @@ pub struct DbWebauthnV1 {
|
||||||
pub v: bool,
|
pub v: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub enum DbCredTypeV1 {
|
||||||
|
Pw,
|
||||||
|
GPw,
|
||||||
|
PwMfa,
|
||||||
|
// PwWn,
|
||||||
|
Wn,
|
||||||
|
// WnVer,
|
||||||
|
// PwWnVer,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dbcred_type_default_pw() -> DbCredTypeV1 {
|
||||||
|
DbCredTypeV1::Pw
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct DbCredV1 {
|
pub struct DbCredV1 {
|
||||||
|
#[serde(default = "dbcred_type_default_pw")]
|
||||||
|
pub type_: DbCredTypeV1,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub password: Option<DbPasswordV1>,
|
pub password: Option<DbPasswordV1>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub webauthn: Option<Vec<DbWebauthnV1>>,
|
pub webauthn: Option<Vec<DbWebauthnV1>>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub totp: Option<DbTotpV1>,
|
pub totp: Option<DbTotpV1>,
|
||||||
pub claims: Vec<String>,
|
pub claims: Vec<String>,
|
||||||
pub uuid: Uuid,
|
pub uuid: Uuid,
|
||||||
|
|
|
@ -1009,6 +1009,30 @@ pub async fn auth(mut req: tide::Request<AppState>) -> tide::Result {
|
||||||
}
|
}
|
||||||
// Do some response/state management.
|
// Do some response/state management.
|
||||||
match state {
|
match state {
|
||||||
|
AuthState::Choose(allowed) => {
|
||||||
|
debug!("🧩 -> AuthState::Choose");
|
||||||
|
let msession = req.session_mut();
|
||||||
|
// Force a new cookie session.
|
||||||
|
// msession.regenerate();
|
||||||
|
// Ensure the auth-session-id is set
|
||||||
|
msession.remove("auth-session-id");
|
||||||
|
msession
|
||||||
|
.insert("auth-session-id", sessionid)
|
||||||
|
.map(|_| ProtoAuthState::Choose(allowed))
|
||||||
|
.map_err(|_| OperationError::InvalidSessionState)
|
||||||
|
}
|
||||||
|
AuthState::Continue(allowed) => {
|
||||||
|
debug!("🧩 -> AuthState::Continue");
|
||||||
|
let msession = req.session_mut();
|
||||||
|
// Force a new cookie session.
|
||||||
|
// msession.regenerate();
|
||||||
|
// Ensure the auth-session-id is set
|
||||||
|
msession.remove("auth-session-id");
|
||||||
|
msession
|
||||||
|
.insert("auth-session-id", sessionid)
|
||||||
|
.map(|_| ProtoAuthState::Continue(allowed))
|
||||||
|
.map_err(|_| OperationError::InvalidSessionState)
|
||||||
|
}
|
||||||
AuthState::Success(uat) => {
|
AuthState::Success(uat) => {
|
||||||
debug!("🧩 -> AuthState::Success");
|
debug!("🧩 -> AuthState::Success");
|
||||||
// Remove the auth-session-id
|
// Remove the auth-session-id
|
||||||
|
@ -1030,18 +1054,6 @@ pub async fn auth(mut req: tide::Request<AppState>) -> tide::Result {
|
||||||
msession.remove("auth-session-id");
|
msession.remove("auth-session-id");
|
||||||
Err(OperationError::AccessDenied)
|
Err(OperationError::AccessDenied)
|
||||||
}
|
}
|
||||||
AuthState::Continue(allowed) => {
|
|
||||||
debug!("🧩 -> AuthState::Continue");
|
|
||||||
let msession = req.session_mut();
|
|
||||||
// Force a new cookie session.
|
|
||||||
// msession.regenerate();
|
|
||||||
// Ensure the auth-session-id is set
|
|
||||||
msession.remove("auth-session-id");
|
|
||||||
msession
|
|
||||||
.insert("auth-session-id", sessionid)
|
|
||||||
.map(|_| ProtoAuthState::Continue(allowed))
|
|
||||||
.map_err(|_| OperationError::InvalidSessionState)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.map(|state| AuthResponse { state, sessionid })
|
.map(|state| AuthResponse { state, sessionid })
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::be::dbvalue::{DbCredV1, DbPasswordV1, DbWebauthnV1};
|
use crate::be::dbvalue::{DbCredTypeV1, DbCredV1, DbPasswordV1, DbWebauthnV1};
|
||||||
use hashbrown::HashMap as Map;
|
use hashbrown::HashMap as Map;
|
||||||
use kanidm_proto::v1::OperationError;
|
use kanidm_proto::v1::OperationError;
|
||||||
use openssl::hash::MessageDigest;
|
use openssl::hash::MessageDigest;
|
||||||
|
@ -225,15 +225,10 @@ impl Password {
|
||||||
/// B requires both the password and otp to be valid.
|
/// B requires both the password and otp to be valid.
|
||||||
///
|
///
|
||||||
/// In this way, each Credential provides it's own password requirements and policy, and requires
|
/// In this way, each Credential provides it's own password requirements and policy, and requires
|
||||||
/// some metadata to support this such as it's source and strength etc. Some of these details are
|
/// some metadata to support this such as it's source and strength etc.
|
||||||
/// to be resolved ...
|
|
||||||
pub struct Credential {
|
pub struct Credential {
|
||||||
// Source (machine, user, ....). Strength?
|
|
||||||
// policy: Policy,
|
// policy: Policy,
|
||||||
pub(crate) password: Option<Password>,
|
pub(crate) type_: CredentialType,
|
||||||
pub(crate) webauthn: Option<Map<String, WebauthnCredential>>,
|
|
||||||
// totp: Option<NonEmptyVec<TOTP>>
|
|
||||||
pub(crate) totp: Option<TOTP>,
|
|
||||||
pub(crate) claims: Vec<String>,
|
pub(crate) claims: Vec<String>,
|
||||||
// Uuid of Credential, used by auth session to lock this specific credential
|
// Uuid of Credential, used by auth session to lock this specific credential
|
||||||
// if required.
|
// if required.
|
||||||
|
@ -242,12 +237,25 @@ pub struct Credential {
|
||||||
// locked: bool
|
// locked: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum CredentialType {
|
||||||
|
// Anonymous,
|
||||||
|
Password(Password),
|
||||||
|
GeneratedPassword(Password),
|
||||||
|
Webauthn(Map<String, WebauthnCredential>),
|
||||||
|
PasswordMFA(Password, Option<TOTP>, Map<String, WebauthnCredential>),
|
||||||
|
// PasswordWebauthn(Password, Map<String, WebauthnCredential>),
|
||||||
|
// WebauthnVerified(Map<String, WebauthnCredential>),
|
||||||
|
// PasswordWebauthnVerified(Password, Map<String, WebauthnCredential>),
|
||||||
|
}
|
||||||
|
|
||||||
impl TryFrom<DbCredV1> for Credential {
|
impl TryFrom<DbCredV1> for Credential {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn try_from(value: DbCredV1) -> Result<Self, Self::Error> {
|
fn try_from(value: DbCredV1) -> Result<Self, Self::Error> {
|
||||||
// Work out what the policy is?
|
// Work out what the policy is?
|
||||||
let DbCredV1 {
|
let DbCredV1 {
|
||||||
|
type_,
|
||||||
password,
|
password,
|
||||||
webauthn,
|
webauthn,
|
||||||
totp,
|
totp,
|
||||||
|
@ -284,10 +292,21 @@ impl TryFrom<DbCredV1> for Credential {
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let type_ = match type_ {
|
||||||
|
DbCredTypeV1::Pw => v_password.map(CredentialType::Password),
|
||||||
|
DbCredTypeV1::GPw => v_password.map(CredentialType::GeneratedPassword),
|
||||||
|
// In the future this could use .zip
|
||||||
|
DbCredTypeV1::PwMfa => match (v_password, v_webauthn) {
|
||||||
|
(Some(pw), Some(wn)) => Some(CredentialType::PasswordMFA(pw, v_totp, wn)),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
DbCredTypeV1::Wn => v_webauthn.map(CredentialType::Webauthn),
|
||||||
|
}
|
||||||
|
.filter(|v| v.is_valid())
|
||||||
|
.ok_or(())?;
|
||||||
|
|
||||||
Ok(Credential {
|
Ok(Credential {
|
||||||
password: v_password,
|
type_,
|
||||||
webauthn: v_webauthn,
|
|
||||||
totp: v_totp,
|
|
||||||
claims,
|
claims,
|
||||||
uuid,
|
uuid,
|
||||||
})
|
})
|
||||||
|
@ -306,9 +325,7 @@ impl Credential {
|
||||||
let mut webauthn_map = Map::new();
|
let mut webauthn_map = Map::new();
|
||||||
webauthn_map.insert(label, cred);
|
webauthn_map.insert(label, cred);
|
||||||
Credential {
|
Credential {
|
||||||
password: None,
|
type_: CredentialType::Webauthn(webauthn_map),
|
||||||
webauthn: Some(webauthn_map),
|
|
||||||
totp: None,
|
|
||||||
claims: Vec::new(),
|
claims: Vec::new(),
|
||||||
uuid: Uuid::new_v4(),
|
uuid: Uuid::new_v4(),
|
||||||
}
|
}
|
||||||
|
@ -319,13 +336,7 @@ impl Credential {
|
||||||
policy: &CryptoPolicy,
|
policy: &CryptoPolicy,
|
||||||
cleartext: &str,
|
cleartext: &str,
|
||||||
) -> Result<Self, OperationError> {
|
) -> Result<Self, OperationError> {
|
||||||
Password::new(policy, cleartext).map(|pw| Credential {
|
Password::new(policy, cleartext).map(|pw| self.update_password(pw))
|
||||||
password: Some(pw),
|
|
||||||
webauthn: self.webauthn.clone(),
|
|
||||||
totp: self.totp.clone(),
|
|
||||||
claims: self.claims.clone(),
|
|
||||||
uuid: self.uuid,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn append_webauthn(
|
pub fn append_webauthn(
|
||||||
|
@ -333,30 +344,37 @@ impl Credential {
|
||||||
label: String,
|
label: String,
|
||||||
cred: WebauthnCredential,
|
cred: WebauthnCredential,
|
||||||
) -> Result<Self, OperationError> {
|
) -> Result<Self, OperationError> {
|
||||||
let webauthn_map = match &self.webauthn {
|
let type_ = match &self.type_ {
|
||||||
Some(map) => {
|
CredentialType::Password(pw) | CredentialType::GeneratedPassword(pw) => {
|
||||||
|
let mut wan = Map::new();
|
||||||
|
wan.insert(label, cred);
|
||||||
|
CredentialType::PasswordMFA(pw.clone(), None, wan)
|
||||||
|
}
|
||||||
|
CredentialType::PasswordMFA(pw, totp, map) => {
|
||||||
let mut nmap = map.clone();
|
let mut nmap = map.clone();
|
||||||
match nmap.insert(label.clone(), cred) {
|
if let Some(_) = nmap.insert(label.clone(), cred) {
|
||||||
Some(_) => {
|
|
||||||
return Err(OperationError::InvalidAttribute(format!(
|
return Err(OperationError::InvalidAttribute(format!(
|
||||||
"Webauthn label '{:?}' already exists",
|
"Webauthn label '{:?}' already exists",
|
||||||
label
|
label
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
None => nmap,
|
CredentialType::PasswordMFA(pw.clone(), totp.clone(), nmap)
|
||||||
}
|
}
|
||||||
|
CredentialType::Webauthn(map) => {
|
||||||
|
let mut nmap = map.clone();
|
||||||
|
if let Some(_) = nmap.insert(label.clone(), cred) {
|
||||||
|
return Err(OperationError::InvalidAttribute(format!(
|
||||||
|
"Webauthn label '{:?}' already exists",
|
||||||
|
label
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
None => {
|
CredentialType::Webauthn(nmap)
|
||||||
let mut map = Map::new();
|
|
||||||
map.insert(label, cred);
|
|
||||||
map
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check stuff
|
// Check stuff
|
||||||
Ok(Credential {
|
Ok(Credential {
|
||||||
password: self.password.clone(),
|
type_,
|
||||||
webauthn: Some(webauthn_map),
|
|
||||||
totp: self.totp.clone(),
|
|
||||||
claims: self.claims.clone(),
|
claims: self.claims.clone(),
|
||||||
uuid: self.uuid,
|
uuid: self.uuid,
|
||||||
})
|
})
|
||||||
|
@ -367,48 +385,105 @@ impl Credential {
|
||||||
cid: &CredentialID,
|
cid: &CredentialID,
|
||||||
counter: Counter,
|
counter: Counter,
|
||||||
) -> Result<Option<Self>, OperationError> {
|
) -> Result<Option<Self>, OperationError> {
|
||||||
let opt_label = self.webauthn.as_ref().and_then(|m| {
|
let nmap = match &self.type_ {
|
||||||
m.iter().fold(None, |acc, (k, v)| {
|
CredentialType::Password(_pw) | CredentialType::GeneratedPassword(_pw) => {
|
||||||
|
// No action required
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
CredentialType::PasswordMFA(_, _, map) | CredentialType::Webauthn(map) => map
|
||||||
|
.iter()
|
||||||
|
.fold(None, |acc, (k, v)| {
|
||||||
if acc.is_none() && &v.cred_id == cid && v.counter < counter {
|
if acc.is_none() && &v.cred_id == cid && v.counter < counter {
|
||||||
Some(k)
|
Some(k)
|
||||||
} else {
|
} else {
|
||||||
acc
|
acc
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
.map(|label| {
|
||||||
|
let mut webauthn_map = map.clone();
|
||||||
if let Some(label) = opt_label {
|
|
||||||
let mut webauthn_map = self.webauthn.clone();
|
|
||||||
|
|
||||||
webauthn_map
|
webauthn_map
|
||||||
.as_mut()
|
.get_mut(label)
|
||||||
.and_then(|m| m.get_mut(label))
|
|
||||||
.map(|cred| cred.counter = counter);
|
.map(|cred| cred.counter = counter);
|
||||||
|
webauthn_map
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let map = match nmap {
|
||||||
|
Some(map) => map,
|
||||||
|
None => {
|
||||||
|
// No action needed.
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let type_ = match &self.type_ {
|
||||||
|
CredentialType::Password(_pw) | CredentialType::GeneratedPassword(_pw) => {
|
||||||
|
// Should not be possible!
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
CredentialType::Webauthn(_) => CredentialType::Webauthn(map),
|
||||||
|
CredentialType::PasswordMFA(pw, totp, _) => {
|
||||||
|
CredentialType::PasswordMFA(pw.clone(), totp.clone(), map)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Ok(Some(Credential {
|
Ok(Some(Credential {
|
||||||
password: self.password.clone(),
|
type_,
|
||||||
webauthn: webauthn_map,
|
|
||||||
totp: self.totp.clone(),
|
|
||||||
claims: self.claims.clone(),
|
claims: self.claims.clone(),
|
||||||
uuid: self.uuid,
|
uuid: self.uuid,
|
||||||
}))
|
}))
|
||||||
} else {
|
}
|
||||||
Ok(None)
|
|
||||||
|
pub fn webauthn_ref(&self) -> Result<&Map<String, WebauthnCredential>, OperationError> {
|
||||||
|
match &self.type_ {
|
||||||
|
CredentialType::Password(_) | CredentialType::GeneratedPassword(_) => Err(
|
||||||
|
OperationError::InvalidAccountState("non-webauthn cred type?".to_string()),
|
||||||
|
),
|
||||||
|
CredentialType::PasswordMFA(_, _, map) | CredentialType::Webauthn(map) => Ok(map),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn password_ref(&self) -> Result<&Password, OperationError> {
|
||||||
|
match &self.type_ {
|
||||||
|
CredentialType::Password(pw)
|
||||||
|
| CredentialType::GeneratedPassword(pw)
|
||||||
|
| CredentialType::PasswordMFA(pw, _, _) => Ok(pw),
|
||||||
|
CredentialType::Webauthn(_) => Err(OperationError::InvalidAccountState(
|
||||||
|
"non-password cred type?".to_string(),
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn verify_password(&self, cleartext: &str) -> bool {
|
pub fn verify_password(&self, cleartext: &str) -> Result<bool, OperationError> {
|
||||||
match &self.password {
|
self.password_ref().and_then(|pw| pw.verify(cleartext))
|
||||||
Some(pw) => pw.verify(cleartext).unwrap_or(false),
|
|
||||||
None => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_db_valuev1(&self) -> DbCredV1 {
|
pub fn to_db_valuev1(&self) -> DbCredV1 {
|
||||||
DbCredV1 {
|
let claims = self.claims.clone();
|
||||||
password: self.password.as_ref().map(|pw| pw.to_dbpasswordv1()),
|
let uuid = self.uuid;
|
||||||
webauthn: self.webauthn.as_ref().map(|map| {
|
match &self.type_ {
|
||||||
|
CredentialType::Password(pw) => DbCredV1 {
|
||||||
|
type_: DbCredTypeV1::Pw,
|
||||||
|
password: Some(pw.to_dbpasswordv1()),
|
||||||
|
webauthn: None,
|
||||||
|
totp: None,
|
||||||
|
claims,
|
||||||
|
uuid,
|
||||||
|
},
|
||||||
|
CredentialType::GeneratedPassword(pw) => DbCredV1 {
|
||||||
|
type_: DbCredTypeV1::GPw,
|
||||||
|
password: Some(pw.to_dbpasswordv1()),
|
||||||
|
webauthn: None,
|
||||||
|
totp: None,
|
||||||
|
claims,
|
||||||
|
uuid,
|
||||||
|
},
|
||||||
|
CredentialType::PasswordMFA(pw, totp, map) => DbCredV1 {
|
||||||
|
type_: DbCredTypeV1::PwMfa,
|
||||||
|
password: Some(pw.to_dbpasswordv1()),
|
||||||
|
webauthn: Some(
|
||||||
map.iter()
|
map.iter()
|
||||||
.map(|(k, v)| DbWebauthnV1 {
|
.map(|(k, v)| DbWebauthnV1 {
|
||||||
l: k.clone(),
|
l: k.clone(),
|
||||||
|
@ -417,19 +492,48 @@ impl Credential {
|
||||||
t: v.counter,
|
t: v.counter,
|
||||||
v: v.verified,
|
v: v.verified,
|
||||||
})
|
})
|
||||||
.collect()
|
.collect(),
|
||||||
}),
|
),
|
||||||
totp: self.totp.as_ref().map(|t| t.to_dbtotpv1()),
|
totp: totp.as_ref().map(|t| t.to_dbtotpv1()),
|
||||||
claims: self.claims.clone(),
|
claims,
|
||||||
uuid: self.uuid,
|
uuid,
|
||||||
|
},
|
||||||
|
CredentialType::Webauthn(map) => DbCredV1 {
|
||||||
|
type_: DbCredTypeV1::Wn,
|
||||||
|
password: None,
|
||||||
|
webauthn: Some(
|
||||||
|
map.iter()
|
||||||
|
.map(|(k, v)| DbWebauthnV1 {
|
||||||
|
l: k.clone(),
|
||||||
|
i: v.cred_id.clone(),
|
||||||
|
c: v.cred.clone(),
|
||||||
|
t: v.counter,
|
||||||
|
v: v.verified,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
totp: None,
|
||||||
|
claims,
|
||||||
|
uuid,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn update_password(&self, pw: Password) -> Self {
|
pub(crate) fn update_password(&self, pw: Password) -> Self {
|
||||||
|
let type_ = match &self.type_ {
|
||||||
|
CredentialType::Password(_) => CredentialType::Password(pw),
|
||||||
|
CredentialType::GeneratedPassword(_) => CredentialType::GeneratedPassword(pw),
|
||||||
|
CredentialType::PasswordMFA(_, totp, wan) => {
|
||||||
|
CredentialType::PasswordMFA(pw, totp.clone(), wan.clone())
|
||||||
|
}
|
||||||
|
CredentialType::Webauthn(wan) => {
|
||||||
|
// Or should this become PasswordWebauthn?
|
||||||
|
debug_assert!(false);
|
||||||
|
CredentialType::Webauthn(wan.clone())
|
||||||
|
}
|
||||||
|
};
|
||||||
Credential {
|
Credential {
|
||||||
password: Some(pw),
|
type_,
|
||||||
webauthn: self.webauthn.clone(),
|
|
||||||
totp: self.totp.clone(),
|
|
||||||
claims: self.claims.clone(),
|
claims: self.claims.clone(),
|
||||||
uuid: self.uuid,
|
uuid: self.uuid,
|
||||||
}
|
}
|
||||||
|
@ -437,10 +541,20 @@ impl Credential {
|
||||||
|
|
||||||
// We don't make totp accessible from outside the crate for now.
|
// We don't make totp accessible from outside the crate for now.
|
||||||
pub(crate) fn update_totp(&self, totp: TOTP) -> Self {
|
pub(crate) fn update_totp(&self, totp: TOTP) -> Self {
|
||||||
|
let type_ = match &self.type_ {
|
||||||
|
CredentialType::Password(pw) | CredentialType::GeneratedPassword(pw) => {
|
||||||
|
CredentialType::PasswordMFA(pw.clone(), Some(totp), Map::new())
|
||||||
|
}
|
||||||
|
CredentialType::PasswordMFA(pw, _, wan) => {
|
||||||
|
CredentialType::PasswordMFA(pw.clone(), Some(totp), wan.clone())
|
||||||
|
}
|
||||||
|
CredentialType::Webauthn(wan) => {
|
||||||
|
debug_assert!(false);
|
||||||
|
CredentialType::Webauthn(wan.clone())
|
||||||
|
}
|
||||||
|
};
|
||||||
Credential {
|
Credential {
|
||||||
password: self.password.clone(),
|
type_,
|
||||||
webauthn: self.webauthn.clone(),
|
|
||||||
totp: Some(totp),
|
|
||||||
claims: self.claims.clone(),
|
claims: self.claims.clone(),
|
||||||
uuid: self.uuid,
|
uuid: self.uuid,
|
||||||
}
|
}
|
||||||
|
@ -448,24 +562,27 @@ impl Credential {
|
||||||
|
|
||||||
pub(crate) fn new_from_password(pw: Password) -> Self {
|
pub(crate) fn new_from_password(pw: Password) -> Self {
|
||||||
Credential {
|
Credential {
|
||||||
password: Some(pw),
|
type_: CredentialType::Password(pw),
|
||||||
webauthn: None,
|
|
||||||
totp: None,
|
|
||||||
claims: Vec::new(),
|
claims: Vec::new(),
|
||||||
uuid: Uuid::new_v4(),
|
uuid: Uuid::new_v4(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn softlock_policy(&self) -> Option<CredSoftLockPolicy> {
|
pub(crate) fn softlock_policy(&self) -> Option<CredSoftLockPolicy> {
|
||||||
match (&self.webauthn, &self.totp, &self.password) {
|
match &self.type_ {
|
||||||
// Has any kind of Webauthn ....
|
CredentialType::Password(_pw) | CredentialType::GeneratedPassword(_pw) => {
|
||||||
(Some(_webauthn), _, _) => Some(CredSoftLockPolicy::Webauthn),
|
Some(CredSoftLockPolicy::Password)
|
||||||
// Has any kind of totp.
|
}
|
||||||
(None, Some(totp), _) => Some(CredSoftLockPolicy::TOTP(totp.step)),
|
CredentialType::PasswordMFA(_pw, totp, wan) => {
|
||||||
// No totp, pw
|
if let Some(r_totp) = totp {
|
||||||
(None, None, Some(_)) => Some(CredSoftLockPolicy::Password),
|
Some(CredSoftLockPolicy::TOTP(r_totp.step))
|
||||||
// Indeterminate
|
} else if wan.len() > 0 {
|
||||||
_ => None,
|
Some(CredSoftLockPolicy::Webauthn)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CredentialType::Webauthn(_wan) => Some(CredSoftLockPolicy::Webauthn),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -490,6 +607,18 @@ impl Credential {
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CredentialType {
|
||||||
|
fn is_valid(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
CredentialType::Password(_) | CredentialType::GeneratedPassword(_) => true,
|
||||||
|
CredentialType::PasswordMFA(_, m_totp, webauthn) => {
|
||||||
|
m_totp.is_some() || !webauthn.is_empty()
|
||||||
|
}
|
||||||
|
CredentialType::Webauthn(webauthn) => !webauthn.is_empty(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::credential::policy::CryptoPolicy;
|
use crate::credential::policy::CryptoPolicy;
|
||||||
|
@ -500,11 +629,11 @@ mod tests {
|
||||||
fn test_credential_simple() {
|
fn test_credential_simple() {
|
||||||
let p = CryptoPolicy::minimum();
|
let p = CryptoPolicy::minimum();
|
||||||
let c = Credential::new_password_only(&p, "password").unwrap();
|
let c = Credential::new_password_only(&p, "password").unwrap();
|
||||||
assert!(c.verify_password("password"));
|
assert!(c.verify_password("password").unwrap());
|
||||||
assert!(!c.verify_password("password1"));
|
assert!(!c.verify_password("password1").unwrap());
|
||||||
assert!(!c.verify_password("Password1"));
|
assert!(!c.verify_password("Password1").unwrap());
|
||||||
assert!(!c.verify_password("It Works!"));
|
assert!(!c.verify_password("It Works!").unwrap());
|
||||||
assert!(!c.verify_password("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
|
assert!(!c.verify_password("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -6,7 +6,9 @@ use crate::schema::SchemaTransaction;
|
||||||
use crate::value::PartialValue;
|
use crate::value::PartialValue;
|
||||||
use kanidm_proto::v1::Entry as ProtoEntry;
|
use kanidm_proto::v1::Entry as ProtoEntry;
|
||||||
use kanidm_proto::v1::ModifyList as ProtoModifyList;
|
use kanidm_proto::v1::ModifyList as ProtoModifyList;
|
||||||
use kanidm_proto::v1::{AuthCredential, AuthStep, SearchResponse, UserAuthToken, WhoamiResponse};
|
use kanidm_proto::v1::{
|
||||||
|
AuthCredential, AuthMech, AuthStep, SearchResponse, UserAuthToken, WhoamiResponse,
|
||||||
|
};
|
||||||
// use error::OperationError;
|
// use error::OperationError;
|
||||||
use crate::modify::{ModifyInvalid, ModifyList, ModifyValid};
|
use crate::modify::{ModifyInvalid, ModifyList, ModifyValid};
|
||||||
use crate::server::{
|
use crate::server::{
|
||||||
|
@ -925,15 +927,22 @@ pub struct AuthEventStepInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AuthEventStepCreds {
|
pub struct AuthEventStepCred {
|
||||||
pub sessionid: Uuid,
|
pub sessionid: Uuid,
|
||||||
pub creds: Vec<AuthCredential>,
|
pub cred: AuthCredential,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AuthEventStepMech {
|
||||||
|
pub sessionid: Uuid,
|
||||||
|
pub mech: AuthMech,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum AuthEventStep {
|
pub enum AuthEventStep {
|
||||||
Init(AuthEventStepInit),
|
Init(AuthEventStepInit),
|
||||||
Creds(AuthEventStepCreds),
|
Begin(AuthEventStepMech),
|
||||||
|
Cred(AuthEventStepCred),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AuthEventStep {
|
impl AuthEventStep {
|
||||||
|
@ -948,10 +957,19 @@ impl AuthEventStep {
|
||||||
Ok(AuthEventStep::Init(AuthEventStepInit { name, appid: None }))
|
Ok(AuthEventStep::Init(AuthEventStepInit { name, appid: None }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AuthStep::Creds(creds) => match sid {
|
AuthStep::Begin(mech) => match sid {
|
||||||
Some(ssid) => Ok(AuthEventStep::Creds(AuthEventStepCreds {
|
Some(ssid) => Ok(AuthEventStep::Begin(AuthEventStepMech {
|
||||||
sessionid: ssid,
|
sessionid: ssid,
|
||||||
creds,
|
mech,
|
||||||
|
})),
|
||||||
|
None => Err(OperationError::InvalidAuthState(
|
||||||
|
"session id not present in cred".to_string(),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
AuthStep::Cred(cred) => match sid {
|
||||||
|
Some(ssid) => Ok(AuthEventStep::Cred(AuthEventStepCred {
|
||||||
|
sessionid: ssid,
|
||||||
|
cred,
|
||||||
})),
|
})),
|
||||||
None => Err(OperationError::InvalidAuthState(
|
None => Err(OperationError::InvalidAuthState(
|
||||||
"session id not present in cred".to_string(),
|
"session id not present in cred".to_string(),
|
||||||
|
@ -976,19 +994,24 @@ impl AuthEventStep {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn begin_mech(sessionid: Uuid, mech: AuthMech) -> Self {
|
||||||
|
AuthEventStep::Begin(AuthEventStepMech { sessionid, mech })
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn cred_step_anonymous(sid: Uuid) -> Self {
|
pub fn cred_step_anonymous(sid: Uuid) -> Self {
|
||||||
AuthEventStep::Creds(AuthEventStepCreds {
|
AuthEventStep::Cred(AuthEventStepCred {
|
||||||
sessionid: sid,
|
sessionid: sid,
|
||||||
creds: vec![AuthCredential::Anonymous],
|
cred: AuthCredential::Anonymous,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn cred_step_password(sid: Uuid, pw: &str) -> Self {
|
pub fn cred_step_password(sid: Uuid, pw: &str) -> Self {
|
||||||
AuthEventStep::Creds(AuthEventStepCreds {
|
AuthEventStep::Cred(AuthEventStepCred {
|
||||||
sessionid: sid,
|
sessionid: sid,
|
||||||
creds: vec![AuthCredential::Password(pw.to_string())],
|
cred: AuthCredential::Password(pw.to_string()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1024,6 +1047,14 @@ impl AuthEvent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn begin_mech(sessionid: Uuid, mech: AuthMech) -> Self {
|
||||||
|
AuthEvent {
|
||||||
|
event: None,
|
||||||
|
step: AuthEventStep::begin_mech(sessionid, mech),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn cred_step_anonymous(sid: Uuid) -> Self {
|
pub fn cred_step_anonymous(sid: Uuid) -> Self {
|
||||||
AuthEvent {
|
AuthEvent {
|
||||||
|
|
|
@ -289,17 +289,11 @@ impl Account {
|
||||||
) -> Result<bool, OperationError> {
|
) -> Result<bool, OperationError> {
|
||||||
match appid {
|
match appid {
|
||||||
Some(_) => Err(OperationError::InvalidState),
|
Some(_) => Err(OperationError::InvalidState),
|
||||||
None => {
|
None => self
|
||||||
match &self.primary {
|
.primary
|
||||||
// Check the cred's associated pw.
|
|
||||||
Some(ref primary) => primary
|
|
||||||
.password
|
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or(OperationError::InvalidState)
|
.ok_or(OperationError::InvalidState)
|
||||||
.and_then(|pw| pw.verify(cleartext)),
|
.and_then(|cred| cred.password_ref().and_then(|pw| pw.verify(cleartext))),
|
||||||
None => Err(OperationError::InvalidState),
|
|
||||||
}
|
|
||||||
} // no appid
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -10,11 +10,12 @@ pub(crate) mod server;
|
||||||
pub(crate) mod unix;
|
pub(crate) mod unix;
|
||||||
// mod identity;
|
// mod identity;
|
||||||
|
|
||||||
use kanidm_proto::v1::{AuthAllowed, UserAuthToken};
|
use kanidm_proto::v1::{AuthAllowed, AuthMech, UserAuthToken};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum AuthState {
|
pub enum AuthState {
|
||||||
Success(UserAuthToken),
|
Choose(Vec<AuthMech>),
|
||||||
Denied(String),
|
|
||||||
Continue(Vec<AuthAllowed>),
|
Continue(Vec<AuthAllowed>),
|
||||||
|
Denied(String),
|
||||||
|
Success(UserAuthToken),
|
||||||
}
|
}
|
||||||
|
|
|
@ -418,9 +418,58 @@ impl<'a> IdmServerWriteTransaction<'a> {
|
||||||
state,
|
state,
|
||||||
delay,
|
delay,
|
||||||
})
|
})
|
||||||
// })
|
} // AuthEventStep::Init
|
||||||
|
AuthEventStep::Begin(mech) => {
|
||||||
|
// lperf_segment!(au, "idm::server::auth<Begin>", || {
|
||||||
|
let _session_ticket = self.session_ticket.acquire().await;
|
||||||
|
let _softlock_ticket = self.softlock_ticket.acquire().await;
|
||||||
|
|
||||||
|
let mut session_write = self.sessions.write();
|
||||||
|
// Do we have a session?
|
||||||
|
let auth_session = session_write
|
||||||
|
// Why is the session missing?
|
||||||
|
.get_mut(&mech.sessionid)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
ladmin_error!(au, "Invalid Session State (no present session uuid)");
|
||||||
|
OperationError::InvalidSessionState
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// From the auth_session, determine if the current account
|
||||||
|
// credential that we are using has become softlocked or not.
|
||||||
|
let mut softlock_write = self.softlocks.write();
|
||||||
|
|
||||||
|
let cred_uuid = auth_session.get_account().primary_cred_uuid();
|
||||||
|
|
||||||
|
let is_valid = softlock_write
|
||||||
|
.get_mut(&cred_uuid)
|
||||||
|
.map(|slock| {
|
||||||
|
// Apply the current time.
|
||||||
|
slock.apply_time_step(ct);
|
||||||
|
// Now check the results
|
||||||
|
slock.is_valid()
|
||||||
|
})
|
||||||
|
.unwrap_or(true);
|
||||||
|
|
||||||
|
let r = if is_valid {
|
||||||
|
// Indicate to the session which auth mech we now want to proceed with.
|
||||||
|
auth_session.start_session(au, &mech.mech)
|
||||||
|
} else {
|
||||||
|
// Fail the session
|
||||||
|
auth_session.end_session("Account is temporarily locked")
|
||||||
}
|
}
|
||||||
AuthEventStep::Creds(creds) => {
|
.map(|aus| {
|
||||||
|
let delay = None;
|
||||||
|
AuthResult {
|
||||||
|
sessionid: mech.sessionid,
|
||||||
|
state: aus,
|
||||||
|
delay,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
softlock_write.commit();
|
||||||
|
session_write.commit();
|
||||||
|
r
|
||||||
|
} // End AuthEventStep::Mech
|
||||||
|
AuthEventStep::Cred(creds) => {
|
||||||
// lperf_segment!(au, "idm::server::auth<Creds>", || {
|
// lperf_segment!(au, "idm::server::auth<Creds>", || {
|
||||||
let _session_ticket = self.session_ticket.acquire().await;
|
let _session_ticket = self.session_ticket.acquire().await;
|
||||||
let _softlock_ticket = self.softlock_ticket.acquire().await;
|
let _softlock_ticket = self.softlock_ticket.acquire().await;
|
||||||
|
@ -456,7 +505,7 @@ impl<'a> IdmServerWriteTransaction<'a> {
|
||||||
// Basically throw them at the auth_session and see what
|
// Basically throw them at the auth_session and see what
|
||||||
// falls out.
|
// falls out.
|
||||||
auth_session
|
auth_session
|
||||||
.validate_creds(au, &creds.creds, &ct, &self.async_tx, self.webauthn)
|
.validate_creds(au, &creds.cred, &ct, &self.async_tx, self.webauthn)
|
||||||
.map(|aus| {
|
.map(|aus| {
|
||||||
// Inspect the result:
|
// Inspect the result:
|
||||||
// if it was a failure, we need to inc the softlock.
|
// if it was a failure, we need to inc the softlock.
|
||||||
|
@ -479,7 +528,7 @@ impl<'a> IdmServerWriteTransaction<'a> {
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// Fail the session
|
// Fail the session
|
||||||
auth_session.end_session("Account is temporarily locked".to_string())
|
auth_session.end_session("Account is temporarily locked")
|
||||||
}
|
}
|
||||||
.map(|aus| {
|
.map(|aus| {
|
||||||
// TODO: Change this william!
|
// TODO: Change this william!
|
||||||
|
@ -495,8 +544,7 @@ impl<'a> IdmServerWriteTransaction<'a> {
|
||||||
softlock_write.commit();
|
softlock_write.commit();
|
||||||
session_write.commit();
|
session_write.commit();
|
||||||
r
|
r
|
||||||
// })
|
} // End AuthEventStep::Cred
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1382,9 +1430,9 @@ mod tests {
|
||||||
use crate::idm::AuthState;
|
use crate::idm::AuthState;
|
||||||
use crate::modify::{Modify, ModifyList};
|
use crate::modify::{Modify, ModifyList};
|
||||||
use crate::value::{PartialValue, Value};
|
use crate::value::{PartialValue, Value};
|
||||||
use kanidm_proto::v1::AuthAllowed;
|
|
||||||
use kanidm_proto::v1::OperationError;
|
use kanidm_proto::v1::OperationError;
|
||||||
use kanidm_proto::v1::SetCredentialResponse;
|
use kanidm_proto::v1::SetCredentialResponse;
|
||||||
|
use kanidm_proto::v1::{AuthAllowed, AuthMech};
|
||||||
|
|
||||||
use crate::audit::AuditScope;
|
use crate::audit::AuditScope;
|
||||||
use crate::idm::server::IdmServer;
|
use crate::idm::server::IdmServer;
|
||||||
|
@ -1430,12 +1478,12 @@ mod tests {
|
||||||
} = ar;
|
} = ar;
|
||||||
debug_assert!(delay.is_none());
|
debug_assert!(delay.is_none());
|
||||||
match state {
|
match state {
|
||||||
AuthState::Continue(mut conts) => {
|
AuthState::Choose(mut conts) => {
|
||||||
// Should only be one auth mech
|
// Should only be one auth mech
|
||||||
assert!(conts.len() == 1);
|
assert!(conts.len() == 1);
|
||||||
// And it should be anonymous
|
// And it should be anonymous
|
||||||
let m = conts.pop().expect("Should not fail");
|
let m = conts.pop().expect("Should not fail");
|
||||||
assert!(m == AuthAllowed::Anonymous);
|
assert!(m == AuthMech::Anonymous);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
error!(
|
error!(
|
||||||
|
@ -1460,6 +1508,49 @@ mod tests {
|
||||||
|
|
||||||
sid
|
sid
|
||||||
};
|
};
|
||||||
|
{
|
||||||
|
let mut idms_write = idms.write();
|
||||||
|
let anon_begin = AuthEvent::begin_mech(sid, AuthMech::Anonymous);
|
||||||
|
|
||||||
|
let r2 = task::block_on(idms_write.auth(
|
||||||
|
au,
|
||||||
|
&anon_begin,
|
||||||
|
Duration::from_secs(TEST_CURRENT_TIME),
|
||||||
|
));
|
||||||
|
debug!("r2 ==> {:?}", r2);
|
||||||
|
|
||||||
|
match r2 {
|
||||||
|
Ok(ar) => {
|
||||||
|
let AuthResult {
|
||||||
|
sessionid: _,
|
||||||
|
state,
|
||||||
|
delay,
|
||||||
|
} = ar;
|
||||||
|
|
||||||
|
debug_assert!(delay.is_none());
|
||||||
|
match state {
|
||||||
|
AuthState::Continue(allowed) => {
|
||||||
|
// Check the uat.
|
||||||
|
assert!(allowed.len() == 1);
|
||||||
|
assert!(allowed.first() == Some(&AuthAllowed::Anonymous));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
error!(
|
||||||
|
"A critical error has occured! We have a non-continue result!"
|
||||||
|
);
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("A critical error has occured! {:?}", e);
|
||||||
|
// Should not occur!
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
idms_write.commit(au).expect("Must not fail");
|
||||||
|
};
|
||||||
{
|
{
|
||||||
let mut idms_write = idms.write();
|
let mut idms_write = idms.write();
|
||||||
// Now send the anonymous request, given the session id.
|
// Now send the anonymous request, given the session id.
|
||||||
|
@ -1566,9 +1657,14 @@ mod tests {
|
||||||
qs_write.commit(au)
|
qs_write.commit(au)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_admin_authsession_sid(idms: &IdmServer, au: &mut AuditScope, ct: Duration) -> Uuid {
|
fn init_admin_authsession_sid(
|
||||||
|
idms: &IdmServer,
|
||||||
|
au: &mut AuditScope,
|
||||||
|
ct: Duration,
|
||||||
|
name: &str,
|
||||||
|
) -> Uuid {
|
||||||
let mut idms_write = idms.write();
|
let mut idms_write = idms.write();
|
||||||
let admin_init = AuthEvent::named_init("admin");
|
let admin_init = AuthEvent::named_init(name);
|
||||||
|
|
||||||
let r1 = task::block_on(idms_write.auth(au, &admin_init, ct));
|
let r1 = task::block_on(idms_write.auth(au, &admin_init, ct));
|
||||||
let ar = r1.unwrap();
|
let ar = r1.unwrap();
|
||||||
|
@ -1578,6 +1674,26 @@ mod tests {
|
||||||
delay,
|
delay,
|
||||||
} = ar;
|
} = ar;
|
||||||
|
|
||||||
|
debug_assert!(delay.is_none());
|
||||||
|
match state {
|
||||||
|
AuthState::Choose(_) => {}
|
||||||
|
_ => {
|
||||||
|
error!("Sessions was not initialised");
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Now push that we want the Password Mech.
|
||||||
|
let admin_begin = AuthEvent::begin_mech(sessionid, AuthMech::Password);
|
||||||
|
|
||||||
|
let r2 = task::block_on(idms_write.auth(au, &admin_begin, ct));
|
||||||
|
let ar = r2.unwrap();
|
||||||
|
let AuthResult {
|
||||||
|
sessionid,
|
||||||
|
state,
|
||||||
|
delay,
|
||||||
|
} = ar;
|
||||||
|
|
||||||
debug_assert!(delay.is_none());
|
debug_assert!(delay.is_none());
|
||||||
|
|
||||||
match state {
|
match state {
|
||||||
|
@ -1594,7 +1710,8 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_admin_password(idms: &IdmServer, au: &mut AuditScope, pw: &str) {
|
fn check_admin_password(idms: &IdmServer, au: &mut AuditScope, pw: &str) {
|
||||||
let sid = init_admin_authsession_sid(idms, au, Duration::from_secs(TEST_CURRENT_TIME));
|
let sid =
|
||||||
|
init_admin_authsession_sid(idms, au, Duration::from_secs(TEST_CURRENT_TIME), "admin");
|
||||||
|
|
||||||
let mut idms_write = idms.write();
|
let mut idms_write = idms.write();
|
||||||
let anon_step = AuthEvent::cred_step_password(sid, pw);
|
let anon_step = AuthEvent::cred_step_password(sid, pw);
|
||||||
|
@ -1652,33 +1769,13 @@ mod tests {
|
||||||
_idms_delayed: &IdmServerDelayed,
|
_idms_delayed: &IdmServerDelayed,
|
||||||
au: &mut AuditScope| {
|
au: &mut AuditScope| {
|
||||||
init_admin_w_password(au, qs, TEST_PASSWORD).expect("Failed to setup admin account");
|
init_admin_w_password(au, qs, TEST_PASSWORD).expect("Failed to setup admin account");
|
||||||
let mut idms_write = idms.write();
|
|
||||||
let admin_init = AuthEvent::named_init("admin@example.com");
|
|
||||||
|
|
||||||
let r1 = task::block_on(idms_write.auth(
|
let sid = init_admin_authsession_sid(
|
||||||
|
idms,
|
||||||
au,
|
au,
|
||||||
&admin_init,
|
|
||||||
Duration::from_secs(TEST_CURRENT_TIME),
|
Duration::from_secs(TEST_CURRENT_TIME),
|
||||||
));
|
"admin@example.com",
|
||||||
let ar = r1.unwrap();
|
);
|
||||||
let AuthResult {
|
|
||||||
sessionid,
|
|
||||||
state,
|
|
||||||
delay,
|
|
||||||
} = ar;
|
|
||||||
|
|
||||||
debug_assert!(delay.is_none());
|
|
||||||
match state {
|
|
||||||
AuthState::Continue(_) => {}
|
|
||||||
_ => {
|
|
||||||
error!("Sessions was not initialised");
|
|
||||||
panic!();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
idms_write.commit(au).expect("Must not fail");
|
|
||||||
|
|
||||||
let sid = sessionid;
|
|
||||||
|
|
||||||
let mut idms_write = idms.write();
|
let mut idms_write = idms.write();
|
||||||
let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD);
|
let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD);
|
||||||
|
@ -1727,7 +1824,12 @@ mod tests {
|
||||||
_idms_delayed: &IdmServerDelayed,
|
_idms_delayed: &IdmServerDelayed,
|
||||||
au: &mut AuditScope| {
|
au: &mut AuditScope| {
|
||||||
init_admin_w_password(au, qs, TEST_PASSWORD).expect("Failed to setup admin account");
|
init_admin_w_password(au, qs, TEST_PASSWORD).expect("Failed to setup admin account");
|
||||||
let sid = init_admin_authsession_sid(idms, au, Duration::from_secs(TEST_CURRENT_TIME));
|
let sid = init_admin_authsession_sid(
|
||||||
|
idms,
|
||||||
|
au,
|
||||||
|
Duration::from_secs(TEST_CURRENT_TIME),
|
||||||
|
"admin",
|
||||||
|
);
|
||||||
let mut idms_write = idms.write();
|
let mut idms_write = idms.write();
|
||||||
let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD_INC);
|
let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD_INC);
|
||||||
|
|
||||||
|
@ -1804,7 +1906,12 @@ mod tests {
|
||||||
_idms_delayed: &IdmServerDelayed,
|
_idms_delayed: &IdmServerDelayed,
|
||||||
au: &mut AuditScope| {
|
au: &mut AuditScope| {
|
||||||
init_admin_w_password(au, qs, TEST_PASSWORD).expect("Failed to setup admin account");
|
init_admin_w_password(au, qs, TEST_PASSWORD).expect("Failed to setup admin account");
|
||||||
let sid = init_admin_authsession_sid(idms, au, Duration::from_secs(TEST_CURRENT_TIME));
|
let sid = init_admin_authsession_sid(
|
||||||
|
idms,
|
||||||
|
au,
|
||||||
|
Duration::from_secs(TEST_CURRENT_TIME),
|
||||||
|
"admin",
|
||||||
|
);
|
||||||
let mut idms_write = idms.write();
|
let mut idms_write = idms.write();
|
||||||
assert!(idms_write.is_sessionid_present(&sid));
|
assert!(idms_write.is_sessionid_present(&sid));
|
||||||
// Expire like we are currently "now". Should not affect our session.
|
// Expire like we are currently "now". Should not affect our session.
|
||||||
|
@ -2503,7 +2610,12 @@ mod tests {
|
||||||
init_admin_w_password(au, qs, TEST_PASSWORD).expect("Failed to setup admin account");
|
init_admin_w_password(au, qs, TEST_PASSWORD).expect("Failed to setup admin account");
|
||||||
|
|
||||||
// Auth invalid, no softlock present.
|
// Auth invalid, no softlock present.
|
||||||
let sid = init_admin_authsession_sid(idms, au, Duration::from_secs(TEST_CURRENT_TIME));
|
let sid = init_admin_authsession_sid(
|
||||||
|
idms,
|
||||||
|
au,
|
||||||
|
Duration::from_secs(TEST_CURRENT_TIME),
|
||||||
|
"admin",
|
||||||
|
);
|
||||||
let mut idms_write = idms.write();
|
let mut idms_write = idms.write();
|
||||||
let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD_INC);
|
let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD_INC);
|
||||||
|
|
||||||
|
@ -2574,8 +2686,12 @@ mod tests {
|
||||||
// Tested in the softlock state machine.
|
// Tested in the softlock state machine.
|
||||||
|
|
||||||
// Auth valid once softlock pass, valid. Count remains.
|
// Auth valid once softlock pass, valid. Count remains.
|
||||||
let sid =
|
let sid = init_admin_authsession_sid(
|
||||||
init_admin_authsession_sid(idms, au, Duration::from_secs(TEST_CURRENT_TIME + 2));
|
idms,
|
||||||
|
au,
|
||||||
|
Duration::from_secs(TEST_CURRENT_TIME + 2),
|
||||||
|
"admin",
|
||||||
|
);
|
||||||
|
|
||||||
let mut idms_write = idms.write();
|
let mut idms_write = idms.write();
|
||||||
let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD);
|
let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD);
|
||||||
|
@ -2632,12 +2748,20 @@ mod tests {
|
||||||
init_admin_w_password(au, qs, TEST_PASSWORD).expect("Failed to setup admin account");
|
init_admin_w_password(au, qs, TEST_PASSWORD).expect("Failed to setup admin account");
|
||||||
|
|
||||||
// Start an *early* auth session.
|
// Start an *early* auth session.
|
||||||
let sid_early =
|
let sid_early = init_admin_authsession_sid(
|
||||||
init_admin_authsession_sid(idms, au, Duration::from_secs(TEST_CURRENT_TIME));
|
idms,
|
||||||
|
au,
|
||||||
|
Duration::from_secs(TEST_CURRENT_TIME),
|
||||||
|
"admin",
|
||||||
|
);
|
||||||
|
|
||||||
// Start a second auth session
|
// Start a second auth session
|
||||||
let sid_later =
|
let sid_later = init_admin_authsession_sid(
|
||||||
init_admin_authsession_sid(idms, au, Duration::from_secs(TEST_CURRENT_TIME));
|
idms,
|
||||||
|
au,
|
||||||
|
Duration::from_secs(TEST_CURRENT_TIME),
|
||||||
|
"admin",
|
||||||
|
);
|
||||||
// Get the detail wrong in sid_later.
|
// Get the detail wrong in sid_later.
|
||||||
let mut idms_write = idms.write();
|
let mut idms_write = idms.write();
|
||||||
let anon_step = AuthEvent::cred_step_password(sid_later, TEST_PASSWORD_INC);
|
let anon_step = AuthEvent::cred_step_password(sid_later, TEST_PASSWORD_INC);
|
||||||
|
@ -2822,7 +2946,7 @@ mod tests {
|
||||||
let cred = account.primary.expect("Must exist.");
|
let cred = account.primary.expect("Must exist.");
|
||||||
|
|
||||||
let wcred = cred
|
let wcred = cred
|
||||||
.webauthn
|
.webauthn_ref()
|
||||||
.expect("must have webauthn")
|
.expect("must have webauthn")
|
||||||
.values()
|
.values()
|
||||||
.next()
|
.next()
|
||||||
|
|
|
@ -228,18 +228,20 @@ impl UnixUserAccount {
|
||||||
// is the cred some or none?
|
// is the cred some or none?
|
||||||
match &self.cred {
|
match &self.cred {
|
||||||
Some(cred) => {
|
Some(cred) => {
|
||||||
match &cred.password {
|
cred.password_ref().and_then(|pw| {
|
||||||
Some(pw) => {
|
|
||||||
if pw.verify(cleartext)? {
|
if pw.verify(cleartext)? {
|
||||||
lsecurity!(au, "Successful unix cred handling");
|
lsecurity!(au, "Successful unix cred handling");
|
||||||
if pw.requires_upgrade() {
|
if pw.requires_upgrade() {
|
||||||
async_tx.send(
|
async_tx
|
||||||
DelayedAction::UnixPwUpgrade(UnixPasswordUpgrade {
|
.send(DelayedAction::UnixPwUpgrade(UnixPasswordUpgrade {
|
||||||
target_uuid: self.uuid,
|
target_uuid: self.uuid,
|
||||||
existing_password: cleartext.to_string(),
|
existing_password: cleartext.to_string(),
|
||||||
})
|
}))
|
||||||
).map_err(|_| {
|
.map_err(|_| {
|
||||||
ladmin_error!(au, "failed to queue delayed action - unix password upgrade");
|
ladmin_error!(
|
||||||
|
au,
|
||||||
|
"failed to queue delayed action - unix password upgrade"
|
||||||
|
);
|
||||||
OperationError::InvalidState
|
OperationError::InvalidState
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
@ -252,15 +254,7 @@ impl UnixUserAccount {
|
||||||
lsecurity!(au, "Failed unix cred handling (denied)");
|
lsecurity!(au, "Failed unix cred handling (denied)");
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
// We have a cred but it's not a password, that's weird
|
|
||||||
None => {
|
|
||||||
lsecurity!(au, "Invalid unix cred request");
|
|
||||||
Err(OperationError::InvalidAccountState(
|
|
||||||
"non-password cred type?".to_string(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// They don't have a unix cred, fail the auth.
|
// They don't have a unix cred, fail the auth.
|
||||||
None => {
|
None => {
|
||||||
|
@ -272,10 +266,7 @@ impl UnixUserAccount {
|
||||||
|
|
||||||
pub(crate) fn check_existing_pw(&self, cleartext: &str) -> Result<bool, OperationError> {
|
pub(crate) fn check_existing_pw(&self, cleartext: &str) -> Result<bool, OperationError> {
|
||||||
match &self.cred {
|
match &self.cred {
|
||||||
Some(cred) => match &cred.password {
|
Some(cred) => cred.password_ref().and_then(|pw| pw.verify(cleartext)),
|
||||||
Some(pw) => pw.verify(cleartext),
|
|
||||||
None => Err(OperationError::InvalidState),
|
|
||||||
},
|
|
||||||
None => Err(OperationError::InvalidState),
|
None => Err(OperationError::InvalidState),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -128,7 +128,7 @@ impl Plugin for PasswordImport {
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::credential::policy::CryptoPolicy;
|
use crate::credential::policy::CryptoPolicy;
|
||||||
use crate::credential::totp::{TOTP, TOTP_DEFAULT_STEP};
|
use crate::credential::totp::{TOTP, TOTP_DEFAULT_STEP};
|
||||||
use crate::credential::Credential;
|
use crate::credential::{Credential, CredentialType};
|
||||||
use crate::entry::{Entry, EntryInit, EntryNew};
|
use crate::entry::{Entry, EntryInit, EntryNew};
|
||||||
use crate::modify::{Modify, ModifyList};
|
use crate::modify::{Modify, ModifyList};
|
||||||
use crate::server::{QueryServerTransaction, QueryServerWriteTransaction};
|
use crate::server::{QueryServerTransaction, QueryServerWriteTransaction};
|
||||||
|
@ -268,8 +268,13 @@ mod tests {
|
||||||
let c = e
|
let c = e
|
||||||
.get_ava_single_credential("primary_credential")
|
.get_ava_single_credential("primary_credential")
|
||||||
.expect("failed to get primary cred.");
|
.expect("failed to get primary cred.");
|
||||||
assert!(c.totp.is_some());
|
match &c.type_ {
|
||||||
assert!(c.password.is_some());
|
CredentialType::PasswordMFA(_pw, totp, webauthn) => {
|
||||||
|
assert!(totp.is_some());
|
||||||
|
assert!(webauthn.is_empty());
|
||||||
|
}
|
||||||
|
_ => assert!(false),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3556,7 +3556,7 @@ mod tests {
|
||||||
.get_ava_single_credential("primary_credential")
|
.get_ava_single_credential("primary_credential")
|
||||||
.expect("Failed");
|
.expect("Failed");
|
||||||
// do a pw check.
|
// do a pw check.
|
||||||
assert!(cred_ref.verify_password("test_password"));
|
assert!(cred_ref.verify_password("test_password").unwrap());
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue