diff --git a/Cargo.lock b/Cargo.lock index 32a25519c..d6fdfabb9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -78,7 +78,7 @@ dependencies = [ "log", "mime", "mime_guess", - "percent-encoding 2.1.0", + "percent-encoding", "v_htmlescape", ] @@ -118,7 +118,7 @@ dependencies = [ "lazy_static", "log", "mime", - "percent-encoding 2.1.0", + "percent-encoding", "pin-project", "rand", "regex", @@ -191,9 +191,9 @@ dependencies = [ [[package]] name = "actix-service" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e4fc95dfa7e24171b2d0bb46b85f8ab0e8499e4e3caec691fc4ea65c287564" +checksum = "0052435d581b5be835d11f4eb3bce417c8af18d87ddf8ace99f8e67e595882bb" dependencies = [ "futures-util", "pin-project", @@ -314,7 +314,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "time 0.1.43", - "url 2.1.1", + "url", ] [[package]] @@ -402,16 +402,63 @@ dependencies = [ ] [[package]] -name = "async-std" -version = "1.6.2" +name = "async-executor" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00d68a33ebc8b57800847d00787307f84a562224a14db069b0acefe4c2abbf5d" +checksum = "90f47c78ea98277cb1f5e6f60ba4fc762f5eafe9f6511bc2f7dfd8b75c225650" dependencies = [ + "async-io", + "futures-lite", + "multitask", + "parking 1.0.6", + "scoped-tls", + "waker-fn", +] + +[[package]] +name = "async-io" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae22a338d28c75b53702b66f77979062cb29675db376d99e451af4fa79dedb3" +dependencies = [ + "cfg-if", + "concurrent-queue", + "futures-lite", + "libc", + "once_cell", + "parking 2.0.0", + "polling", + "socket2", + "vec-arena", + "wepoll-sys-stjepang", + "winapi 0.3.9", +] + +[[package]] +name = "async-mutex" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20e85981fc34e84cdff3fc2c9219189752633fdc538a06df8b5ac45b68a4f3a9" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-std" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c8da367da62b8ff2313c406c9ac091c1b31d67a165becdd2de380d846260f7" +dependencies = [ + "async-executor", + "async-io", + "async-mutex", "async-task", + "blocking", "crossbeam-utils", "futures-channel", "futures-core", "futures-io", + "futures-lite", "kv-log-macro", "log", "memchr", @@ -420,7 +467,6 @@ dependencies = [ "pin-project-lite", "pin-utils", "slab", - "smol", "wasm-bindgen-futures", ] @@ -432,9 +478,9 @@ checksum = "c17772156ef2829aadc587461c7753af20b7e8db1529bc66855add962a3b35d3" [[package]] name = "async-trait" -version = "0.1.36" +version = "0.1.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a265e3abeffdce30b2e26b7a11b222fe37c6067404001b434101457d0385eb92" +checksum = "6e1a4a2f97ce50c9d0282c1468816208588441492b40d813b2e0419c22c05e7f" dependencies = [ "proc-macro2", "quote", @@ -460,9 +506,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "awc" @@ -481,7 +527,7 @@ dependencies = [ "log", "mime", "openssl", - "percent-encoding 2.1.0", + "percent-encoding", "rand", "serde", "serde_json", @@ -581,15 +627,14 @@ dependencies = [ [[package]] name = "blocking" -version = "0.4.7" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2468ff7bf85066b4a3678fede6fe66db31846d753ff0adfbfab2c6a6e81612b" +checksum = "ea5800d29218fea137b0880387e5948694a23c93fcdde157006966693a865c7c" dependencies = [ "async-channel", "atomic-waker", "futures-lite", "once_cell", - "parking", "waker-fn", ] @@ -681,9 +726,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a06fb2e53271d7c279ec1efea6ab691c35a2ae67ec0d91d7acec0caf13b518" +checksum = "66120af515773fb005778dc07c261bd201ec8ce50bd6e7144c927753fe013381" [[package]] name = "cfg-if" @@ -693,9 +738,9 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "chrono" -version = "0.4.13" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c74d84029116787153e02106bf53e66828452a4b325cc8652b788b5967c0a0b6" +checksum = "942f72db697d8767c22d46a598e01f2d3b475501ea43d0db4f16d90259182d0b" dependencies = [ "num-integer", "num-traits", @@ -704,9 +749,9 @@ dependencies = [ [[package]] name = "clap" -version = "2.33.1" +version = "2.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" dependencies = [ "bitflags", "textwrap", @@ -749,9 +794,9 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "1.2.0" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e296417c8154304ac70aceda41f05318f986f7c0c767bcb0a2366fbb890e78e1" +checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" dependencies = [ "cache-padded", ] @@ -782,40 +827,31 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" -[[package]] -name = "cookie" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "888604f00b3db336d2af898ec3c1d5d0ddf5e6d462220f2ededc33a87ac4bbd5" -dependencies = [ - "time 0.1.43", - "url 1.7.2", -] - [[package]] name = "cookie" version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1373a16a4937bc34efec7b391f9c1500c30b8478a701a4f44c9165cc0475a6e0" dependencies = [ + "percent-encoding", "time 0.2.16", "version_check 0.9.2", ] [[package]] name = "cookie_store" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2a7e5bf5517bf3c3f4358f13d3ec2092419ac2332aa8593605c957da73d491" +checksum = "3818dfca4b0cb5211a659bbcbb94225b7127407b2b135e650d717bfb78ab10d3" dependencies = [ - "cookie 0.12.0", - "idna 0.2.0", + "cookie", + "idna", "log", "publicsuffix", "serde", "serde_json", - "time 0.1.43", - "url 2.1.1", + "time 0.2.16", + "url", ] [[package]] @@ -1094,9 +1130,9 @@ checksum = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b" [[package]] name = "either" -version = "1.5.3" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" +checksum = "cd56b59865bce947ac5958779cfa508f6c3b9497cc762b7e24a12d11ccde2c4f" [[package]] name = "encoding_rs" @@ -1109,9 +1145,9 @@ dependencies = [ [[package]] name = "enum-as-inner" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc4bfcfacb61d231109d1d55202c1f33263319668b168843e02ad4652725ec9c" +checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595" dependencies = [ "heck", "proc-macro2", @@ -1134,18 +1170,18 @@ dependencies = [ [[package]] name = "error-chain" -version = "0.12.2" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d371106cc88ffdfb1eabd7111e432da544f16f3e2d7bf1dfe8bf575f1df045cd" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" dependencies = [ "version_check 0.9.2", ] [[package]] name = "event-listener" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298f00c3b04c1d9b4cb86aefaaa35348af0957d98b30a5306fc635f8e718923d" +checksum = "e1cd41440ae7e4734bbd42302f63eaba892afc93a3912dad84006247f0dedb0e" [[package]] name = "failure" @@ -1199,15 +1235,15 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.3.3" +version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36a9cb09840f81cd211e435d00a4e487edd263dc3c8ff815c32dd76ad668ebed" +checksum = "4bd3bdaaf0a72155260a1c098989b60db1cbb22d6a628e64f16237aa4da93cc7" [[package]] name = "flate2" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68c90b0fc46cf89d227cc78b40e494ff81287a92dd07631e5af0d06fe3cf885e" +checksum = "766d0e77a2c1502169d4a93ff3b8c15a71fd946cd0126309752104e5f3c46d94" dependencies = [ "cfg-if", "crc32fast", @@ -1302,15 +1338,15 @@ checksum = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789" [[package]] name = "futures-lite" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe71459749b2e8e66fb95df721b22fa08661ad384a0c5b519e11d3893b4692a" +checksum = "97999970129b808f0ccba93211201d431fcc12d7e1ffae03a61b5cedd1a7ced2" dependencies = [ "fastrand", "futures-core", "futures-io", "memchr", - "parking", + "parking 2.0.0", "pin-project-lite", "waker-fn", ] @@ -1424,9 +1460,9 @@ checksum = "d36fab90f82edc3c747f9d438e06cf0a491055896f2a279638bb5beed6c40177" [[package]] name = "hashbrown" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34f595585f103464d8d2f6e9864682d74c1601fed5e07d62b1c9058dba8246fb" +checksum = "e91b62f79061a0bc2e046024cb7ba44b08419ed238ecbd9adbd787434b9e8c25" dependencies = [ "ahash", "autocfg", @@ -1552,17 +1588,6 @@ dependencies = [ "time 0.1.43", ] -[[package]] -name = "idna" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "idna" version = "0.2.0" @@ -1576,9 +1601,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b88cd59ee5f71fea89a62248fc8f387d44400cefe05ef548466d61ced9029a7" +checksum = "86b45e59b16c76b11bf9738fd5d38879d3bd28ad292d7b313608becb17ae2df9" dependencies = [ "autocfg", "hashbrown", @@ -1611,6 +1636,12 @@ dependencies = [ "winreg 0.6.2", ] +[[package]] +name = "ipnet" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135" + [[package]] name = "itertools" version = "0.8.2" @@ -1637,9 +1668,9 @@ checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" [[package]] name = "js-sys" -version = "0.3.42" +version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52732a3d3ad72c58ad2dc70624f9c17b46ecd0943b9a4f1ee37c4c18c5d983e2" +checksum = "85a7e2c92a4804dd459b86c339278d0fe87cf93757fae222c3fa3ae75458bc73" dependencies = [ "wasm-bindgen", ] @@ -1653,11 +1684,12 @@ dependencies = [ "actix-rt", "actix-session", "actix-web", + "async-std", "base64 0.12.3", "cargo-husky", "chrono", "concread", - "cookie 0.14.2", + "cookie", "criterion", "crossbeam", "env_logger", @@ -1829,9 +1861,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.73" +version = "0.2.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd7d4bd64732af4bf3a67f367c27df8520ad7e230c5817b8ff485864d80242b9" +checksum = "755456fae044e6fa1ebbbd1b3e902ae19e73097ed4ed87bb79934a867c007bc3" [[package]] name = "libnss" @@ -1947,9 +1979,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be0f75932c1f6cfae3c04000e40114adf955636e19040f9c0a2c380702aa1c7f" +checksum = "4d7559a8a40d0f97e1edea3220f698f78b1c5ab67532e49f68fde3910323b722" dependencies = [ "adler", ] @@ -2018,6 +2050,17 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "multitask" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c09c35271e7dcdb5f709779111f2c8e8ab8e06c1b587c1c6a9e179d865aaa5b4" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", +] + [[package]] name = "native-tls" version = "0.2.4" @@ -2169,9 +2212,9 @@ checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5" [[package]] name = "once_cell" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" +checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad" [[package]] name = "oorandom" @@ -2244,6 +2287,12 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6cb300f271742d4a2a66c01b6b2fa0c83dfebd2e0bf11addb879a3547b4ed87c" +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + [[package]] name = "parking_lot" version = "0.10.2" @@ -2313,12 +2362,6 @@ dependencies = [ "proc-macro-hack", ] -[[package]] -name = "percent-encoding" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" - [[package]] name = "percent-encoding" version = "2.1.0" @@ -2327,18 +2370,18 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pin-project" -version = "0.4.22" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12e3a6cdbfe94a5e4572812a0201f8c0ed98c1c452c7b8563ce2276988ef9c17" +checksum = "ca4433fff2ae79342e497d9f8ee990d174071408f28f726d6d83af93e58e48aa" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "0.4.22" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a0ffd45cf79d88737d7cc85bfd5d2894bee1139b356e616fe85dc389c61aaf7" +checksum = "2c0e815c3ee9a031fdf5af21c10aa17c573c9c6a566328d99e3936c34e36461f" dependencies = [ "proc-macro2", "quote", @@ -2376,16 +2419,28 @@ dependencies = [ ] [[package]] -name = "ppv-lite86" -version = "0.2.8" +name = "polling" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" +checksum = "9e09dffb745feffca5be3dea51c02b7b368c4597ab0219a82acaf9799ab3e0d1" +dependencies = [ + "cfg-if", + "libc", + "wepoll-sys-stjepang", + "winapi 0.3.9", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c36fa947111f5c62a733b652544dd0016a43ce89619538a8ef92724a6f501a20" [[package]] name = "proc-macro-error" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc175e9777c3116627248584e8f8b3e2987405cabe1c0adf7d1dd28f09dc7880" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", "proc-macro2", @@ -2396,22 +2451,20 @@ dependencies = [ [[package]] name = "proc-macro-error-attr" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cc9795ca17eb581285ec44936da7fc2335a3f34f2ddd13118b6f4d515435c50" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ "proc-macro2", "quote", - "syn", - "syn-mid", "version_check 0.9.2", ] [[package]] name = "proc-macro-hack" -version = "0.5.16" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4" +checksum = "99c605b9a0adc77b7211c6b1f722dcb613d68d66859a44f3d485a6da332b0598" [[package]] name = "proc-macro-nested" @@ -2435,10 +2488,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3bbaa49075179162b49acac1c6aa45fb4dafb5f13cf6794276d77bc7fd95757b" dependencies = [ "error-chain", - "idna 0.2.0", + "idna", "lazy_static", "regex", - "url 2.1.1", + "url", ] [[package]] @@ -2598,13 +2651,13 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b82c9238b305f26f53443e3a4bc8528d64b8d0bee408ec949eb7bf5635ec680" +checksum = "12427a5577082c24419c9c417db35cfeb65962efc7675bb6b0d5f1f9d315bfe6" dependencies = [ "base64 0.12.3", "bytes", - "cookie 0.12.0", + "cookie", "cookie_store", "encoding_rs", "futures-core", @@ -2613,21 +2666,22 @@ dependencies = [ "http-body", "hyper", "hyper-tls", + "ipnet", "js-sys", "lazy_static", "log", "mime", "mime_guess", "native-tls", - "percent-encoding 2.1.0", + "percent-encoding", "pin-project-lite", "serde", "serde_json", "serde_urlencoded", - "time 0.1.43", + "time 0.2.16", "tokio", "tokio-tls", - "url 2.1.1", + "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -2798,9 +2852,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.114" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" +checksum = "e54c9a88f2da7238af84b5101443f0c0d0a3bbdc455e34a5c9497b1903ed55d5" dependencies = [ "serde_derive", ] @@ -2817,9 +2871,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.114" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e" +checksum = "609feed1d0a73cc36a0182a840a9b37b4a82f0b1150369f0536a9e3f2a31dc48" dependencies = [ "proc-macro2", "quote", @@ -2828,9 +2882,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3433e879a558dde8b5e8feb2a04899cf34fdde1fafb894687e52105fc1162ac3" +checksum = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c" dependencies = [ "itoa", "ryu", @@ -2846,7 +2900,7 @@ dependencies = [ "dtoa", "itoa", "serde", - "url 2.1.1", + "url", ] [[package]] @@ -2878,9 +2932,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41" +checksum = "a3e12110bc539e657a646068aaf5eb5b63af9d0c1f7b29c97113fad80e15f035" dependencies = [ "arc-swap", "libc", @@ -2894,34 +2948,13 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" [[package]] name = "smallvec" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3757cb9d89161a2f24e1cf78efa0c1fcff485d18e3f55e0aa3480824ddaa0f3f" +checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252" dependencies = [ "serde", ] -[[package]] -name = "smol" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "620cbb3c6e34da57d3a248cda0cd01cd5848164dc062e764e65d06fe3ea7aed5" -dependencies = [ - "async-task", - "blocking", - "concurrent-queue", - "fastrand", - "futures-io", - "futures-util", - "libc", - "once_cell", - "scoped-tls", - "slab", - "socket2", - "wepoll-sys-stjepang", - "winapi 0.3.9", -] - [[package]] name = "socket2" version = "0.3.12" @@ -3017,9 +3050,9 @@ checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" [[package]] name = "structopt" -version = "0.3.15" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de2f5e239ee807089b62adce73e48c625e0ed80df02c7ab3f068f5db5281065c" +checksum = "de5472fb24d7e80ae84a7801b7978f95a19ec32cb1876faea59ab711eb901976" dependencies = [ "clap", "lazy_static", @@ -3028,9 +3061,9 @@ dependencies = [ [[package]] name = "structopt-derive" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "510413f9de616762a4fbeab62509bf15c729603b72d7cd71280fbca431b1c118" +checksum = "1e0eb37335aeeebe51be42e2dc07f031163fbabfa6ac67d7ea68b5c2f68d5f99" dependencies = [ "heck", "proc-macro-error", @@ -3041,26 +3074,15 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.35" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb7f4c519df8c117855e19dd8cc851e89eb746fe7a73f0157e0d95fdec5369b0" +checksum = "891d8d6567fe7c7f8835a3a98af4208f3846fba258c1bc3c31d6e506239f11f9" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] -[[package]] -name = "syn-mid" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "synstructure" version = "0.12.4" @@ -3203,9 +3225,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53953d2d3a5ad81d9f844a32f14ebb121f50b650cd59d0ee2a07cf13c617efed" +checksum = "238ce071d267c5710f9d31451efec16c5ee22de34df17cc05e56cbc92e967117" [[package]] name = "tokio" @@ -3307,9 +3329,9 @@ checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" [[package]] name = "tracing" -version = "0.1.17" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbdf4ccd1652592b01286a5dbe1e2a77d78afaa34beadd9872a5f7396f92aaa9" +checksum = "6d79ca061b032d6ce30c660fded31189ca0b9922bf483cd70759f13a2d86786c" dependencies = [ "cfg-if", "log", @@ -3318,9 +3340,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.11" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94ae75f0d28ae10786f3b1895c55fe72e79928fd5ccdebb5438c75e93fec178f" +checksum = "4f0e00789804e99b20f12bc7003ca416309d28a6f495d6af58d1e2c2842461b5" dependencies = [ "lazy_static", ] @@ -3335,14 +3357,14 @@ dependencies = [ "enum-as-inner", "failure", "futures", - "idna 0.2.0", + "idna", "lazy_static", "log", "rand", "smallvec", "socket2", "tokio", - "url 2.1.1", + "url", ] [[package]] @@ -3427,26 +3449,15 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" -[[package]] -name = "url" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" -dependencies = [ - "idna 0.1.5", - "matches", - "percent-encoding 1.0.1", -] - [[package]] name = "url" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" dependencies = [ - "idna 0.2.0", + "idna", "matches", - "percent-encoding 2.1.0", + "percent-encoding", ] [[package]] @@ -3506,6 +3517,12 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" +[[package]] +name = "vec-arena" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cb18268690309760d59ee1a9b21132c126ba384f374c59a94db4bc03adeb561" + [[package]] name = "version_check" version = "0.1.5" @@ -3553,9 +3570,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasm-bindgen" -version = "0.2.65" +version = "0.2.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edbcc9536ab7eababcc6d2374a0b7bfe13a2b6d562c5e07f370456b1a8f33d" +checksum = "f0563a9a4b071746dd5aedbc3a28c6fe9be4586fb3fbadb67c400d4f53c6b16c" dependencies = [ "cfg-if", "serde", @@ -3565,9 +3582,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.65" +version = "0.2.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ed2fb8c84bfad20ea66b26a3743f3e7ba8735a69fe7d95118c33ec8fc1244d" +checksum = "bc71e4c5efa60fb9e74160e89b93353bc24059999c0ae0fb03affc39770310b0" dependencies = [ "bumpalo", "lazy_static", @@ -3580,9 +3597,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.15" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ad6e4e8b2b7f8c90b6e09a9b590ea15cb0d1dbe28502b5a405cd95d1981671" +checksum = "95f8d235a77f880bcef268d379810ea6c0af2eacfa90b1ad5af731776e0c4699" dependencies = [ "cfg-if", "js-sys", @@ -3592,9 +3609,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.65" +version = "0.2.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb071268b031a64d92fc6cf691715ca5a40950694d8f683c5bb43db7c730929e" +checksum = "97c57cefa5fa80e2ba15641578b44d36e7a64279bc5ed43c6dbaf329457a2ed2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3602,9 +3619,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.65" +version = "0.2.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf592c807080719d1ff2f245a687cbadb3ed28b2077ed7084b47aba8b691f2c6" +checksum = "841a6d1c35c6f596ccea1f82504a192a60378f64b3bb0261904ad8f2f5657556" dependencies = [ "proc-macro2", "quote", @@ -3615,15 +3632,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.65" +version = "0.2.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b6c0220ded549d63860c78c38f3bcc558d1ca3f4efa74942c536ddbbb55e87" +checksum = "93b162580e34310e5931c4b792560108b10fd14d64915d7fff8ff00180e70092" [[package]] name = "web-sys" -version = "0.3.42" +version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be2398f326b7ba09815d0b403095f34dd708579220d099caae89be0b32137b2" +checksum = "dda38f4e5ca63eda02c059d243aa25b5f35ab98451e518c51612cd0f1bd19a47" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md new file mode 100644 index 000000000..5c24708c5 --- /dev/null +++ b/GETTING_STARTED.md @@ -0,0 +1,9 @@ +## Getting Started (for Administrators) + +If you want to deploy kanidm, or to see what it can do, you should read the [kanidm book] + +[kanidm book]: https://github.com/kanidm/kanidm/blob/master/kanidm_book/src/SUMMARY.md + +## Getting Started (for Developers) + +See the README.md file. diff --git a/README.md b/README.md index 0de2f3ee7..fc924ad83 100644 --- a/README.md +++ b/README.md @@ -16,34 +16,25 @@ Today the project is still under heavy development to achieve these goals - we d functional release before early 2020. It is important to note that not all needed security features of the system have been completed yet! -## Code of Conduct +## Code of Conduct / Ethics See our [code of conduct] -[code of conduct]: https://github.com/kanidm/kanidm/blob/master/CODE_OF_CONDUCT.md - -## Ethics / Rights - See our documentation on [rights and ethics] +[code of conduct]: https://github.com/kanidm/kanidm/blob/master/CODE_OF_CONDUCT.md [rights and ethics]: https://github.com/kanidm/kanidm/blob/master/ethics/README.md -## Some key ideas - -* All people should be respected and able to be respresented securely. -* Devices represent users and their identities - they are part of the authentication. -* Human error occurs - we should be designed to minimise human mistakes and empower people. -* The system should be easy to understand and reason about for users and admins. - -## Documentation +## Documentation / Getting Started / Install If you want to deploy kanidm, or to see what it can do, you should read the [kanidm book] [kanidm book]: https://github.com/kanidm/kanidm/blob/master/kanidm_book/src/SUMMARY.md -## Getting in Contact +## Getting in Contact / Questions -We have a [gitter community channel] where we can talk. Firstyear is also happy to answer questions via email, which can be found on their github profile. +We have a [gitter community channel] where we can talk. Firstyear is also happy to +answer questions via email, which can be found on their github profile. [gitter community channel]: https://gitter.im/kanidm/community @@ -70,6 +61,13 @@ We have a [gitter community channel] where we can talk. Firstyear is also happy * Generic database: We don't want to be another NoSQL database, we want to be an IDM solution. * Being LDAP/GSSAPI/Kerberos: These are all legacy protocols that are hard to use and confine our thinking - we should avoid "being like them". +## Some key ideas + +* All people should be respected and able to be respresented securely. +* Devices represent users and their identities - they are part of the authentication. +* Human error occurs - we should be designed to minimise human mistakes and empower people. +* The system should be easy to understand and reason about for users and admins. + ## Development and Testing ### Designs diff --git a/kanidm_proto/src/v1.rs b/kanidm_proto/src/v1.rs index 13bf3f6f8..8b56509e1 100644 --- a/kanidm_proto/src/v1.rs +++ b/kanidm_proto/src/v1.rs @@ -102,6 +102,7 @@ pub enum OperationError { PasswordBadListed, CryptographyError, ResourceLimit, + QueueDisconnected, } impl PartialEq for OperationError { diff --git a/kanidmd/Cargo.toml b/kanidmd/Cargo.toml index 9621970d2..60d7c461b 100644 --- a/kanidmd/Cargo.toml +++ b/kanidmd/Cargo.toml @@ -34,6 +34,8 @@ actix-web = { version = "2.0", features = ["openssl"] } actix-session = "0.3" actix-files = "0.2" +async-std = "1.6" + log = "0.4" env_logger = "0.7" rand = "0.7" diff --git a/kanidmd/src/lib/be/dbvalue.rs b/kanidmd/src/lib/be/dbvalue.rs index 125c882be..54a842ec7 100644 --- a/kanidmd/src/lib/be/dbvalue.rs +++ b/kanidmd/src/lib/be/dbvalue.rs @@ -11,6 +11,7 @@ pub struct DbCidV1 { #[derive(Serialize, Deserialize, Debug)] pub enum DbPasswordV1 { PBKDF2(usize, Vec, Vec), + SSHA512(Vec, Vec), } #[derive(Serialize, Deserialize, Debug)] diff --git a/kanidmd/src/lib/core/mod.rs b/kanidmd/src/lib/core/mod.rs index 2ced4b86a..a9ff03ddd 100644 --- a/kanidmd/src/lib/core/mod.rs +++ b/kanidmd/src/lib/core/mod.rs @@ -36,7 +36,7 @@ use crate::audit::AuditScope; use crate::be::{Backend, BackendTransaction, FsType}; use crate::crypto::setup_tls; use crate::filter::{Filter, FilterInvalid}; -use crate::idm::server::IdmServer; +use crate::idm::server::{IdmServer, IdmServerDelayed}; use crate::interval::IntervalActor; use crate::ldap::LdapServer; use crate::schema::Schema; @@ -1301,7 +1301,7 @@ fn setup_qs_idms( audit: &mut AuditScope, be: Backend, schema: Schema, -) -> Result<(QueryServer, IdmServer), OperationError> { +) -> Result<(QueryServer, IdmServer, IdmServerDelayed), OperationError> { // Create a query_server implementation let query_server = QueryServer::new(be, schema); @@ -1317,9 +1317,9 @@ fn setup_qs_idms( // We generate a SINGLE idms only! - let idms = IdmServer::new(query_server.clone()); + let (idms, idms_delayed) = IdmServer::new(query_server.clone()); - Ok((query_server, idms)) + Ok((query_server, idms, idms_delayed)) } pub fn backup_server_core(config: &Configuration, dst_path: &str) { @@ -1389,7 +1389,7 @@ pub fn restore_server_core(config: &Configuration, dst_path: &str) { info!("Attempting to init query server ..."); - let (qs, _idms) = match setup_qs_idms(&mut audit, be, schema) { + let (qs, _idms, _idms_delayed) = match setup_qs_idms(&mut audit, be, schema) { Ok(t) => t, Err(e) => { audit.write_log(); @@ -1457,7 +1457,7 @@ pub fn reindex_server_core(config: &Configuration) { eprintln!("Attempting to init query server ..."); - let (qs, _idms) = match setup_qs_idms(&mut audit, be, schema) { + let (qs, _idms, _idms_delayed) = match setup_qs_idms(&mut audit, be, schema) { Ok(t) => t, Err(e) => { audit.write_log(); @@ -1508,7 +1508,7 @@ pub fn domain_rename_core(config: &Configuration, new_domain_name: &str) { } }; // setup the qs - *with* init of the migrations and schema. - let (qs, _idms) = match setup_qs_idms(&mut audit, be, schema) { + let (qs, _idms, _idms_delayed) = match setup_qs_idms(&mut audit, be, schema) { Ok(t) => t, Err(e) => { audit.write_log(); @@ -1606,7 +1606,7 @@ pub fn recover_account_core(config: &Configuration, name: &str, password: &str) } }; // setup the qs - *with* init of the migrations and schema. - let (_qs, idms) = match setup_qs_idms(&mut audit, be, schema) { + let (_qs, idms, _idms_delayed) = match setup_qs_idms(&mut audit, be, schema) { Ok(t) => t, Err(e) => { audit.write_log(); @@ -1689,7 +1689,7 @@ pub async fn create_server_core(config: Configuration) -> Result } }; // Start the IDM server. - let (qs, idms) = match setup_qs_idms(&mut audit, be, schema) { + let (qs, idms, mut idms_delayed) = match setup_qs_idms(&mut audit, be, schema) { Ok(t) => t, Err(e) => { audit.write_log(); @@ -1756,6 +1756,9 @@ pub async fn create_server_core(config: Configuration) -> Result let server_write_addr = QueryServerWriteV1::start(log_tx.clone(), config.log_level, qs, idms_arc); + // TODO #314: For now we just drop everything from the delayed queue until we rewrite to be async. + tokio::spawn(async move { idms_delayed.temp_drop_all().await; }); + // Setup timed events associated to the write thread let _int_addr = IntervalActor::new(server_write_addr.clone()).start(); diff --git a/kanidmd/src/lib/credential/mod.rs b/kanidmd/src/lib/credential/mod.rs index 87bfcc6e7..668549985 100644 --- a/kanidmd/src/lib/credential/mod.rs +++ b/kanidmd/src/lib/credential/mod.rs @@ -2,6 +2,7 @@ use crate::be::dbvalue::{DbCredV1, DbPasswordV1}; use kanidm_proto::v1::OperationError; use openssl::hash::MessageDigest; use openssl::pkcs5::pbkdf2_hmac; +use openssl::sha::Sha512; use rand::prelude::*; use std::convert::TryFrom; use std::time::{Duration, Instant}; @@ -20,6 +21,9 @@ const PBKDF2_SALT_LEN: usize = 24; const PBKDF2_KEY_LEN: usize = 64; const PBKDF2_IMPORT_MIN_LEN: usize = 32; +const DS_SSHA512_SALT_LEN: usize = 8; +const DS_SSHA512_HASH_LEN: usize = 64; + // These are in order of "relative" strength. /* #[derive(Clone, Debug)] @@ -38,6 +42,8 @@ pub enum Policy { enum KDF { // cost, salt, hash PBKDF2(usize, Vec, Vec), + // salt hash + SSHA512(Vec, Vec), } #[derive(Clone, Debug)] @@ -53,6 +59,9 @@ impl TryFrom for Password { DbPasswordV1::PBKDF2(c, s, h) => Ok(Password { material: KDF::PBKDF2(c, s, h), }), + DbPasswordV1::SSHA512(s, h) => Ok(Password { + material: KDF::SSHA512(s, h), + }), } } } @@ -88,6 +97,18 @@ impl TryFrom<&str> for Password { } } + // Test 389ds formats + if let Some(ds_ssha512) = value.strip_prefix("{SSHA512}") { + let sh = base64::decode(ds_ssha512).map_err(|_| ())?; + let (h, s) = sh.split_at(DS_SSHA512_HASH_LEN); + if s.len() != DS_SSHA512_SALT_LEN { + return Err(()); + } + return Ok(Password { + material: KDF::SSHA512(s.to_vec(), h.to_vec()), + }); + } + // Nothing matched to this point. Err(()) } @@ -160,6 +181,13 @@ impl Password { &chal_key == key }) } + KDF::SSHA512(salt, key) => { + let mut hasher = Sha512::new(); + hasher.update(cleartext.as_bytes()); + hasher.update(&salt); + let r = hasher.finish(); + Ok(key == &(r.to_vec())) + } } } @@ -168,6 +196,14 @@ impl Password { KDF::PBKDF2(cost, salt, hash) => { DbPasswordV1::PBKDF2(*cost, salt.clone(), hash.clone()) } + KDF::SSHA512(salt, hash) => DbPasswordV1::SSHA512(salt.clone(), hash.clone()), + } + } + + pub fn requires_upgrade(&self) -> bool { + match &self.material { + KDF::PBKDF2(_, _, _) => false, + _ => true, } } } @@ -352,4 +388,14 @@ mod tests { let r = Password::try_from(im_pw).expect("Failed to parse"); assert!(r.verify(password).unwrap_or(false)); } + + #[test] + fn test_password_from_ds_ssha512() { + let im_pw = "{SSHA512}JwrSUHkI7FTAfHRVR6KoFlSN0E3dmaQWARjZ+/UsShYlENOqDtFVU77HJLLrY2MuSp0jve52+pwtdVl2QUAHukQ0XUf5LDtM"; + let password = "password"; + let r = Password::try_from(im_pw).expect("Failed to parse"); + // Known weak, require upgrade. + assert!(r.requires_upgrade()); + assert!(r.verify(password).unwrap_or(false)); + } } diff --git a/kanidmd/src/lib/idm/account.rs b/kanidmd/src/lib/idm/account.rs index c70bcc49a..59ca70587 100644 --- a/kanidmd/src/lib/idm/account.rs +++ b/kanidmd/src/lib/idm/account.rs @@ -200,6 +200,31 @@ impl Account { } } + pub(crate) fn check_credential_pw( + &self, + cleartext: &str, + appid: &Option, + ) -> Result { + match appid { + Some(_) => Err(OperationError::InvalidState), + None => { + match &self.primary { + // Check the cred's associated pw. + Some(ref primary) => { + primary.password.as_ref() + .ok_or(OperationError::InvalidState) + .and_then(|pw| { + pw.verify(cleartext) + }) + } + None => { + Err(OperationError::InvalidState) + } + } + } // no appid + } + } + pub(crate) fn regenerate_radius_secret_mod( &self, cleartext: &str, diff --git a/kanidmd/src/lib/idm/authsession.rs b/kanidmd/src/lib/idm/authsession.rs index 8457d0fc2..de8154eb2 100644 --- a/kanidmd/src/lib/idm/authsession.rs +++ b/kanidmd/src/lib/idm/authsession.rs @@ -6,8 +6,13 @@ use kanidm_proto::v1::{AuthAllowed, AuthCredential, AuthState}; use crate::credential::{totp::TOTP, Credential, Password}; +use crate::idm::delayed::{DelayedAction, PasswordUpgrade}; +// use crossbeam::channel::Sender; +use tokio::sync::mpsc::UnboundedSender as Sender; + use std::convert::TryFrom; use std::time::Duration; +use uuid::Uuid; // Each CredHandler takes one or more credentials and determines if the // handlers requirements can be 100% fufilled. This is where MFA or other @@ -72,14 +77,216 @@ impl TryFrom<&Credential> for CredHandler { } } -// TODO: Can this be improved? -#[allow(clippy::cognitive_complexity)] impl CredHandler { + fn maybe_pw_upgrade( + au: &mut AuditScope, + pw: &Password, + who: Uuid, + cleartext: &str, + async_tx: &Sender + ) + { + if pw.requires_upgrade() { + if let Err(_e) = async_tx.send(DelayedAction::PwUpgrade(PasswordUpgrade { + target_uuid: who, + existing_password: cleartext.to_string(), + appid: None, + })) { + ladmin_warning!(au, "unable to queue delayed pwupgrade, continuing ... "); + }; + } + } + + fn validate_anonymous( + au: &mut AuditScope, + creds: &[AuthCredential], + ) -> CredState { + creds.iter().fold( + CredState::Continue(vec![AuthAllowed::Anonymous]), + |acc, cred| { + // There is no "continuation" from this type - we only set it at + // the start assuming there is no values in the iter so we can tell + // the session to continue up to some timelimit. + match acc { + // If denied, continue returning denied. + CredState::Denied(_) => { + lsecurity!(au, "Handler::Anonymous -> Result::Denied - already denied"); + acc + } + // We have a continue or success, it's important we keep checking here + // after the success, because if they sent "multiple" anonymous or + // they sent anon + password, we need to handle both cases. Double anon + // is okay, but anything else is instant failure, even if we already + // had a success. + _ => { + match cred { + AuthCredential::Anonymous => { + // For anonymous, no claims will ever be issued. + lsecurity!(au, "Handler::Anonymous -> Result::Success"); + CredState::Success(Vec::new()) + } + _ => { + lsecurity!(au, "Handler::Anonymous -> Result::Denied - invalid cred type for handler"); + CredState::Denied(BAD_AUTH_TYPE_MSG) + } + } + } + } // end match acc + }, + ) + } + + fn validate_password( + au: &mut AuditScope, + creds: &[AuthCredential], + pw: &mut Password, + who: Uuid, + async_tx: &Sender, + ) -> CredState { + creds.iter().fold( + // If no creds, remind that we want pw ... + CredState::Continue(vec![AuthAllowed::Password]), + |acc, cred| { + match acc { + // If failed, continue to fail. + CredState::Denied(_) => { + lsecurity!(au, "Handler::Password -> Result::Denied - already denied"); + acc + } + _ => { + match cred { + AuthCredential::Password(cleartext) => { + if pw.verify(cleartext.as_str()).unwrap_or(false) { + lsecurity!(au, "Handler::Password -> Result::Success"); + Self::maybe_pw_upgrade(au, pw, who, cleartext.as_str(), async_tx); + CredState::Success(Vec::new()) + } else { + lsecurity!(au, "Handler::Password -> Result::Denied - incorrect password"); + CredState::Denied(BAD_PASSWORD_MSG) + } + } + // All other cases fail. + _ => { + lsecurity!(au, "Handler::Anonymous -> Result::Denied - invalid cred type for handler"); + CredState::Denied(BAD_AUTH_TYPE_MSG) + } + } + } + } // end match acc + }, + ) + } + + fn validate_totp_password( + au: &mut AuditScope, + creds: &[AuthCredential], + ts: &Duration, + pw_totp: &mut CredTotpPw, + who: Uuid, + async_tx: &Sender, + ) -> CredState { + // Set the default reminder to both pw + totp + creds.iter().fold( + // If no creds, remind that we want pw ... + CredState::Continue(vec![AuthAllowed::TOTP, AuthAllowed::Password]), + |acc, cred| { + match acc { + CredState::Denied(_) => { + lsecurity!(au, "Handler::TOTPPassword -> Result::Denied - already denied"); + acc + } + _ => { + match cred { + AuthCredential::Password(cleartext) => { + // if pw -> check + if pw_totp.pw.verify(cleartext.as_str()).unwrap_or(false) { + pw_totp.pw_state = CredVerifyState::Success; + Self::maybe_pw_upgrade(au, &pw_totp.pw, who, cleartext.as_str(), async_tx); + match pw_totp.totp_state { + CredVerifyState::Init => { + // TOTP hasn't been run yet, we need it before + // we indicate the pw status. + lsecurity!(au, "Handler::TOTPPassword -> Result::Continue - TOTP -, password OK"); + CredState::Continue(vec![AuthAllowed::TOTP]) + } + CredVerifyState::Success => { + // The totp is success, and password good, let's go! + lsecurity!(au, "Handler::TOTPPassword -> Result::Success - TOTP OK, password OK"); + CredState::Success(Vec::new()) + } + CredVerifyState::Fail => { + // The totp already failed, send that message now. + // Should be impossible state. + lsecurity!(au, "Handler::TOTPPassword -> Result::Denied - TOTP Fail, password OK"); + CredState::Denied(BAD_TOTP_MSG) + } + } + } else { + pw_totp.pw_state = CredVerifyState::Fail; + match pw_totp.totp_state { + CredVerifyState::Init => { + // TOTP hasn't been run yet, we need it before + // we indicate the pw status. + lsecurity!(au, "Handler::TOTPPassword -> Result::Continue - TOTP -, password Fail"); + CredState::Continue(vec![AuthAllowed::TOTP]) + } + CredVerifyState::Success => { + // The totp is success, but password bad. + lsecurity!(au, "Handler::TOTPPassword -> Result::Denied - TOTP OK, password Fail"); + CredState::Denied(BAD_PASSWORD_MSG) + } + CredVerifyState::Fail => { + // The totp already failed, remind. + // this should be an impossible state. + lsecurity!(au, "Handler::TOTPPassword -> Result::Denied - TOTP Fail, password Fail"); + CredState::Denied(BAD_TOTP_MSG) + } + } + } + } + AuthCredential::TOTP(totp_chal) => { + // if totp -> check + if pw_totp.totp.verify(*totp_chal, ts) { + pw_totp.totp_state = CredVerifyState::Success; + match pw_totp.pw_state { + CredVerifyState::Init => { + lsecurity!(au, "Handler::TOTPPassword -> Result::Continue - TOTP OK, password -"); + CredState::Continue(vec![AuthAllowed::Password]) + } + CredVerifyState::Success => { + lsecurity!(au, "Handler::TOTPPassword -> Result::Success - TOTP OK, password OK"); + CredState::Success(Vec::new()) + } + CredVerifyState::Fail => { + lsecurity!(au, "Handler::TOTPPassword -> Result::Denied - TOTP OK, password Fail"); + CredState::Denied(BAD_PASSWORD_MSG) + } + } + } else { + pw_totp.totp_state = CredVerifyState::Fail; + lsecurity!(au, "Handler::TOTPPassword -> Result::Denied - TOTP Fail, password -"); + CredState::Denied(BAD_TOTP_MSG) + } + } + // All other cases fail. + _ => { + lsecurity!(au, "Handler::TOTPPassword -> Result::Denied - invalid cred type for handler"); + CredState::Denied(BAD_AUTH_TYPE_MSG) + } + } // end match cred + } + } // end match acc + }, + ) // end fold + } // end CredHandler::TOTPPassword + pub fn validate( &mut self, au: &mut AuditScope, creds: &[AuthCredential], ts: &Duration, + who: Uuid, + async_tx: &Sender, ) -> CredState { match self { CredHandler::Denied => { @@ -87,168 +294,9 @@ impl CredHandler { lsecurity!(au, "Handler::Denied -> Result::Denied"); CredState::Denied("authentication denied") } - CredHandler::Anonymous => { - creds.iter().fold( - CredState::Continue(vec![AuthAllowed::Anonymous]), - |acc, cred| { - // There is no "continuation" from this type - we only set it at - // the start assuming there is no values in the iter so we can tell - // the session to continue up to some timelimit. - match acc { - // If denied, continue returning denied. - CredState::Denied(_) => { - lsecurity!(au, "Handler::Anonymous -> Result::Denied - already denied"); - acc - } - // We have a continue or success, it's important we keep checking here - // after the success, because if they sent "multiple" anonymous or - // they sent anon + password, we need to handle both cases. Double anon - // is okay, but anything else is instant failure, even if we already - // had a success. - _ => { - match cred { - AuthCredential::Anonymous => { - // For anonymous, no claims will ever be issued. - lsecurity!(au, "Handler::Anonymous -> Result::Success"); - CredState::Success(Vec::new()) - } - _ => { - lsecurity!(au, "Handler::Anonymous -> Result::Denied - invalid cred type for handler"); - CredState::Denied(BAD_AUTH_TYPE_MSG) - } - } - } - } // end match acc - }, - ) - } // end credhandler::anonymous - CredHandler::Password(pw) => { - creds.iter().fold( - // If no creds, remind that we want pw ... - CredState::Continue(vec![AuthAllowed::Password]), - |acc, cred| { - match acc { - // If failed, continue to fail. - CredState::Denied(_) => { - lsecurity!(au, "Handler::Password -> Result::Denied - already denied"); - acc - } - _ => { - match cred { - AuthCredential::Password(cleartext) => { - if pw.verify(cleartext.as_str()).unwrap_or(false) { - lsecurity!(au, "Handler::Password -> Result::Success"); - CredState::Success(Vec::new()) - } else { - lsecurity!(au, "Handler::Password -> Result::Denied - incorrect password"); - CredState::Denied(BAD_PASSWORD_MSG) - } - } - // All other cases fail. - _ => { - lsecurity!(au, "Handler::Anonymous -> Result::Denied - invalid cred type for handler"); - CredState::Denied(BAD_AUTH_TYPE_MSG) - } - } - } - } // end match acc - }, - ) - } // end credhandler::password - CredHandler::TOTPPassword(pw_totp) => { - // Set the default reminder to both pw + totp - creds.iter().fold( - // If no creds, remind that we want pw ... - CredState::Continue(vec![AuthAllowed::TOTP, AuthAllowed::Password]), - |acc, cred| { - match acc { - CredState::Denied(_) => { - lsecurity!(au, "Handler::TOTPPassword -> Result::Denied - already denied"); - acc - } - _ => { - match cred { - AuthCredential::Password(cleartext) => { - // if pw -> check - if pw_totp.pw.verify(cleartext.as_str()).unwrap_or(false) { - pw_totp.pw_state = CredVerifyState::Success; - match pw_totp.totp_state { - CredVerifyState::Init => { - // TOTP hasn't been run yet, we need it before - // we indicate the pw status. - lsecurity!(au, "Handler::TOTPPassword -> Result::Continue - TOTP -, password OK"); - CredState::Continue(vec![AuthAllowed::TOTP]) - } - CredVerifyState::Success => { - // The totp is success, and password good, let's go! - lsecurity!(au, "Handler::TOTPPassword -> Result::Success - TOTP OK, password OK"); - CredState::Success(Vec::new()) - } - CredVerifyState::Fail => { - // The totp already failed, send that message now. - // Should be impossible state. - lsecurity!(au, "Handler::TOTPPassword -> Result::Denied - TOTP Fail, password OK"); - CredState::Denied(BAD_TOTP_MSG) - } - } - } else { - pw_totp.pw_state = CredVerifyState::Fail; - match pw_totp.totp_state { - CredVerifyState::Init => { - // TOTP hasn't been run yet, we need it before - // we indicate the pw status. - lsecurity!(au, "Handler::TOTPPassword -> Result::Continue - TOTP -, password Fail"); - CredState::Continue(vec![AuthAllowed::TOTP]) - } - CredVerifyState::Success => { - // The totp is success, but password bad. - lsecurity!(au, "Handler::TOTPPassword -> Result::Denied - TOTP OK, password Fail"); - CredState::Denied(BAD_PASSWORD_MSG) - } - CredVerifyState::Fail => { - // The totp already failed, remind. - // this should be an impossible state. - lsecurity!(au, "Handler::TOTPPassword -> Result::Denied - TOTP Fail, password Fail"); - CredState::Denied(BAD_TOTP_MSG) - } - } - } - } - AuthCredential::TOTP(totp_chal) => { - // if totp -> check - if pw_totp.totp.verify(*totp_chal, ts) { - pw_totp.totp_state = CredVerifyState::Success; - match pw_totp.pw_state { - CredVerifyState::Init => { - lsecurity!(au, "Handler::TOTPPassword -> Result::Continue - TOTP OK, password -"); - CredState::Continue(vec![AuthAllowed::Password]) - } - CredVerifyState::Success => { - lsecurity!(au, "Handler::TOTPPassword -> Result::Success - TOTP OK, password OK"); - CredState::Success(Vec::new()) - } - CredVerifyState::Fail => { - lsecurity!(au, "Handler::TOTPPassword -> Result::Denied - TOTP OK, password Fail"); - CredState::Denied(BAD_PASSWORD_MSG) - } - } - } else { - pw_totp.totp_state = CredVerifyState::Fail; - lsecurity!(au, "Handler::TOTPPassword -> Result::Denied - TOTP Fail, password -"); - CredState::Denied(BAD_TOTP_MSG) - } - } - // All other cases fail. - _ => { - lsecurity!(au, "Handler::TOTPPassword -> Result::Denied - invalid cred type for handler"); - CredState::Denied(BAD_AUTH_TYPE_MSG) - } - } // end match cred - } - } // end match acc - }, - ) // end fold - } // end CredHandler::TOTPPassword + CredHandler::Anonymous => Self::validate_anonymous(au, creds), + CredHandler::Password(ref mut pw) => Self::validate_password(au, creds, pw, who, async_tx), + CredHandler::TOTPPassword(ref mut pw_totp) => Self::validate_totp_password(au, creds, ts, pw_totp, who, async_tx), } } @@ -344,6 +392,7 @@ impl AuthSession { au: &mut AuditScope, creds: &[AuthCredential], time: &Duration, + async_tx: &Sender, ) -> Result { if self.finished { return Err(OperationError::InvalidAuthState( @@ -360,7 +409,7 @@ impl AuthSession { return Ok(AuthState::Denied(BAD_CREDENTIALS.to_string())); } - match self.handler.validate(au, creds, time) { + match self.handler.validate(au, creds, time, self.account.uuid, async_tx) { CredState::Success(claims) => { lsecurity!(au, "Successful cred handling"); self.finished = true; @@ -414,6 +463,10 @@ mod tests { }; use kanidm_proto::v1::{AuthAllowed, AuthCredential, AuthState}; use std::time::Duration; + // use async_std::task; + + use tokio::sync::mpsc::unbounded_channel as unbounded; + // , UnboundedSender as Sender, UnboundedReceiver as Receiver}; #[test] fn test_idm_authsession_anonymous_auth_mech() { @@ -422,6 +475,7 @@ mod tests { uuid::Uuid::new_v4(), None, ); + let anon_account = entry_str_to_account!(JSON_ANONYMOUS_V1); let session = AuthSession::new(&mut audit, anon_account, None); @@ -445,6 +499,7 @@ mod tests { ); let anon_account = entry_str_to_account!(JSON_ANONYMOUS_V1); let mut session = AuthSession::new(&mut audit, anon_account, None); + let (async_tx, mut async_rx) = unbounded(); let attempt = vec![ AuthCredential::Anonymous, @@ -453,12 +508,13 @@ mod tests { AuthCredential::Anonymous, AuthCredential::Anonymous, ]; - match session.validate_creds(&mut audit, &attempt, &Duration::from_secs(0)) { + match session.validate_creds(&mut audit, &attempt, &Duration::from_secs(0), &async_tx) { Ok(AuthState::Denied(msg)) => { assert!(msg == BAD_CREDENTIALS); } _ => panic!(), }; + assert!(async_rx.try_recv().is_err()); audit.write_log(); } @@ -499,6 +555,7 @@ mod tests { // now check let mut session = AuthSession::new(&mut audit, account.clone(), None); + let (async_tx, mut async_rx) = unbounded(); let auth_mechs = session.valid_auth_mechs(); assert!( @@ -509,17 +566,18 @@ mod tests { ); let attempt = vec![AuthCredential::Password("bad_password".to_string())]; - match session.validate_creds(&mut audit, &attempt, &Duration::from_secs(0)) { + match session.validate_creds(&mut audit, &attempt, &Duration::from_secs(0), &async_tx) { Ok(AuthState::Denied(_)) => {} _ => panic!(), }; let mut session = AuthSession::new(&mut audit, account, None); let attempt = vec![AuthCredential::Password("test_password".to_string())]; - match session.validate_creds(&mut audit, &attempt, &Duration::from_secs(0)) { + match session.validate_creds(&mut audit, &attempt, &Duration::from_secs(0), &async_tx) { Ok(AuthState::Success(_)) => {} _ => panic!(), }; + assert!(async_rx.try_recv().is_err()); audit.write_log(); } @@ -560,6 +618,7 @@ mod tests { // now check let session = AuthSession::new(&mut audit, account.clone(), None); + let (async_tx, mut async_rx) = unbounded(); let auth_mechs = session.valid_auth_mechs(); assert!(auth_mechs.iter().fold(true, |acc, x| match x { AuthAllowed::Password => acc, @@ -572,7 +631,7 @@ mod tests { // check send anon (fail) { let mut session = AuthSession::new(&mut audit, account.clone(), None); - match session.validate_creds(&mut audit, &vec![AuthCredential::Anonymous], &ts) { + match session.validate_creds(&mut audit, &vec![AuthCredential::Anonymous], &ts, &async_tx) { Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG), _ => panic!(), }; @@ -588,11 +647,12 @@ mod tests { &mut audit, &vec![AuthCredential::Password(pw_bad.to_string())], &ts, + &async_tx ) { Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::TOTP]), _ => panic!(), }; - match session.validate_creds(&mut audit, &vec![AuthCredential::TOTP(totp_good)], &ts) { + match session.validate_creds(&mut audit, &vec![AuthCredential::TOTP(totp_good)], &ts, &async_tx) { Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG), _ => panic!(), }; @@ -605,11 +665,12 @@ mod tests { &mut audit, &vec![AuthCredential::Password(pw_bad.to_string())], &ts, + &async_tx ) { Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::TOTP]), _ => panic!(), }; - match session.validate_creds(&mut audit, &vec![AuthCredential::TOTP(totp_bad)], &ts) { + match session.validate_creds(&mut audit, &vec![AuthCredential::TOTP(totp_bad)], &ts, &async_tx) { Ok(AuthState::Denied(msg)) => assert!(msg == BAD_TOTP_MSG), _ => panic!(), }; @@ -623,11 +684,12 @@ mod tests { &mut audit, &vec![AuthCredential::Password(pw_good.to_string())], &ts, + &async_tx ) { Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::TOTP]), _ => panic!(), }; - match session.validate_creds(&mut audit, &vec![AuthCredential::TOTP(totp_good)], &ts) { + match session.validate_creds(&mut audit, &vec![AuthCredential::TOTP(totp_good)], &ts, &async_tx) { Ok(AuthState::Success(_)) => {} _ => panic!(), }; @@ -641,11 +703,12 @@ mod tests { &mut audit, &vec![AuthCredential::Password(pw_good.to_string())], &ts, + &async_tx ) { Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::TOTP]), _ => panic!(), }; - match session.validate_creds(&mut audit, &vec![AuthCredential::TOTP(totp_bad)], &ts) { + match session.validate_creds(&mut audit, &vec![AuthCredential::TOTP(totp_bad)], &ts, &async_tx) { Ok(AuthState::Denied(msg)) => assert!(msg == BAD_TOTP_MSG), _ => panic!(), }; @@ -654,7 +717,7 @@ mod tests { // check send bad totp, should fail immediate { let mut session = AuthSession::new(&mut audit, account.clone(), None); - match session.validate_creds(&mut audit, &vec![AuthCredential::TOTP(totp_bad)], &ts) { + match session.validate_creds(&mut audit, &vec![AuthCredential::TOTP(totp_bad)], &ts, &async_tx) { Ok(AuthState::Denied(msg)) => assert!(msg == BAD_TOTP_MSG), _ => panic!(), }; @@ -664,7 +727,7 @@ mod tests { // then bad pw, fail pw { let mut session = AuthSession::new(&mut audit, account.clone(), None); - match session.validate_creds(&mut audit, &vec![AuthCredential::TOTP(totp_good)], &ts) { + match session.validate_creds(&mut audit, &vec![AuthCredential::TOTP(totp_good)], &ts, &async_tx) { Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]), _ => panic!(), }; @@ -672,6 +735,7 @@ mod tests { &mut audit, &vec![AuthCredential::Password(pw_bad.to_string())], &ts, + &async_tx ) { Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG), _ => panic!(), @@ -682,7 +746,7 @@ mod tests { // then good pw, success { let mut session = AuthSession::new(&mut audit, account.clone(), None); - match session.validate_creds(&mut audit, &vec![AuthCredential::TOTP(totp_good)], &ts) { + match session.validate_creds(&mut audit, &vec![AuthCredential::TOTP(totp_good)], &ts, &async_tx) { Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]), _ => panic!(), }; @@ -690,6 +754,7 @@ mod tests { &mut audit, &vec![AuthCredential::Password(pw_good.to_string())], &ts, + &async_tx ) { Ok(AuthState::Success(_)) => {} _ => panic!(), @@ -708,6 +773,7 @@ mod tests { AuthCredential::TOTP(totp_bad), ], &ts, + &async_tx ) { Ok(AuthState::Denied(msg)) => assert!(msg == BAD_TOTP_MSG), _ => panic!(), @@ -723,6 +789,7 @@ mod tests { AuthCredential::Password(pw_bad.to_string()), ], &ts, + &async_tx ) { Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG), _ => panic!(), @@ -738,6 +805,7 @@ mod tests { AuthCredential::Password(pw_good.to_string()), ], &ts, + &async_tx ) { Ok(AuthState::Denied(msg)) => assert!(msg == BAD_TOTP_MSG), _ => panic!(), @@ -753,12 +821,14 @@ mod tests { AuthCredential::Password(pw_good.to_string()), ], &ts, + &async_tx ) { Ok(AuthState::Success(_)) => {} _ => panic!(), }; } + assert!(async_rx.try_recv().is_err()); audit.write_log(); } } diff --git a/kanidmd/src/lib/idm/delayed.rs b/kanidmd/src/lib/idm/delayed.rs new file mode 100644 index 000000000..4e28462f8 --- /dev/null +++ b/kanidmd/src/lib/idm/delayed.rs @@ -0,0 +1,17 @@ +use uuid::Uuid; + +pub(crate) enum DelayedAction { + PwUpgrade(PasswordUpgrade), + UnixPwUpgrade(UnixPasswordUpgrade), +} + +pub(crate) struct PasswordUpgrade { + pub target_uuid: Uuid, + pub existing_password: String, + pub appid: Option, +} + +pub(crate) struct UnixPasswordUpgrade { + pub target_uuid: Uuid, + pub existing_password: String, +} diff --git a/kanidmd/src/lib/idm/mod.rs b/kanidmd/src/lib/idm/mod.rs index 9f54cb514..fc08f42ec 100644 --- a/kanidmd/src/lib/idm/mod.rs +++ b/kanidmd/src/lib/idm/mod.rs @@ -1,3 +1,4 @@ +pub(crate) mod delayed; pub(crate) mod account; pub(crate) mod authsession; pub(crate) mod claim; diff --git a/kanidmd/src/lib/idm/server.rs b/kanidmd/src/lib/idm/server.rs index 970eeedf3..d742da702 100644 --- a/kanidmd/src/lib/idm/server.rs +++ b/kanidmd/src/lib/idm/server.rs @@ -19,6 +19,8 @@ use crate::server::{QueryServer, QueryServerTransaction, QueryServerWriteTransac use crate::utils::{password_from_random, readable_password_from_random, uuid_from_duration, SID}; use crate::value::PartialValue; +use crate::idm::delayed::{DelayedAction, PasswordUpgrade, UnixPasswordUpgrade}; + use kanidm_proto::v1::AuthState; use kanidm_proto::v1::OperationError; use kanidm_proto::v1::RadiusAuthToken; @@ -27,6 +29,12 @@ use kanidm_proto::v1::SetCredentialResponse; use kanidm_proto::v1::UnixGroupToken; use kanidm_proto::v1::UnixUserToken; +// use crossbeam::channel::{unbounded, Sender, Receiver, TryRecvError}; +use tokio::sync::mpsc::{unbounded_channel as unbounded, UnboundedSender as Sender, UnboundedReceiver as Receiver}; +#[cfg(test)] +use tokio::sync::mpsc::error::TryRecvError; + + use concread::collections::bptree::*; use rand::prelude::*; use std::time::Duration; @@ -45,6 +53,7 @@ pub struct IdmServer { // The configured crypto policy for the IDM server. Later this could be transactional // and loaded from the db similar to access. But today it's just to allow dynamic pbkdf2rounds crypto_policy: CryptoPolicy, + async_tx: Sender, } pub struct IdmServerWriteTransaction<'a> { @@ -55,6 +64,8 @@ pub struct IdmServerWriteTransaction<'a> { pub qs_read: QueryServerReadTransaction<'a>, // thread/server id sid: SID, + // For flagging eventual actions. + async_tx: Sender, } pub struct IdmServerProxyReadTransaction<'a> { @@ -73,9 +84,13 @@ pub struct IdmServerProxyWriteTransaction<'a> { crypto_policy: &'a CryptoPolicy, } +pub struct IdmServerDelayed { + async_rx: Receiver, +} + impl IdmServer { // TODO #59: Make number of authsessions configurable!!! - pub fn new(qs: QueryServer) -> IdmServer { + pub fn new(qs: QueryServer) -> (IdmServer, IdmServerDelayed) { // This is calculated back from: // 500 auths / thread -> 0.002 sec per op // we can then spend up to ~0.001s hashing @@ -84,12 +99,16 @@ impl IdmServer { // overtime, we could increase this as auth parallelism // improves. let crypto_policy = CryptoPolicy::time_target(Duration::from_millis(1)); - IdmServer { + let (async_tx, async_rx) = unbounded(); + (IdmServer { sessions: BptreeMap::new(), mfareg_sessions: BptreeMap::new(), qs, crypto_policy, - } + async_tx + }, IdmServerDelayed { + async_rx + }) } pub fn write(&self) -> IdmServerWriteTransaction { @@ -102,6 +121,7 @@ impl IdmServer { // qs: &self.qs, qs_read: self.qs.read(), sid, + async_tx: self.async_tx.clone(), } } @@ -123,6 +143,47 @@ impl IdmServer { crypto_policy: &self.crypto_policy, } } + + pub(crate) fn delayed_action(&self, + au: &mut AuditScope, + ts: Duration, + da: DelayedAction, + ) -> Result { + let mut pw = self.proxy_write(ts); + pw.process_delayedaction(au, da) + .and_then(|_| { + pw.commit(au) + }) + .map(|()| true) + } +} + +impl IdmServerDelayed { + #[cfg(test)] + pub fn is_empty_or_panic(&mut self) { + assert!(self.async_rx.try_recv().is_err()); + } + + #[cfg(test)] + pub(crate) fn try_recv(&mut self) -> Result { + self.async_rx.try_recv().map_err(|e| + match e { + TryRecvError::Empty => OperationError::InvalidState, + TryRecvError::Closed => OperationError::QueueDisconnected, + } + ) + } + + pub(crate) async fn temp_drop_all(&mut self) { + loop { + match self.async_rx.recv().await { + // Drop it + Some(_) => {}, + // Channel has closed + None => return, + } + } + } } impl<'a> IdmServerWriteTransaction<'a> { @@ -229,7 +290,7 @@ impl<'a> IdmServerWriteTransaction<'a> { // Basically throw them at the auth_session and see what // falls out. auth_session - .validate_creds(au, &creds.creds, &ct) + .validate_creds(au, &creds.creds, &ct, &self.async_tx) .map(|aus| { AuthResult { // Is this right? @@ -263,7 +324,7 @@ impl<'a> IdmServerWriteTransaction<'a> { })?; // Validate the unix_pw - this checks the account/cred lock states. - account.verify_unix_credential(au, uae.cleartext.as_str()) + account.verify_unix_credential(au, uae.cleartext.as_str(), &self.async_tx) } pub fn auth_ldap( @@ -303,7 +364,7 @@ impl<'a> IdmServerWriteTransaction<'a> { let account = UnixUserAccount::try_from_entry_ro(au, &account_entry, &mut self.qs_read)?; if account - .verify_unix_credential(au, lae.cleartext.as_str())? + .verify_unix_credential(au, lae.cleartext.as_str(), &self.async_tx)? .is_some() { // Get the anon uat @@ -797,6 +858,85 @@ impl<'a> IdmServerProxyWriteTransaction<'a> { Ok(next) } + // -- delayed action processing -- + fn process_pwupgrade( + &mut self, + au: &mut AuditScope, + pwu: PasswordUpgrade, + ) -> Result<(), OperationError> { + // get the account + let account = self.target_to_account(au, &pwu.target_uuid)?; + + // check, does the pw still match? + let same = account.check_credential_pw(pwu.existing_password.as_str(), &pwu.appid)?; + + // if yes, gen the pw mod and apply. + if same { + let modlist = account + .gen_password_mod(pwu.existing_password.as_str(), &pwu.appid, self.crypto_policy) + .map_err(|e| { + ladmin_error!(au, "Unable to generate password mod {:?}", e); + e + })?; + + self.qs_write.internal_modify( + au, + &filter_all!(f_eq("uuid", PartialValue::new_uuidr(&pwu.target_uuid))), + &modlist) + } else { + // No action needed, it's probably been changed/updated already. + Ok(()) + } + } + + fn process_unixpwupgrade( + &mut self, + au: &mut AuditScope, + pwu: UnixPasswordUpgrade, + ) -> Result<(), OperationError> { + let account = self + .qs_write + .internal_search_uuid(au, &pwu.target_uuid) + .and_then(|account_entry| { + UnixUserAccount::try_from_entry_rw(au, &account_entry, &mut self.qs_write) + }) + .map_err(|e| { + ladmin_error!(au, "Failed to start unix pw upgrade -> {:?}", e); + e + })?; + + let same = account.check_existing_pw(pwu.existing_password.as_str())?; + + if same { + let modlist = account + .gen_password_mod(pwu.existing_password.as_str(), self.crypto_policy) + .map_err(|e| { + ladmin_error!(au, "Unable to generate password mod {:?}", e); + e + })?; + + self.qs_write.internal_modify( + au, + &filter_all!(f_eq("uuid", PartialValue::new_uuidr(&pwu.target_uuid))), + &modlist) + } else { + Ok(()) + } + } + + fn process_delayedaction( + &mut self, + au: &mut AuditScope, + da: DelayedAction, + ) -> Result<(), OperationError> { + match da { + DelayedAction::PwUpgrade(pwu) => + self.process_pwupgrade(au, pwu), + DelayedAction::UnixPwUpgrade(upwu) => + self.process_unixpwupgrade(au, upwu), + } + } + pub fn commit(self, au: &mut AuditScope) -> Result<(), OperationError> { lperf_trace_segment!(au, "idm::server::IdmServerWriteTransaction::commit", || { self.mfareg_sessions.commit(); @@ -814,7 +954,7 @@ mod tests { }; use crate::credential::policy::CryptoPolicy; use crate::credential::totp::TOTP; - use crate::credential::Credential; + use crate::credential::{Credential, Password}; use crate::entry::{Entry, EntryInit, EntryNew}; use crate::event::{AuthEvent, AuthResult, CreateEvent, ModifyEvent}; use crate::idm::event::{ @@ -824,15 +964,19 @@ mod tests { }; use crate::modify::{Modify, ModifyList}; use crate::value::{PartialValue, Value}; + // use crate::idm::delayed::{PasswordUpgrade, DelayedAction}; + use kanidm_proto::v1::OperationError; use kanidm_proto::v1::SetCredentialResponse; use kanidm_proto::v1::{AuthAllowed, AuthState}; use crate::audit::AuditScope; use crate::idm::server::IdmServer; + // , IdmServerDelayed; use crate::server::QueryServer; use crate::utils::duration_from_epoch_now; use std::time::Duration; + use std::convert::TryFrom; use uuid::Uuid; const TEST_PASSWORD: &'static str = "ntaoeuntnaoeuhraohuercahu😍"; @@ -842,7 +986,8 @@ mod tests { #[test] fn test_idm_anonymous_auth() { - run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| { + run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed, + au: &mut AuditScope| { let sid = { // Start and test anonymous auth. let mut idms_write = idms.write(); @@ -928,7 +1073,7 @@ mod tests { // Test sending anonymous but with no session init. #[test] fn test_idm_anonymous_auth_invalid_states() { - run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| { + run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed, au: &mut AuditScope| { { let mut idms_write = idms.write(); let sid = Uuid::new_v4(); @@ -999,14 +1144,13 @@ mod tests { sessionid } - #[test] - fn test_idm_simple_password_auth() { - run_idm_test!(|qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| { - init_admin_w_password(au, qs, TEST_PASSWORD).expect("Failed to setup admin account"); + fn check_admin_password( + idms: &IdmServer, au: &mut AuditScope, pw: &str + ) { let sid = init_admin_authsession_sid(idms, au); let mut idms_write = idms.write(); - let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD); + let anon_step = AuthEvent::cred_step_password(sid, pw); // Expect success let r2 = idms_write.auth(au, &anon_step, Duration::from_secs(TEST_CURRENT_TIME)); @@ -1036,12 +1180,19 @@ mod tests { }; idms_write.commit(au).expect("Must not fail"); + } + + #[test] + fn test_idm_simple_password_auth() { + run_idm_test!(|qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed, au: &mut AuditScope| { + init_admin_w_password(au, qs, TEST_PASSWORD).expect("Failed to setup admin account"); + check_admin_password(idms, au, TEST_PASSWORD); }) } #[test] fn test_idm_simple_password_spn_auth() { - run_idm_test!(|qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| { + run_idm_test!(|qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed, au: &mut AuditScope| { 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"); @@ -1098,7 +1249,7 @@ mod tests { #[test] fn test_idm_simple_password_invalid() { - run_idm_test!(|qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| { + run_idm_test!(|qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed, au: &mut AuditScope| { init_admin_w_password(au, qs, TEST_PASSWORD).expect("Failed to setup admin account"); let sid = init_admin_authsession_sid(idms, au); let mut idms_write = idms.write(); @@ -1137,7 +1288,7 @@ mod tests { #[test] fn test_idm_simple_password_reset() { - run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| { + run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed, au: &mut AuditScope| { let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, TEST_PASSWORD, None); let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()); @@ -1149,7 +1300,7 @@ mod tests { #[test] fn test_idm_anonymous_set_password_denied() { - run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| { + run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed, au: &mut AuditScope| { let pce = PasswordChangeEvent::new_internal(&UUID_ANONYMOUS, TEST_PASSWORD, None); let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()); @@ -1160,7 +1311,7 @@ mod tests { #[test] fn test_idm_session_expire() { - run_idm_test!(|qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| { + run_idm_test!(|qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed, au: &mut AuditScope| { init_admin_w_password(au, qs, TEST_PASSWORD).expect("Failed to setup admin account"); let sid = init_admin_authsession_sid(idms, au); let mut idms_write = idms.write(); @@ -1179,7 +1330,7 @@ mod tests { #[test] fn test_idm_regenerate_radius_secret() { - run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| { + run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed, au: &mut AuditScope| { let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()); let rrse = RegenerateRadiusSecretEvent::new_internal(UUID_ADMIN.clone()); @@ -1197,7 +1348,7 @@ mod tests { #[test] fn test_idm_radiusauthtoken() { - run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| { + run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed, au: &mut AuditScope| { let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()); let rrse = RegenerateRadiusSecretEvent::new_internal(UUID_ADMIN.clone()); let r1 = idms_prox_write @@ -1218,7 +1369,7 @@ mod tests { #[test] fn test_idm_simple_password_reject_weak() { - run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| { + run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed, au: &mut AuditScope| { // len check let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()); @@ -1251,7 +1402,7 @@ mod tests { #[test] fn test_idm_unixusertoken() { - run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| { + run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed, au: &mut AuditScope| { let idms_prox_write = idms.proxy_write(duration_from_epoch_now()); // Modify admin to have posixaccount let me_posix = unsafe { @@ -1323,7 +1474,7 @@ mod tests { #[test] fn test_idm_simple_unix_password_reset() { - run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| { + run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed, au: &mut AuditScope| { let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()); // make the admin a valid posix account let me_posix = unsafe { @@ -1385,7 +1536,7 @@ mod tests { #[test] fn test_idm_totp_registration() { - run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| { + run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed, au: &mut AuditScope| { let ct = duration_from_epoch_now(); let expire = Duration::from_secs(ct.as_secs() + MFAREG_SESSION_TIMEOUT + 2); let mut idms_prox_write = idms.proxy_write(ct.clone()); @@ -1513,4 +1664,92 @@ mod tests { assert!(idms_prox_write.commit(au).is_ok()); }) } + + #[test] + fn test_idm_simple_password_upgrade() { + run_idm_test!(|qs: &QueryServer, idms: &IdmServer, idms_delayed: &mut IdmServerDelayed, au: &mut AuditScope| { + // Assert the delayed action queue is empty + idms_delayed.is_empty_or_panic(); + // Setup the admin w_ an imported password. + { + let qs_write = qs.write(duration_from_epoch_now()); + // now modify and provide a primary credential. + let me_inv_m = unsafe { + ModifyEvent::new_internal_invalid( + filter!(f_eq("name", PartialValue::new_iname("admin"))), + ModifyList::new_list(vec![Modify::Present( + "password_import".to_string(), + Value::from("{SSHA512}JwrSUHkI7FTAfHRVR6KoFlSN0E3dmaQWARjZ+/UsShYlENOqDtFVU77HJLLrY2MuSp0jve52+pwtdVl2QUAHukQ0XUf5LDtM") + )]), + ) + }; + // go! + assert!(qs_write.modify(au, &me_inv_m).is_ok()); + qs_write.commit(au).expect("failed to commit"); + } + // Still empty + idms_delayed.is_empty_or_panic(); + // Do an auth, this will trigger the action to send. + check_admin_password(idms, au, "password"); + // process it. + let da = idms_delayed.try_recv().expect("invalid"); + assert!(Ok(true) == idms.delayed_action(au, duration_from_epoch_now(), da)); + // Check the admin pw still matches + check_admin_password(idms, au, "password"); + // No delayed action was queued. + idms_delayed.is_empty_or_panic(); + }) + } + + #[test] + fn test_idm_unix_password_upgrade() { + run_idm_test!(|qs: &QueryServer, idms: &IdmServer, idms_delayed: &mut IdmServerDelayed, au: &mut AuditScope| { + // Assert the delayed action queue is empty + idms_delayed.is_empty_or_panic(); + // Setup the admin with an imported unix pw. + let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()); + + let im_pw = "{SSHA512}JwrSUHkI7FTAfHRVR6KoFlSN0E3dmaQWARjZ+/UsShYlENOqDtFVU77HJLLrY2MuSp0jve52+pwtdVl2QUAHukQ0XUf5LDtM"; + let pw = Password::try_from(im_pw).expect("failed to parse"); + let cred = Credential::new_from_password(pw); + let v_cred = Value::new_credential("unix", cred); + + let me_posix = unsafe { + ModifyEvent::new_internal_invalid( + filter!(f_eq("name", PartialValue::new_iname("admin"))), + ModifyList::new_list(vec![ + Modify::Present("class".to_string(), Value::new_class("posixaccount")), + Modify::Present("gidnumber".to_string(), Value::new_uint32(2001)), + Modify::Present("unix_password".to_string(), v_cred), + ]), + ) + }; + assert!(idms_prox_write.qs_write.modify(au, &me_posix).is_ok()); + assert!(idms_prox_write.commit(au).is_ok()); + idms_delayed.is_empty_or_panic(); + // Get the auth ready. + let uuae = UnixUserAuthEvent::new_internal(&UUID_ADMIN, "password"); + let mut idms_write = idms.write(); + let a1 = idms_write.auth_unix(au, &uuae, Duration::from_secs(TEST_CURRENT_TIME)); + match a1 { + Ok(Some(_tok)) => {} + _ => assert!(false), + }; + idms_write.commit(au).expect("Must not fail"); + // The upgrade was queued + // Process it. + let da = idms_delayed.try_recv().expect("invalid"); + assert!(Ok(true) == idms.delayed_action(au, duration_from_epoch_now(), da)); + // Go again + let mut idms_write = idms.write(); + let a2 = idms_write.auth_unix(au, &uuae, Duration::from_secs(TEST_CURRENT_TIME)); + match a2 { + Ok(Some(_tok)) => {} + _ => assert!(false), + }; + idms_write.commit(au).expect("Must not fail"); + // No delayed action was queued. + idms_delayed.is_empty_or_panic(); + }) + } } diff --git a/kanidmd/src/lib/idm/unix.rs b/kanidmd/src/lib/idm/unix.rs index ded98dcc5..4e7e868f2 100644 --- a/kanidmd/src/lib/idm/unix.rs +++ b/kanidmd/src/lib/idm/unix.rs @@ -13,6 +13,11 @@ use crate::value::{PartialValue, Value}; use kanidm_proto::v1::OperationError; use kanidm_proto::v1::{UnixGroupToken, UnixUserToken}; +use crate::idm::delayed::{DelayedAction, UnixPasswordUpgrade}; + +// use crossbeam::channel::Sender; +use tokio::sync::mpsc::UnboundedSender as Sender; + use std::iter; #[derive(Debug, Clone)] @@ -165,6 +170,7 @@ impl UnixUserAccount { &self, au: &mut AuditScope, cleartext: &str, + async_tx: &Sender, ) -> Result, OperationError> { // TODO #59: Is the cred locked? // is the cred some or none? @@ -173,6 +179,18 @@ impl UnixUserAccount { Some(pw) => { if pw.verify(cleartext)? { lsecurity!(au, "Successful unix cred handling"); + if pw.requires_upgrade() { + async_tx.send( + DelayedAction::UnixPwUpgrade(UnixPasswordUpgrade { + target_uuid: self.uuid, + existing_password: cleartext.to_string(), + }) + ).map_err(|_| { + ladmin_error!(au, "failed to queue delayed action - unix password upgrade"); + OperationError::InvalidState + })?; + } + Some(self.to_unixusertoken()).transpose() } else { // Failed to auth @@ -195,6 +213,25 @@ impl UnixUserAccount { } } } + + pub(crate) fn check_existing_pw( + &self, + cleartext: &str, + ) -> Result { + match &self.cred { + Some(cred) => match &cred.password { + Some(pw) => { + pw.verify(cleartext) + } + None => { + Err(OperationError::InvalidState) + } + } + None => { + Err(OperationError::InvalidState) + } + } + } } #[derive(Debug, Clone)] diff --git a/kanidmd/src/lib/ldap.rs b/kanidmd/src/lib/ldap.rs index 32dde37f5..99b18e8ea 100644 --- a/kanidmd/src/lib/ldap.rs +++ b/kanidmd/src/lib/ldap.rs @@ -490,7 +490,7 @@ mod tests { #[test] fn test_ldap_simple_bind() { - run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| { + run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed, au: &mut AuditScope| { let ldaps = LdapServer::new(au, idms).expect("failed to start ldap"); let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()); diff --git a/kanidmd/src/lib/macros.rs b/kanidmd/src/lib/macros.rs index 6e5d73c2c..21626b6c1 100644 --- a/kanidmd/src/lib/macros.rs +++ b/kanidmd/src/lib/macros.rs @@ -117,7 +117,7 @@ macro_rules! run_idm_test { ($test_fn:expr) => {{ use crate::audit::AuditScope; use crate::be::{Backend, FsType}; - use crate::idm::server::IdmServer; + use crate::idm::server::{IdmServer, IdmServerDelayed}; use crate::schema::Schema; use crate::server::QueryServer; use crate::utils::duration_from_epoch_now; @@ -145,12 +145,13 @@ macro_rules! run_idm_test { .initialise_helper(&mut audit, duration_from_epoch_now()) .expect("init failed"); - let test_idm_server = IdmServer::new(test_server.clone()); + let (test_idm_server, mut idms_delayed) = IdmServer::new(test_server.clone()); - $test_fn(&test_server, &test_idm_server, &mut audit); + $test_fn(&test_server, &test_idm_server, &mut idms_delayed, &mut audit); // Any needed teardown? // Make sure there are no errors. assert!(test_server.verify(&mut audit).len() == 0); + idms_delayed.is_empty_or_panic(); audit.write_log(); }}; }