On login pw upgrade (#315)

Fixes #295 on login pw upgrade. This adds support for SSHA512 to be imported at the request of @colbyprior, and adds a delayed action queue so that events can be run after-the-fact so that authentication does not need to exist under the write path.
This commit is contained in:
Firstyear 2020-08-24 12:15:21 +10:00 committed by GitHub
parent e6e89ed13a
commit dbfe87e675
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 889 additions and 422 deletions

403
Cargo.lock generated
View file

@ -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",

9
GETTING_STARTED.md Normal file
View file

@ -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.

View file

@ -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

View file

@ -102,6 +102,7 @@ pub enum OperationError {
PasswordBadListed,
CryptographyError,
ResourceLimit,
QueueDisconnected,
}
impl PartialEq for OperationError {

View file

@ -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"

View file

@ -11,6 +11,7 @@ pub struct DbCidV1 {
#[derive(Serialize, Deserialize, Debug)]
pub enum DbPasswordV1 {
PBKDF2(usize, Vec<u8>, Vec<u8>),
SSHA512(Vec<u8>, Vec<u8>),
}
#[derive(Serialize, Deserialize, Debug)]

View file

@ -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<ServerCtx, ()>
}
};
// 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<ServerCtx, ()>
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();

View file

@ -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<u8>, Vec<u8>),
// salt hash
SSHA512(Vec<u8>, Vec<u8>),
}
#[derive(Clone, Debug)]
@ -53,6 +59,9 @@ impl TryFrom<DbPasswordV1> 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));
}
}

View file

@ -200,6 +200,31 @@ impl Account {
}
}
pub(crate) fn check_credential_pw(
&self,
cleartext: &str,
appid: &Option<String>,
) -> Result<bool, OperationError> {
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,

View file

@ -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,22 +77,30 @@ impl TryFrom<&Credential> for CredHandler {
}
}
// TODO: Can this be improved?
#[allow(clippy::cognitive_complexity)]
impl CredHandler {
pub fn validate(
&mut self,
fn maybe_pw_upgrade(
au: &mut AuditScope,
pw: &Password,
who: Uuid,
cleartext: &str,
async_tx: &Sender<DelayedAction>
)
{
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],
ts: &Duration,
) -> CredState {
match self {
CredHandler::Denied => {
// Sad trombone.
lsecurity!(au, "Handler::Denied -> Result::Denied");
CredState::Denied("authentication denied")
}
CredHandler::Anonymous => {
creds.iter().fold(
CredState::Continue(vec![AuthAllowed::Anonymous]),
|acc, cred| {
@ -121,8 +134,15 @@ impl CredHandler {
} // end match acc
},
)
} // end credhandler::anonymous
CredHandler::Password(pw) => {
}
fn validate_password(
au: &mut AuditScope,
creds: &[AuthCredential],
pw: &mut Password,
who: Uuid,
async_tx: &Sender<DelayedAction>,
) -> CredState {
creds.iter().fold(
// If no creds, remind that we want pw ...
CredState::Continue(vec![AuthAllowed::Password]),
@ -138,6 +158,7 @@ impl CredHandler {
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");
@ -154,8 +175,16 @@ impl CredHandler {
} // end match acc
},
)
} // end credhandler::password
CredHandler::TOTPPassword(pw_totp) => {
}
fn validate_totp_password(
au: &mut AuditScope,
creds: &[AuthCredential],
ts: &Duration,
pw_totp: &mut CredTotpPw,
who: Uuid,
async_tx: &Sender<DelayedAction>,
) -> CredState {
// Set the default reminder to both pw + totp
creds.iter().fold(
// If no creds, remind that we want pw ...
@ -172,6 +201,7 @@ impl CredHandler {
// 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
@ -249,6 +279,24 @@ impl CredHandler {
},
) // end fold
} // end CredHandler::TOTPPassword
pub fn validate(
&mut self,
au: &mut AuditScope,
creds: &[AuthCredential],
ts: &Duration,
who: Uuid,
async_tx: &Sender<DelayedAction>,
) -> CredState {
match self {
CredHandler::Denied => {
// Sad trombone.
lsecurity!(au, "Handler::Denied -> Result::Denied");
CredState::Denied("authentication denied")
}
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<DelayedAction>,
) -> Result<AuthState, OperationError> {
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();
}
}

View file

@ -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<String>,
}
pub(crate) struct UnixPasswordUpgrade {
pub target_uuid: Uuid,
pub existing_password: String,
}

View file

@ -1,3 +1,4 @@
pub(crate) mod delayed;
pub(crate) mod account;
pub(crate) mod authsession;
pub(crate) mod claim;

View file

@ -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<DelayedAction>,
}
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<DelayedAction>,
}
pub struct IdmServerProxyReadTransaction<'a> {
@ -73,9 +84,13 @@ pub struct IdmServerProxyWriteTransaction<'a> {
crypto_policy: &'a CryptoPolicy,
}
pub struct IdmServerDelayed {
async_rx: Receiver<DelayedAction>,
}
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<bool, OperationError> {
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<DelayedAction, OperationError> {
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();
})
}
}

View file

@ -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<DelayedAction>,
) -> Result<Option<UnixUserToken>, 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<bool, OperationError> {
match &self.cred {
Some(cred) => match &cred.password {
Some(pw) => {
pw.verify(cleartext)
}
None => {
Err(OperationError::InvalidState)
}
}
None => {
Err(OperationError::InvalidState)
}
}
}
}
#[derive(Debug, Clone)]

View file

@ -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());

View file

@ -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();
}};
}