diff --git a/Cargo.lock b/Cargo.lock index 511524075..51d4ce7db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -113,9 +113,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.97" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "arc-swap" @@ -188,7 +188,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0" dependencies = [ - "nom", + "nom 7.1.3", ] [[package]] @@ -200,7 +200,7 @@ dependencies = [ "asn1-rs-derive", "asn1-rs-impl", "displaydoc", - "nom", + "nom 7.1.3", "num-traits", "rusticata-macros", "thiserror 1.0.69", @@ -536,7 +536,7 @@ dependencies = [ "bitflags 2.9.0", "cexpr", "clang-sys", - "itertools 0.10.5", + "itertools 0.13.0", "log", "prettyplease", "proc-macro2", @@ -654,6 +654,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.10.1" @@ -675,7 +681,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ - "nom", + "nom 7.1.3", ] [[package]] @@ -690,12 +696,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" -[[package]] -name = "checked_int_cast" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cc5e6b5ab06331c33589842070416baa137e8b0eb912b008cfd4a78ada7919" - [[package]] name = "chrono" version = "0.4.40" @@ -724,9 +724,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.34" +version = "4.5.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e958897981290da2a852763fe9cdb89cd36977a5d729023127095fa94d95e2ff" +checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" dependencies = [ "clap_builder", "clap_derive", @@ -734,14 +734,14 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.34" +version = "4.5.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83b0f35019843db2160b5bb19ae09b4e6411ac33fc6a712003c33e03090e2489" +checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.11.1", + "strsim", ] [[package]] @@ -818,7 +818,7 @@ dependencies = [ "crossbeam-epoch", "crossbeam-queue", "crossbeam-utils", - "lru", + "lru 0.13.0", "smallvec", "sptr", "tokio", @@ -1051,38 +1051,14 @@ dependencies = [ "whoami", ] -[[package]] -name = "darling" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" -dependencies = [ - "darling_core 0.14.4", - "darling_macro 0.14.4", -] - [[package]] name = "darling" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ - "darling_core 0.20.10", - "darling_macro 0.20.10", -] - -[[package]] -name = "darling_core" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.10.0", - "syn 1.0.109", + "darling_core", + "darling_macro", ] [[package]] @@ -1095,28 +1071,17 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim 0.11.1", + "strsim", "syn 2.0.100", ] -[[package]] -name = "darling_macro" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" -dependencies = [ - "darling_core 0.14.4", - "quote", - "syn 1.0.109", -] - [[package]] name = "darling_macro" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ - "darling_core 0.20.10", + "darling_core", "quote", "syn 2.0.100", ] @@ -1148,7 +1113,7 @@ checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" dependencies = [ "asn1-rs", "displaydoc", - "nom", + "nom 7.1.3", "num-bigint", "num-traits", "rusticata-macros", @@ -1177,33 +1142,33 @@ dependencies = [ [[package]] name = "derive_builder" -version = "0.12.0" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" dependencies = [ "derive_builder_macro", ] [[package]] name = "derive_builder_core" -version = "0.12.0" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" dependencies = [ - "darling 0.14.4", + "darling", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.100", ] [[package]] name = "derive_builder_macro" -version = "0.12.0" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 1.0.109", + "syn 2.0.100", ] [[package]] @@ -1213,7 +1178,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9313f104b590510b46fc01c0a324fc76505c13871454d3c48490468d04c8d395" dependencies = [ "libc", - "nom", + "nom 7.1.3", ] [[package]] @@ -1408,9 +1373,9 @@ dependencies = [ [[package]] name = "fallible-iterator" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "fallible-streaming-iterator" @@ -1420,12 +1385,13 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fancy-regex" -version = "0.11.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" +checksum = "531e46835a22af56d1e3b66f04844bed63158bc094a628bec1d321d9b4c44bf2" dependencies = [ "bit-set 0.5.3", - "regex", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", ] [[package]] @@ -1468,6 +1434,9 @@ name = "faster-hex" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183" +dependencies = [ + "serde", +] [[package]] name = "fastrand" @@ -1761,9 +1730,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "gix" -version = "0.64.0" +version = "0.71.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d78414d29fcc82329080166077e0f7689f4016551fdb334d787c3d040fe2634f" +checksum = "a61e71ec6817fc3c9f12f812682cfe51ee6ea0d2e27e02fc3849c35524617435" dependencies = [ "gix-actor", "gix-commitgraph", @@ -1777,16 +1746,17 @@ dependencies = [ "gix-hash", "gix-hashtable", "gix-lock", - "gix-macros", "gix-object", "gix-odb", "gix-pack", "gix-path", + "gix-protocol", "gix-ref", "gix-refspec", "gix-revision", "gix-revwalk", "gix-sec", + "gix-shallow", "gix-tempfile", "gix-trace", "gix-traverse", @@ -1795,21 +1765,21 @@ dependencies = [ "gix-validate", "once_cell", "smallvec", - "thiserror 1.0.69", + "thiserror 2.0.12", ] [[package]] name = "gix-actor" -version = "0.31.5" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0e454357e34b833cc3a00b6efbbd3dd4d18b24b9fb0c023876ec2645e8aa3f2" +checksum = "f438c87d4028aca4b82f82ba8d8ab1569823cfb3e5bc5fa8456a71678b2a20e7" dependencies = [ "bstr", "gix-date", "gix-utils", "itoa", - "thiserror 1.0.69", - "winnow 0.6.26", + "thiserror 2.0.12", + "winnow 0.7.6", ] [[package]] @@ -1822,24 +1792,36 @@ dependencies = [ ] [[package]] -name = "gix-commitgraph" -version = "0.24.3" +name = "gix-command" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "133b06f67f565836ec0c473e2116a60fb74f80b6435e21d88013ac0e3c60fc78" +checksum = "c0378995847773a697f8e157fe2963ecf3462fe64be05b7b3da000b3b472def8" +dependencies = [ + "bstr", + "gix-path", + "gix-quote", + "gix-trace", + "shell-words", +] + +[[package]] +name = "gix-commitgraph" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "043cbe49b7a7505150db975f3cb7c15833335ac1e26781f615454d9d640a28fe" dependencies = [ "bstr", "gix-chunk", - "gix-features", "gix-hash", "memmap2", - "thiserror 1.0.69", + "thiserror 2.0.12", ] [[package]] name = "gix-config" -version = "0.38.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28f53fd03d1bf09ebcc2c8654f08969439c4556e644ca925f27cf033bc43e658" +checksum = "9c6f830bf746604940261b49abf7f655d2c19cadc9f4142ae9379e3a316e8cfa" dependencies = [ "bstr", "gix-config-value", @@ -1851,16 +1833,16 @@ dependencies = [ "memchr", "once_cell", "smallvec", - "thiserror 1.0.69", + "thiserror 2.0.12", "unicode-bom", - "winnow 0.6.26", + "winnow 0.7.6", ] [[package]] name = "gix-config-value" -version = "0.14.11" +version = "0.14.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11365144ef93082f3403471dbaa94cfe4b5e72743bdb9560719a251d439f4cee" +checksum = "8dc2c844c4cf141884678cabef736fd91dd73068b9146e6f004ba1a0457944b6" dependencies = [ "bitflags 2.9.0", "bstr", @@ -1871,33 +1853,33 @@ dependencies = [ [[package]] name = "gix-date" -version = "0.8.7" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eed6931f21491ee0aeb922751bd7ec97b4b2fe8fbfedcb678e2a2dce5f3b8c0" +checksum = "daa30058ec7d3511fbc229e4f9e696a35abd07ec5b82e635eff864a2726217e4" dependencies = [ "bstr", "itoa", - "thiserror 1.0.69", - "time", + "jiff", + "thiserror 2.0.12", ] [[package]] name = "gix-diff" -version = "0.44.1" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1996d5c8a305b59709467d80617c9fde48d9d75fd1f4179ea970912630886c9d" +checksum = "a2c975dad2afc85e4e233f444d1efbe436c3cdcf3a07173984509c436d00a3f8" dependencies = [ "bstr", "gix-hash", "gix-object", - "thiserror 1.0.69", + "thiserror 2.0.12", ] [[package]] name = "gix-discover" -version = "0.33.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67662731cec3cb31ba3ed2463809493f76d8e5d6c6d245de8b0560438c13450e" +checksum = "f7fb8a4349b854506a3915de18d3341e5f1daa6b489c8affc9ca0d69efe86781" dependencies = [ "bstr", "dunce", @@ -1906,44 +1888,46 @@ dependencies = [ "gix-path", "gix-ref", "gix-sec", - "thiserror 1.0.69", + "thiserror 2.0.12", ] [[package]] name = "gix-features" -version = "0.38.2" +version = "0.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac7045ac9fe5f9c727f38799d002a7ed3583cd777e3322a7c4b43e3cf437dc69" +checksum = "016d6050219458d14520fe22bdfdeb9cb71631dec9bc2724767c983f60109634" dependencies = [ "crc32fast", "flate2", - "gix-hash", + "gix-path", "gix-trace", "gix-utils", "libc", "once_cell", "prodash", - "sha1_smol", - "thiserror 1.0.69", + "thiserror 2.0.12", "walkdir", ] [[package]] name = "gix-fs" -version = "0.11.3" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2bfe6249cfea6d0c0e0990d5226a4cb36f030444ba9e35e0639275db8f98575" +checksum = "951e886120dc5fa8cac053e5e5c89443f12368ca36811b2e43d1539081f9c111" dependencies = [ + "bstr", "fastrand", "gix-features", + "gix-path", "gix-utils", + "thiserror 2.0.12", ] [[package]] name = "gix-glob" -version = "0.16.5" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74908b4bbc0a0a40852737e5d7889f676f081e340d5451a16e5b4c50d592f111" +checksum = "20972499c03473e773a2099e5fd0c695b9b72465837797a51a43391a1635a030" dependencies = [ "bitflags 2.9.0", "bstr", @@ -1953,19 +1937,21 @@ dependencies = [ [[package]] name = "gix-hash" -version = "0.14.2" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93d7df7366121b5018f947a04d37f034717e113dcf9ccd85c34b58e57a74d5e" +checksum = "834e79722063958b03342edaa1e17595cd2939bb2b3306b3225d0815566dcb49" dependencies = [ "faster-hex", - "thiserror 1.0.69", + "gix-features", + "sha1-checked", + "thiserror 2.0.12", ] [[package]] name = "gix-hashtable" -version = "0.5.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ddf80e16f3c19ac06ce415a38b8591993d3f73aede049cb561becb5b3a8e242" +checksum = "f06066d8702a9186dc1fdc1ed751ff2d7e924ceca21cb5d51b8f990c9c2e014a" dependencies = [ "gix-hash", "hashbrown 0.14.5", @@ -1974,70 +1960,62 @@ dependencies = [ [[package]] name = "gix-lock" -version = "14.0.0" +version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bc7fe297f1f4614774989c00ec8b1add59571dc9b024b4c00acb7dedd4e19d" +checksum = "df47b8f11c34520db5541bc5fc9fbc8e4b0bdfcec3736af89ccb1a5728a0126f" dependencies = [ "gix-tempfile", "gix-utils", - "thiserror 1.0.69", -] - -[[package]] -name = "gix-macros" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "999ce923619f88194171a67fb3e6d613653b8d4d6078b529b15a765da0edcc17" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", + "thiserror 2.0.12", ] [[package]] name = "gix-object" -version = "0.42.3" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25da2f46b4e7c2fa7b413ce4dffb87f69eaf89c2057e386491f4c55cadbfe386" +checksum = "4943fcdae6ffc135920c9ea71e0362ed539182924ab7a85dd9dac8d89b0dd69a" dependencies = [ "bstr", "gix-actor", "gix-date", "gix-features", "gix-hash", + "gix-hashtable", + "gix-path", "gix-utils", "gix-validate", "itoa", "smallvec", - "thiserror 1.0.69", - "winnow 0.6.26", + "thiserror 2.0.12", + "winnow 0.7.6", ] [[package]] name = "gix-odb" -version = "0.61.1" +version = "0.68.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20d384fe541d93d8a3bb7d5d5ef210780d6df4f50c4e684ccba32665a5e3bc9b" +checksum = "50306d40dcc982eb6b7593103f066ea6289c7b094cb9db14f3cd2be0b9f5e610" dependencies = [ "arc-swap", "gix-date", "gix-features", "gix-fs", "gix-hash", + "gix-hashtable", "gix-object", "gix-pack", "gix-path", "gix-quote", "parking_lot", "tempfile", - "thiserror 1.0.69", + "thiserror 2.0.12", ] [[package]] name = "gix-pack" -version = "0.51.1" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0594491fffe55df94ba1c111a6566b7f56b3f8d2e1efc750e77d572f5f5229" +checksum = "9b65fffb09393c26624ca408d32cfe8776fb94cd0a5cdf984905e1d2f39779cb" dependencies = [ "clru", "gix-chunk", @@ -2048,14 +2026,26 @@ dependencies = [ "gix-path", "memmap2", "smallvec", - "thiserror 1.0.69", + "thiserror 2.0.12", +] + +[[package]] +name = "gix-packetline" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "123844a70cf4d5352441dc06bab0da8aef61be94ec239cb631e0ba01dc6d3a04" +dependencies = [ + "bstr", + "faster-hex", + "gix-trace", + "thiserror 2.0.12", ] [[package]] name = "gix-path" -version = "0.10.14" +version = "0.10.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c40f12bb65a8299be0cfb90fe718e3be236b7a94b434877012980863a883a99f" +checksum = "f910668e2f6b2a55ff35a1f04df88a1a049f7b868507f4cbeeaa220eaba7be87" dependencies = [ "bstr", "gix-trace", @@ -2065,10 +2055,29 @@ dependencies = [ ] [[package]] -name = "gix-quote" -version = "0.4.15" +name = "gix-protocol" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e49357fccdb0c85c0d3a3292a9f6db32d9b3535959b5471bb9624908f4a066c6" +checksum = "5678ddae1d62880bc30e2200be1b9387af3372e0e88e21f81b4e7f8367355b5a" +dependencies = [ + "bstr", + "gix-date", + "gix-features", + "gix-hash", + "gix-ref", + "gix-shallow", + "gix-transport", + "gix-utils", + "maybe-async", + "thiserror 2.0.12", + "winnow 0.7.6", +] + +[[package]] +name = "gix-quote" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b005c550bf84de3b24aa5e540a23e6146a1c01c7d30470e35d75a12f827f969" dependencies = [ "bstr", "gix-utils", @@ -2077,9 +2086,9 @@ dependencies = [ [[package]] name = "gix-ref" -version = "0.45.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "636e96a0a5562715153fee098c217110c33a6f8218f08f4687ff99afde159bb5" +checksum = "b2e1f7eb6b7ce82d2d19961f74bd637bab3ea79b1bc7bfb23dbefc67b0415d8b" dependencies = [ "gix-actor", "gix-features", @@ -2092,43 +2101,44 @@ dependencies = [ "gix-utils", "gix-validate", "memmap2", - "thiserror 1.0.69", - "winnow 0.6.26", + "thiserror 2.0.12", + "winnow 0.7.6", ] [[package]] name = "gix-refspec" -version = "0.23.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6868f8cd2e62555d1f7c78b784bece43ace40dd2a462daf3b588d5416e603f37" +checksum = "1d8587b21e2264a6e8938d940c5c99662779c13a10741a5737b15fc85c252ffc" dependencies = [ "bstr", "gix-hash", "gix-revision", "gix-validate", "smallvec", - "thiserror 1.0.69", + "thiserror 2.0.12", ] [[package]] name = "gix-revision" -version = "0.27.2" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b13e43c2118c4b0537ddac7d0821ae0dfa90b7b8dbf20c711e153fb749adce" +checksum = "342caa4e158df3020cadf62f656307c3948fe4eacfdf67171d7212811860c3e9" dependencies = [ "bstr", + "gix-commitgraph", "gix-date", "gix-hash", "gix-object", "gix-revwalk", - "thiserror 1.0.69", + "thiserror 2.0.12", ] [[package]] name = "gix-revwalk" -version = "0.13.2" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b030ccaab71af141f537e0225f19b9e74f25fefdba0372246b844491cab43e0" +checksum = "2dc7c3d7e5cdc1ab8d35130106e4af0a4f9f9eca0c81f4312b690780e92bde0d" dependencies = [ "gix-commitgraph", "gix-date", @@ -2136,14 +2146,14 @@ dependencies = [ "gix-hashtable", "gix-object", "smallvec", - "thiserror 1.0.69", + "thiserror 2.0.12", ] [[package]] name = "gix-sec" -version = "0.10.11" +version = "0.10.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d84dae13271f4313f8d60a166bf27e54c968c7c33e2ffd31c48cafe5da649875" +checksum = "47aeb0f13de9ef2f3033f5ff218de30f44db827ac9f1286f9ef050aacddd5888" dependencies = [ "bitflags 2.9.0", "gix-path", @@ -2152,10 +2162,22 @@ dependencies = [ ] [[package]] -name = "gix-tempfile" -version = "14.0.2" +name = "gix-shallow" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046b4927969fa816a150a0cda2e62c80016fe11fb3c3184e4dddf4e542f108aa" +checksum = "cc0598aacfe1d52575a21c9492fee086edbb21e228ec36c819c42ab923f434c3" +dependencies = [ + "bstr", + "gix-hash", + "gix-lock", + "thiserror 2.0.12", +] + +[[package]] +name = "gix-tempfile" +version = "17.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6de439bbb9a5d3550c9c7fab0e16d2d637d120fcbe0dfbc538772a187f099b" dependencies = [ "gix-fs", "libc", @@ -2171,10 +2193,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c396a2036920c69695f760a65e7f2677267ccf483f25046977d87e4cb2665f7" [[package]] -name = "gix-traverse" -version = "0.39.2" +name = "gix-transport" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e499a18c511e71cf4a20413b743b9f5bcf64b3d9e81e9c3c6cd399eae55a8840" +checksum = "b3f68c2870bfca8278389d2484a7f2215b67d0b0cc5277d3c72ad72acf41787e" +dependencies = [ + "bstr", + "gix-command", + "gix-features", + "gix-packetline", + "gix-quote", + "gix-sec", + "gix-url", + "thiserror 2.0.12", +] + +[[package]] +name = "gix-traverse" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c0b049f8bdb61b20016694102f7b507f2e1727e83e9c5e6dad4f7d84ff7384" dependencies = [ "bitflags 2.9.0", "gix-commitgraph", @@ -2184,28 +2222,28 @@ dependencies = [ "gix-object", "gix-revwalk", "smallvec", - "thiserror 1.0.69", + "thiserror 2.0.12", ] [[package]] name = "gix-url" -version = "0.27.5" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd280c5e84fb22e128ed2a053a0daeacb6379469be6a85e3d518a0636e160c89" +checksum = "48dfe23f93f1ddb84977d80bb0dd7aa09d1bf5d5afc0c9b6820cccacc25ae860" dependencies = [ "bstr", "gix-features", "gix-path", - "home", - "thiserror 1.0.69", + "percent-encoding", + "thiserror 2.0.12", "url", ] [[package]] name = "gix-utils" -version = "0.1.14" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff08f24e03ac8916c478c8419d7d3c33393da9bb41fa4c24455d5406aeefd35f" +checksum = "189f8724cf903e7fd57cfe0b7bc209db255cacdcb22c781a022f52c3a774f8d0" dependencies = [ "fastrand", "unicode-normalization", @@ -2213,12 +2251,12 @@ dependencies = [ [[package]] name = "gix-validate" -version = "0.8.5" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c27dd34a49b1addf193c92070bcbf3beaf6e10f16a78544de6372e146a0acf" +checksum = "34b5f1253109da6c79ed7cf6e1e38437080bb6d704c76af14c93e2f255234084" dependencies = [ "bstr", - "thiserror 1.0.69", + "thiserror 2.0.12", ] [[package]] @@ -2271,6 +2309,18 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" +[[package]] +name = "haproxy-protocol" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f61fc527a2f089b57ebc09301b6371bbbff4ce7b547306c17dfa55766655bec6" +dependencies = [ + "hex", + "nom 8.0.0", + "tokio", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -2282,11 +2332,6 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", - "serde", -] [[package]] name = "hashbrown" @@ -2297,15 +2342,16 @@ dependencies = [ "allocator-api2", "equivalent", "foldhash", + "serde", ] [[package]] name = "hashlink" -version = "0.8.4" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown 0.14.5", + "hashbrown 0.15.2", ] [[package]] @@ -2314,6 +2360,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + [[package]] name = "hex" version = "0.4.3" @@ -2712,30 +2764,28 @@ dependencies = [ [[package]] name = "image" -version = "0.23.14" +version = "0.25.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" +checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" dependencies = [ "bytemuck", - "byteorder", + "byteorder-lite", "color_quant", - "num-iter", - "num-rational 0.3.2", + "gif", + "image-webp", "num-traits", + "zune-core", + "zune-jpeg", ] [[package]] -name = "image" -version = "0.24.9" +name = "image-webp" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +checksum = "b77d01e822461baa8409e156015a1d91735549f0f2c17691bd2d996bef238f7f" dependencies = [ - "bytemuck", - "byteorder", - "color_quant", - "gif", - "jpeg-decoder", - "num-traits", + "byteorder-lite", + "quick-error", ] [[package]] @@ -2794,9 +2844,9 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" -version = "0.10.5" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] @@ -2817,10 +2867,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] -name = "jpeg-decoder" -version = "0.3.1" +name = "jiff" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" +checksum = "5a064218214dc6a10fbae5ec5fa888d80c45d611aba169222fc272072bf7aef6" +dependencies = [ + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", + "windows-sys 0.59.0", +] + +[[package]] +name = "jiff-static" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "199b7932d97e325aff3a7030e141eafe7f2c6268e1d1b24859b753a627f45254" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1283705eb0a21404d2bfd6eef2a7593d240bc42a0bdb39db0ad6fa2ec026524" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] [[package]] name = "js-sys" @@ -2981,7 +3066,7 @@ dependencies = [ "md-5", "openssl", "openssl-sys", - "rand 0.8.5", + "rand 0.9.1", "serde", "sha-crypt", "sha2", @@ -3086,7 +3171,7 @@ dependencies = [ "clap_complete", "dialoguer", "futures", - "hashbrown 0.14.5", + "hashbrown 0.15.2", "kanidm-hsm-crypto", "kanidm_build_profiles", "kanidm_client", @@ -3098,7 +3183,7 @@ dependencies = [ "kanidmd_core", "kanidmd_testkit", "libc", - "lru", + "lru 0.14.0", "mimalloc", "notify-debouncer-full", "prctl", @@ -3142,6 +3227,8 @@ dependencies = [ "filetime", "futures", "futures-util", + "haproxy-protocol", + "hashbrown 0.15.2", "hyper 1.6.0", "hyper-util", "kanidm_build_profiles", @@ -3192,10 +3279,10 @@ dependencies = [ "dyn-clone", "fernet", "futures", - "hashbrown 0.14.5", + "hashbrown 0.15.2", "hex", "idlset", - "image 0.24.9", + "image", "itertools 0.14.0", "kanidm_build_profiles", "kanidm_lib_crypto", @@ -3209,7 +3296,7 @@ dependencies = [ "num_enum", "openssl", "openssl-sys", - "rand 0.8.5", + "rand 0.9.1", "regex", "rusqlite", "serde", @@ -3249,6 +3336,10 @@ dependencies = [ "escargot", "fantoccini", "futures", + "hex", + "http-body-util", + "hyper 1.6.0", + "hyper-util", "jsonschema", "kanidm_build_profiles", "kanidm_client", @@ -3294,6 +3385,16 @@ dependencies = [ "libc", ] +[[package]] +name = "lambert_w" +version = "1.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "913e1e36ca541d75f384593fa70bf5a5e9f001f2996bfa7926550d921f83baf6" +dependencies = [ + "num-complex", + "num-traits", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -3313,7 +3414,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2df7f9fd9f64cf8f59e1a4a0753fe7d575a5b38d3d7ac5758dcee9357d83ef0a" dependencies = [ "bytes", - "nom", + "nom 7.1.3", ] [[package]] @@ -3345,7 +3446,7 @@ dependencies = [ "base64 0.21.7", "bytes", "lber", - "nom", + "nom 7.1.3", "peg", "serde", "thiserror 1.0.69", @@ -3356,9 +3457,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.171" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libloading" @@ -3378,9 +3479,9 @@ checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libmimalloc-sys" -version = "0.1.40" +version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07d0e07885d6a754b9c7993f2625187ad694ee985d60f23355ff0e7077261502" +checksum = "ec9d6fac27761dabcd4ee73571cdb06b7022dc99089acbe5435691edffaac0f4" dependencies = [ "cc", "libc", @@ -3409,9 +3510,9 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.25.2" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29f835d03d717946d28b1d1ed632eb6f0e24a299388ee623d0c23118d3e8a7fa" +checksum = "947e6816f7825b2b45027c2c32e7085da9934defa535de4a6a46b10a4d5257fa" dependencies = [ "cc", "pkg-config", @@ -3499,6 +3600,15 @@ dependencies = [ "hashbrown 0.15.2", ] +[[package]] +name = "lru" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f8cc7106155f10bdf99a6f379688f543ad6596a415375b36a59a054ceda1198" +dependencies = [ + "hashbrown 0.15.2", +] + [[package]] name = "malloced" version = "1.3.1" @@ -3522,13 +3632,39 @@ checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "mathru" -version = "0.13.0" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a42bf938e4c9a6ad581cf528d5606eb50c5458ac759ca23719291e2f6499bec" +checksum = "f3df2d16c016b28cd94248072b6cd8106d8abd7184ec90ea09660c4b7fa989f1" dependencies = [ + "lambert_w", + "matrixmultiply", "rand 0.8.5", ] +[[package]] +name = "matrixmultiply" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" +dependencies = [ + "autocfg", + "num_cpus", + "once_cell", + "rawpointer", + "thread-tree", +] + +[[package]] +name = "maybe-async" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "md-5" version = "0.10.6" @@ -3565,9 +3701,9 @@ dependencies = [ [[package]] name = "mimalloc" -version = "0.1.44" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99585191385958383e13f6b822e6b6d8d9cf928e7d286ceb092da92b43c87bc1" +checksum = "995942f432bbb4822a7e9c3faa87a695185b0d09273ba85f097b54f4e458f2af" dependencies = [ "libmimalloc-sys", ] @@ -3678,10 +3814,19 @@ dependencies = [ ] [[package]] -name = "nonempty" -version = "0.8.1" +name = "nom" +version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aeaf4ad7403de93e699c191202f017118df734d3850b01e13a3a8b2e6953d3c9" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "nonempty" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "549e471b99ccaf2f89101bec68f4d244457d5a95a9c3d0672e9564124397741d" dependencies = [ "serde", ] @@ -3755,7 +3900,7 @@ dependencies = [ "num-complex", "num-integer", "num-iter", - "num-rational 0.4.2", + "num-rational", "num-traits", ] @@ -3821,17 +3966,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-rational" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-rational" version = "0.4.2" @@ -3853,24 +3987,34 @@ dependencies = [ ] [[package]] -name = "num_enum" -version = "0.5.11" +name = "num_cpus" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.5.11" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.100", ] [[package]] @@ -4093,14 +4237,14 @@ dependencies = [ "clap", "crossbeam", "csv", - "hashbrown 0.14.5", + "hashbrown 0.15.2", "idlset", "kanidm_build_profiles", "kanidm_client", "mathru", "mimalloc", - "rand 0.8.5", - "rand_chacha 0.3.1", + "rand 0.9.1", + "rand_chacha 0.9.0", "serde", "serde_json", "tokio", @@ -4305,6 +4449,21 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -4347,7 +4506,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", - "toml_edit", + "toml_edit 0.19.15", ] [[package]] @@ -4376,18 +4535,22 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "prodash" -version = "28.0.0" +version = "29.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744a264d26b88a6a7e37cbad97953fa233b94d585236310bcbc88474b4092d79" +checksum = "f04bb108f648884c23b98a0e940ebc2c93c0c3b89f04dbaf7eb8256ce617d1bc" +dependencies = [ + "log", + "parking_lot", +] [[package]] name = "prost" @@ -4430,12 +4593,11 @@ dependencies = [ [[package]] name = "qrcode" -version = "0.12.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16d2f1455f3630c6e5107b4f2b94e74d76dea80736de0981fd27644216cff57f" +checksum = "d68782463e408eb1e668cf6152704bd856c78c5b6417adaee3203d8f4c1fc9ec" dependencies = [ - "checked_int_cast", - "image 0.23.14", + "image", ] [[package]] @@ -4472,7 +4634,7 @@ checksum = "b820744eb4dc9b57a3398183639c511b5a26d2ed702cedd3febaa1393caa22cc" dependencies = [ "bytes", "getrandom 0.3.2", - "rand 0.9.0", + "rand 0.9.1", "ring", "rustc-hash 2.1.1", "rustls", @@ -4526,13 +4688,12 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", - "zerocopy 0.8.24", ] [[package]] @@ -4573,6 +4734,12 @@ dependencies = [ "getrandom 0.3.2", ] +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "redox_syscall" version = "0.5.10" @@ -4613,12 +4780,6 @@ dependencies = [ "syn 2.0.100", ] -[[package]] -name = "reference-counted-singleton" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5daffa8f5ca827e146485577fa9dba9bd9c6921e06e954ab8f6408c10f753086" - [[package]] name = "referencing" version = "0.29.1" @@ -4805,11 +4966,11 @@ checksum = "5d79b4b604167921892e84afbbaad9d5ad74e091bf6c511d9dbfb0593f09fabd" [[package]] name = "rusqlite" -version = "0.28.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01e213bc3ecb39ac32e81e51ebe31fd888a940515173e3a18a35f8c6e896422a" +checksum = "a22715a5d6deef63c637207afbe68d0c72c3f8d0022d7cf9714c442d6157606b" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.0", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -4875,7 +5036,7 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" dependencies = [ - "nom", + "nom 7.1.3", ] [[package]] @@ -5059,16 +5220,16 @@ dependencies = [ [[package]] name = "selinux" -version = "0.4.6" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0139b2436c81305eb6bda33af151851f75bd62783817b25f44daa371119c30b5" +checksum = "e37f432dfe840521abd9a72fefdf88ed7ad0f43bbea7d9d1d3d80383e9f4ad13" dependencies = [ "bitflags 2.9.0", "libc", "once_cell", - "reference-counted-singleton", + "parking_lot", "selinux-sys", - "thiserror 1.0.69", + "thiserror 2.0.12", ] [[package]] @@ -5160,6 +5321,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -5196,7 +5366,7 @@ version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" dependencies = [ - "darling 0.20.10", + "darling", "proc-macro2", "quote", "syn 2.0.100", @@ -5215,10 +5385,25 @@ dependencies = [ ] [[package]] -name = "sha1_smol" -version = "1.0.1" +name = "sha1" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha1-checked" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423" +dependencies = [ + "digest", + "sha1", +] [[package]] name = "sha2" @@ -5248,9 +5433,9 @@ checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" [[package]] name = "shellexpand" -version = "2.1.2" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ccc8076840c4da029af4f87e4e8daeb0fca6b87bbb02e10cb60b791450e11e4" +checksum = "8b1fdf65dd6331831494dd616b30351c38e96e45921a27745cf98490458b90bb" dependencies = [ "dirs", ] @@ -5366,7 +5551,7 @@ checksum = "34285eaade87ba166c4f17c0ae1e35d52659507db81888beae277e962b9e5a02" dependencies = [ "base64 0.21.7", "base64urlsafedata", - "nom", + "nom 7.1.3", "openssl", "serde", "serde_cbor_2", @@ -5379,8 +5564,7 @@ dependencies = [ [[package]] name = "sshkeys" version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45287473d24bf7ad9ebad1aff097ad0424c16cd9430549170c3a67c5b05705bd" +source = "git+https://github.com/Firstyear/rust-sshkeys.git?rev=3a081cbf7480628223bcb96fc8aaa8c19109d007#3a081cbf7480628223bcb96fc8aaa8c19109d007" dependencies = [ "base64 0.22.1", "byteorder", @@ -5400,12 +5584,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "strsim" version = "0.11.1" @@ -5420,9 +5598,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "svg" -version = "0.13.1" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d815ad337e8449d2374d4248448645edfe74e699343dd5719139d93fa87112" +checksum = "94afda9cd163c04f6bee8b4bf2501c91548deae308373c436f36aeff3cf3c4a3" [[package]] name = "syn" @@ -5567,6 +5745,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" +[[package]] +name = "thread-tree" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbd370cb847953a25954d9f63e14824a36113f8c72eecf6eccef5dc4b45d630" +dependencies = [ + "crossbeam-channel", +] + [[package]] name = "thread_local" version = "1.1.8" @@ -5743,11 +5930,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.11" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.24", ] [[package]] @@ -5755,6 +5945,9 @@ name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" @@ -5767,6 +5960,19 @@ dependencies = [ "winnow 0.5.40", ] +[[package]] +name = "toml_edit" +version = "0.22.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +dependencies = [ + "indexmap 2.8.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.7.6", +] + [[package]] name = "tonic" version = "0.12.3" @@ -6343,7 +6549,7 @@ dependencies = [ "bitflags 1.3.2", "futures", "hex", - "nom", + "nom 7.1.3", "num-derive", "num-traits", "openssl", @@ -6388,7 +6594,7 @@ dependencies = [ "compact_jwt", "der-parser", "hex", - "nom", + "nom 7.1.3", "openssl", "rand 0.8.5", "rand_chacha 0.3.1", @@ -6837,6 +7043,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.50.0" @@ -6890,7 +7105,7 @@ dependencies = [ "data-encoding", "der-parser", "lazy_static", - "nom", + "nom 7.1.3", "oid-registry", "rusticata-macros", "thiserror 1.0.69", @@ -7037,17 +7252,33 @@ dependencies = [ ] [[package]] -name = "zxcvbn" -version = "2.2.2" +name = "zune-core" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "103fa851fff70ea29af380e87c25c48ff7faac5c530c70bd0e65366d4e0c94e4" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-jpeg" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028" dependencies = [ + "zune-core", +] + +[[package]] +name = "zxcvbn" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad76e35b00ad53688d6b90c431cabe3cbf51f7a4a154739e04b63004ab1c736c" +dependencies = [ + "chrono", "derive_builder", - "fancy-regex 0.11.0", - "itertools 0.10.5", - "js-sys", + "fancy-regex 0.13.0", + "itertools 0.13.0", "lazy_static", - "quick-error", "regex", "time", + "wasm-bindgen", + "web-sys", ] diff --git a/Cargo.toml b/Cargo.toml index 2503267d6..6d614450e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -118,9 +118,11 @@ codegen-units = 256 # webauthn-rs-proto = { path = "../webauthn-rs/webauthn-rs-proto" } # sshkey-attest = { path = "../webauthn-rs/sshkey-attest" } -# kanidm-hsm-crypto = { path = "../hsm-crypto" } - +# For BSD nss support libnss = { git = "https://github.com/Firstyear/libnss-rs.git", branch = "20250207-freebsd" } +# Allow ssh keys to have comments with spaces. +sshkeys = { git = "https://github.com/Firstyear/rust-sshkeys.git", rev = "3a081cbf7480628223bcb96fc8aaa8c19109d007" } + [workspace.dependencies] kanidmd_core = { path = "./server/core", version = "=1.6.0-dev" } @@ -138,7 +140,7 @@ kanidm_utils_users = { path = "./libs/users", version = "=1.6.0-dev" } scim_proto = { path = "./libs/scim_proto", version = "=1.6.0-dev" } sketching = { path = "./libs/sketching", version = "=1.6.0-dev" } -anyhow = { version = "1.0.95" } +anyhow = { version = "1.0.98" } argon2 = { version = "0.5.3", features = ["alloc"] } askama = { version = "0.12.1", features = ["serde", "with-axum"] } askama_axum = { version = "0.4.0" } @@ -159,7 +161,7 @@ base64 = "^0.22.1" base64urlsafedata = "0.5.1" bitflags = "^2.8.0" bytes = "^1.9.0" -clap = { version = "^4.5.34", features = ["derive", "env"] } +clap = { version = "4.5.37", features = ["derive", "env"] } clap_complete = "^4.5.42" # Forced by saffron/cron chrono = "^0.4.39" @@ -176,16 +178,18 @@ filetime = "^0.2.24" fs4 = "^0.13.0" futures = "^0.3.31" futures-util = { version = "^0.3.30", features = ["sink"] } -gix = { version = "0.64.0", default-features = false } -hashbrown = { version = "0.14.3", features = ["serde", "inline-more", "ahash"] } +gix = { version = "0.71.0", default-features = false } +haproxy-protocol = { version = "0.0.1" } +hashbrown = { version = "0.15.2", features = ["serde", "inline-more"] } hex = "^0.4.3" http = "1.2.0" +http-body-util = "0.1" hyper = { version = "1.5.1", features = [ "full", ] } # hyper full includes client/server/http2 hyper-util = { version = "0.1.10", features = ["server", "tokio"] } idlset = "^0.2.5" -image = { version = "0.24.9", default-features = false, features = [ +image = { version = "0.25.6", default-features = false, features = [ "gif", "jpeg", "webp", @@ -198,16 +202,16 @@ lazy_static = "^1.5.0" ldap3_client = "^0.5.2" ldap3_proto = { version = "^0.5.2", features = ["serde"] } -libc = "^0.2.168" +libc = "0.2.172" libnss = "^0.8.0" libsqlite3-sys = "^0.25.2" lodepng = "3.11.0" -lru = "^0.13.0" -mathru = "^0.13.0" +lru = "0.14.0" +mathru = "0.15.5" md-5 = "0.10.6" -mimalloc = "0.1.43" +mimalloc = "0.1.46" notify-debouncer-full = { version = "0.5" } -num_enum = "^0.5.11" +num_enum = "0.7.3" oauth2_ext = { version = "^4.4.2", package = "oauth2", default-features = false } openssl-sys = "^0.9" openssl = "^0.10.72" @@ -229,11 +233,11 @@ tracing-core = "0.1.33" peg = "0.8" pkg-config = "^0.3.31" prctl = "1.0.0" -proc-macro2 = "1.0.93" -qrcode = "^0.12.0" +proc-macro2 = "1.0.95" +qrcode = "0.14.1" quote = "1" -rand = "^0.8.5" -rand_chacha = "0.3.1" +rand = "0.9.1" +rand_chacha = "0.9.0" regex = "1.11.0" reqwest = { version = "0.12.12", default-features = false, features = [ "cookies", @@ -243,13 +247,13 @@ reqwest = { version = "0.12.12", default-features = false, features = [ "rustls-tls-native-roots", "rustls-tls-native-roots-no-provider", ] } -rusqlite = { version = "^0.28.0", features = ["array", "bundled"] } -rustls = { version = "0.23.21", default-features = false, features = [ +rusqlite = { version = "0.35.0", features = ["array", "bundled"] } +rustls = { version = "0.23.26", default-features = false, features = [ "aws_lc_rs", ] } sd-notify = "^0.4.5" -selinux = "^0.4.6" +selinux = "^0.5.1" serde = "^1.0.217" serde_cbor = { version = "0.12.0-dev", package = "serde_cbor_2" } serde_json = "^1.0.137" @@ -257,13 +261,13 @@ serde_urlencoded = "^0.7.1" serde_with = "3.12.0" sha-crypt = "0.5.0" sha2 = "0.10.8" -shellexpand = "^2.1.2" +shellexpand = "3.1.1" smartstring = "^1.0.1" smolset = "^1.3.1" sshkey-attest = "^0.5.0" sshkeys = "0.3.3" -svg = "0.13.1" -syn = { version = "2.0.96", features = ["full"] } +svg = "0.18.0" +syn = { version = "2.0.100", features = ["full"] } tempfile = "3.15.0" testkit-macros = { path = "./server/testkit-macros" } time = { version = "^0.3.36", features = ["formatting", "local-offset"] } @@ -272,7 +276,7 @@ tokio = "^1.44.2" tokio-openssl = "^0.6.5" tokio-util = "^0.7.13" -toml = "^0.5.11" +toml = "^0.8.20" tracing = { version = "^0.1.41", features = [ "max_level_trace", "release_max_level_debug", @@ -299,6 +303,6 @@ walkdir = "2" x509-cert = "0.2.5" -zxcvbn = "^2.2.2" +zxcvbn = "3.1.0" -nonempty = "0.8.1" +nonempty = "0.11.0" diff --git a/examples/server.toml b/examples/server.toml index 9a41738c5..5a69fbbcc 100644 --- a/examples/server.toml +++ b/examples/server.toml @@ -13,16 +13,6 @@ bindaddress = "[::]:443" # Defaults to "" (disabled) # ldapbindaddress = "[::]:636" # -# HTTPS requests can be reverse proxied by a loadbalancer. -# To preserve the original IP of the caller, these systems -# will often add a header such as "Forwarded" or -# "X-Forwarded-For". If set to true, then this header is -# respected as the "authoritative" source of the IP of the -# connected client. If you are not using a load balancer -# then you should leave this value as default. -# Defaults to false -# trust_x_forward_for = false -# # The path to the kanidm database. db_path = "/var/lib/private/kanidm/kanidm.db" # @@ -86,6 +76,32 @@ domain = "idm.example.com" # origin = "https://idm.example.com" origin = "https://idm.example.com:8443" # + +# HTTPS requests can be reverse proxied by a loadbalancer. +# To preserve the original IP of the caller, these systems +# will often add a header such as "Forwarded" or +# "X-Forwarded-For". Some other proxies can use the PROXY +# protocol v2 header. +# This setting allows configuration of the range of trusted +# IPs which can supply this header information, and which +# format the information is provided in. +# Defaults to "none" (no trusted sources) +# Only one option can be used at a time. +# [http_client_address_info] +# proxy-v2 = ["127.0.0.1"] +# # OR +# x-forward-for = ["127.0.0.1"] + +# LDAPS requests can be reverse proxied by a loadbalancer. +# To preserve the original IP of the caller, these systems +# can add a header such as the PROXY protocol v2 header. +# This setting allows configuration of the range of trusted +# IPs which can supply this header information, and which +# format the information is provided in. +# Defaults to "none" (no trusted sources) +# [ldap_client_address_info] +# proxy-v2 = ["127.0.0.1"] + [online_backup] # The path to the output folder for online backups path = "/var/lib/private/kanidm/backups/" diff --git a/examples/server_container.toml b/examples/server_container.toml index f57923a40..2d706b77d 100644 --- a/examples/server_container.toml +++ b/examples/server_container.toml @@ -13,16 +13,6 @@ bindaddress = "[::]:8443" # Defaults to "" (disabled) # ldapbindaddress = "[::]:3636" # -# HTTPS requests can be reverse proxied by a loadbalancer. -# To preserve the original IP of the caller, these systems -# will often add a header such as "Forwarded" or -# "X-Forwarded-For". If set to true, then this header is -# respected as the "authoritative" source of the IP of the -# connected client. If you are not using a load balancer -# then you should leave this value as default. -# Defaults to false -# trust_x_forward_for = false -# # The path to the kanidm database. db_path = "/data/kanidm.db" # @@ -85,7 +75,32 @@ domain = "idm.example.com" # not consistent, the server WILL refuse to start! # origin = "https://idm.example.com" origin = "https://idm.example.com:8443" -# + +# HTTPS requests can be reverse proxied by a loadbalancer. +# To preserve the original IP of the caller, these systems +# will often add a header such as "Forwarded" or +# "X-Forwarded-For". Some other proxies can use the PROXY +# protocol v2 header. +# This setting allows configuration of the range of trusted +# IPs which can supply this header information, and which +# format the information is provided in. +# Defaults to "none" (no trusted sources) +# Only one option can be used at a time. +# [http_client_address_info] +# proxy-v2 = ["127.0.0.1"] +# # OR +# x-forward-for = ["127.0.0.1"] + +# LDAPS requests can be reverse proxied by a loadbalancer. +# To preserve the original IP of the caller, these systems +# can add a header such as the PROXY protocol v2 header. +# This setting allows configuration of the range of trusted +# IPs which can supply this header information, and which +# format the information is provided in. +# Defaults to "none" (no trusted sources) +# [ldap_client_address_info] +# proxy-v2 = ["127.0.0.1"] + [online_backup] # The path to the output folder for online backups path = "/data/kanidm/backups/" diff --git a/libs/crypto/src/lib.rs b/libs/crypto/src/lib.rs index f95b2ae73..c1417d35b 100644 --- a/libs/crypto/src/lib.rs +++ b/libs/crypto/src/lib.rs @@ -834,9 +834,9 @@ impl TryFrom<&str> for Password { impl Password { fn bench_pbkdf2(pbkdf2_cost: usize) -> Option<Duration> { - let mut rng = rand::thread_rng(); - let salt: Vec<u8> = (0..PBKDF2_SALT_LEN).map(|_| rng.gen()).collect(); - let input: Vec<u8> = (0..PBKDF2_SALT_LEN).map(|_| rng.gen()).collect(); + let mut rng = rand::rng(); + let salt: Vec<u8> = (0..PBKDF2_SALT_LEN).map(|_| rng.random()).collect(); + let input: Vec<u8> = (0..PBKDF2_SALT_LEN).map(|_| rng.random()).collect(); // This is 512 bits of output let mut key: Vec<u8> = (0..PBKDF2_KEY_LEN).map(|_| 0).collect(); @@ -855,9 +855,9 @@ impl Password { } fn bench_argon2id(params: Params) -> Option<Duration> { - let mut rng = rand::thread_rng(); - let salt: Vec<u8> = (0..ARGON2_SALT_LEN).map(|_| rng.gen()).collect(); - let input: Vec<u8> = (0..ARGON2_SALT_LEN).map(|_| rng.gen()).collect(); + let mut rng = rand::rng(); + let salt: Vec<u8> = (0..ARGON2_SALT_LEN).map(|_| rng.random()).collect(); + let input: Vec<u8> = (0..ARGON2_SALT_LEN).map(|_| rng.random()).collect(); let mut key: Vec<u8> = (0..ARGON2_KEY_LEN).map(|_| 0).collect(); let argon = Argon2::new(Algorithm::Argon2id, Version::V0x13, params); @@ -873,8 +873,8 @@ impl Password { pub fn new_pbkdf2(policy: &CryptoPolicy, cleartext: &str) -> Result<Self, CryptoError> { let pbkdf2_cost = policy.pbkdf2_cost; - let mut rng = rand::thread_rng(); - let salt: Vec<u8> = (0..PBKDF2_SALT_LEN).map(|_| rng.gen()).collect(); + let mut rng = rand::rng(); + let salt: Vec<u8> = (0..PBKDF2_SALT_LEN).map(|_| rng.random()).collect(); let mut key: Vec<u8> = (0..PBKDF2_KEY_LEN).map(|_| 0).collect(); pbkdf2_hmac( @@ -897,8 +897,8 @@ impl Password { let argon = Argon2::new(Algorithm::Argon2id, version, policy.argon2id_params.clone()); - let mut rng = rand::thread_rng(); - let salt: Vec<u8> = (0..ARGON2_SALT_LEN).map(|_| rng.gen()).collect(); + let mut rng = rand::rng(); + let salt: Vec<u8> = (0..ARGON2_SALT_LEN).map(|_| rng.random()).collect(); let mut key: Vec<u8> = (0..ARGON2_KEY_LEN).map(|_| 0).collect(); argon @@ -925,8 +925,8 @@ impl Password { let argon = Argon2::new(Algorithm::Argon2id, version, policy.argon2id_params.clone()); - let mut rng = rand::thread_rng(); - let salt: Vec<u8> = (0..ARGON2_SALT_LEN).map(|_| rng.gen()).collect(); + let mut rng = rand::rng(); + let salt: Vec<u8> = (0..ARGON2_SALT_LEN).map(|_| rng.random()).collect(); let mut check_key: Vec<u8> = (0..ARGON2_KEY_LEN).map(|_| 0).collect(); argon diff --git a/libs/profiles/src/lib.rs b/libs/profiles/src/lib.rs index a0714eff3..29b35582a 100644 --- a/libs/profiles/src/lib.rs +++ b/libs/profiles/src/lib.rs @@ -77,7 +77,10 @@ pub fn apply_profile() { .decode(contents) .unwrap_or_else(|_| panic!("Failed to parse profile - {} - {}", profile, contents)); - let profile_cfg: ProfileConfig = toml::from_slice(&data) + let data_str = String::from_utf8(data) + .unwrap_or_else(|_| panic!("Failed to read profile data to UTF-8 string - {}", profile)); + + let profile_cfg: ProfileConfig = toml::from_str(&data_str) .unwrap_or_else(|_| panic!("Failed to parse profile - {} - {}", profile, contents)); // We have to setup for our pkg version to be passed into things correctly diff --git a/server/core/Cargo.toml b/server/core/Cargo.toml index 1c753cb44..89cf3c59a 100644 --- a/server/core/Cargo.toml +++ b/server/core/Cargo.toml @@ -34,6 +34,8 @@ cron = { workspace = true } filetime = { workspace = true } futures = { workspace = true } futures-util = { workspace = true } +haproxy-protocol = { workspace = true, features = ["tokio"] } +hashbrown = { workspace = true } hyper = { workspace = true } hyper-util = { workspace = true } kanidm_proto = { workspace = true } diff --git a/server/core/src/config.rs b/server/core/src/config.rs index ad3d3bd9c..01bf005fd 100644 --- a/server/core/src/config.rs +++ b/server/core/src/config.rs @@ -4,18 +4,18 @@ //! These components should be "per server". Any "per domain" config should be in the system //! or domain entries that are able to be replicated. -use std::fmt::{self, Display}; -use std::fs::File; -use std::io::Read; -use std::path::{Path, PathBuf}; -use std::str::FromStr; - +use hashbrown::HashSet; use kanidm_proto::constants::DEFAULT_SERVER_ADDRESS; use kanidm_proto::internal::FsType; use kanidm_proto::messages::ConsoleOutputMode; - use serde::Deserialize; use sketching::LogLevel; +use std::fmt::{self, Display}; +use std::fs::File; +use std::io::Read; +use std::net::IpAddr; +use std::path::{Path, PathBuf}; +use std::str::FromStr; use url::Url; use crate::repl::config::ReplicationConfiguration; @@ -100,6 +100,111 @@ pub struct TlsConfiguration { pub client_ca: Option<PathBuf>, } +#[derive(Deserialize, Debug, Clone, Default)] +pub enum LdapAddressInfo { + #[default] + None, + #[serde(rename = "proxy-v2")] + ProxyV2(HashSet<IpAddr>), +} + +impl LdapAddressInfo { + pub fn trusted_proxy_v2(&self) -> Option<HashSet<IpAddr>> { + if let Self::ProxyV2(trusted) = self { + Some(trusted.clone()) + } else { + None + } + } +} + +impl Display for LdapAddressInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::None => f.write_str("none"), + Self::ProxyV2(trusted) => { + f.write_str("proxy-v2 [ ")?; + for ip in trusted { + write!(f, "{} ", ip)?; + } + f.write_str("]") + } + } + } +} + +pub(crate) enum AddressSet { + NonContiguousIpSet(HashSet<IpAddr>), + All, +} + +impl AddressSet { + pub(crate) fn contains(&self, ip_addr: &IpAddr) -> bool { + match self { + Self::All => true, + Self::NonContiguousIpSet(range) => range.contains(ip_addr), + } + } +} + +#[derive(Deserialize, Debug, Clone, Default)] +pub enum HttpAddressInfo { + #[default] + None, + #[serde(rename = "x-forward-for")] + XForwardFor(HashSet<IpAddr>), + // IMPORTANT: This is undocumented, and only exists for backwards compat + // with config v1 which has a boolean toggle for this option. + #[serde(rename = "x-forward-for-all-source-trusted")] + XForwardForAllSourcesTrusted, + #[serde(rename = "proxy-v2")] + ProxyV2(HashSet<IpAddr>), +} + +impl HttpAddressInfo { + pub(crate) fn trusted_x_forward_for(&self) -> Option<AddressSet> { + match self { + Self::XForwardForAllSourcesTrusted => Some(AddressSet::All), + Self::XForwardFor(trusted) => Some(AddressSet::NonContiguousIpSet(trusted.clone())), + _ => None, + } + } + + pub(crate) fn trusted_proxy_v2(&self) -> Option<HashSet<IpAddr>> { + if let Self::ProxyV2(trusted) = self { + Some(trusted.clone()) + } else { + None + } + } +} + +impl Display for HttpAddressInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::None => f.write_str("none"), + + Self::XForwardFor(trusted) => { + f.write_str("x-forward-for [ ")?; + for ip in trusted { + write!(f, "{} ", ip)?; + } + f.write_str("]") + } + Self::XForwardForAllSourcesTrusted => { + f.write_str("x-forward-for [ ALL SOURCES TRUSTED ]") + } + Self::ProxyV2(trusted) => { + f.write_str("proxy-v2 [ ")?; + for ip in trusted { + write!(f, "{} ", ip)?; + } + f.write_str("]") + } + } + } +} + /// This is the Server Configuration as read from `server.toml` or environment variables. /// /// Fields noted as "REQUIRED" are required for the server to start, even if they show as optional due to how file parsing works. @@ -217,7 +322,10 @@ pub struct ServerConfigV2 { role: Option<ServerRole>, log_level: Option<LogLevel>, online_backup: Option<OnlineBackup>, - trust_x_forward_for: Option<bool>, + + http_client_address_info: Option<HttpAddressInfo>, + ldap_client_address_info: Option<LdapAddressInfo>, + adminbindpath: Option<String>, thread_count: Option<usize>, maximum_request_size_bytes: Option<usize>, @@ -490,7 +598,10 @@ pub struct Configuration { pub db_fs_type: Option<FsType>, pub db_arc_size: Option<usize>, pub maximum_request: usize, - pub trust_x_forward_for: bool, + + pub http_client_address_info: HttpAddressInfo, + pub ldap_client_address_info: LdapAddressInfo, + pub tls_config: Option<TlsConfiguration>, pub integration_test_config: Option<Box<IntegrationTestConfig>>, pub online_backup: Option<OnlineBackup>, @@ -522,7 +633,8 @@ impl Configuration { db_fs_type: None, db_arc_size: None, maximum_request: 256 * 1024, // 256k - trust_x_forward_for: None, + http_client_address_info: HttpAddressInfo::default(), + ldap_client_address_info: LdapAddressInfo::default(), tls_key: None, tls_chain: None, tls_client_ca: None, @@ -547,7 +659,8 @@ impl Configuration { db_fs_type: None, db_arc_size: None, maximum_request: 256 * 1024, // 256k - trust_x_forward_for: false, + http_client_address_info: HttpAddressInfo::default(), + ldap_client_address_info: LdapAddressInfo::default(), tls_config: None, integration_test_config: None, online_backup: None, @@ -587,7 +700,17 @@ impl fmt::Display for Configuration { None => write!(f, "arcsize: AUTO, "), }?; write!(f, "max request size: {}b, ", self.maximum_request)?; - write!(f, "trust X-Forwarded-For: {}, ", self.trust_x_forward_for)?; + write!( + f, + "http client address info: {}, ", + self.http_client_address_info + )?; + write!( + f, + "ldap client address info: {}, ", + self.ldap_client_address_info + )?; + write!(f, "with TLS: {}, ", self.tls_config.is_some())?; match &self.online_backup { Some(bck) => write!( @@ -642,7 +765,8 @@ pub struct ConfigurationBuilder { db_fs_type: Option<FsType>, db_arc_size: Option<usize>, maximum_request: usize, - trust_x_forward_for: Option<bool>, + http_client_address_info: HttpAddressInfo, + ldap_client_address_info: LdapAddressInfo, tls_key: Option<PathBuf>, tls_chain: Option<PathBuf>, tls_client_ca: Option<PathBuf>, @@ -691,8 +815,8 @@ impl ConfigurationBuilder { self.db_arc_size = env_config.db_arc_size; } - if env_config.trust_x_forward_for.is_some() { - self.trust_x_forward_for = env_config.trust_x_forward_for; + if env_config.trust_x_forward_for == Some(true) { + self.http_client_address_info = HttpAddressInfo::XForwardForAllSourcesTrusted; } if env_config.tls_key.is_some() { @@ -813,8 +937,8 @@ impl ConfigurationBuilder { self.db_arc_size = config.db_arc_size; } - if config.trust_x_forward_for.is_some() { - self.trust_x_forward_for = config.trust_x_forward_for; + if config.trust_x_forward_for == Some(true) { + self.http_client_address_info = HttpAddressInfo::XForwardForAllSourcesTrusted; } if config.online_backup.is_some() { @@ -893,8 +1017,12 @@ impl ConfigurationBuilder { self.db_arc_size = config.db_arc_size; } - if config.trust_x_forward_for.is_some() { - self.trust_x_forward_for = config.trust_x_forward_for; + if let Some(http_client_address_info) = config.http_client_address_info { + self.http_client_address_info = http_client_address_info + } + + if let Some(ldap_client_address_info) = config.ldap_client_address_info { + self.ldap_client_address_info = ldap_client_address_info } if config.online_backup.is_some() { @@ -930,7 +1058,8 @@ impl ConfigurationBuilder { db_fs_type, db_arc_size, maximum_request, - trust_x_forward_for, + http_client_address_info, + ldap_client_address_info, tls_key, tls_chain, tls_client_ca, @@ -986,7 +1115,6 @@ impl ConfigurationBuilder { let adminbindpath = adminbindpath.unwrap_or(env!("KANIDM_SERVER_ADMIN_BIND_PATH").to_string()); let address = bindaddress.unwrap_or(DEFAULT_SERVER_ADDRESS.to_string()); - let trust_x_forward_for = trust_x_forward_for.unwrap_or_default(); let output_mode = output_mode.unwrap_or_default(); let role = role.unwrap_or(ServerRole::WriteReplica); let log_level = log_level.unwrap_or_default(); @@ -1000,7 +1128,8 @@ impl ConfigurationBuilder { db_fs_type, db_arc_size, maximum_request, - trust_x_forward_for, + http_client_address_info, + ldap_client_address_info, tls_config, online_backup, domain, diff --git a/server/core/src/https/extractors/mod.rs b/server/core/src/https/extractors/mod.rs index 4d3fd686f..105b4c680 100644 --- a/server/core/src/https/extractors/mod.rs +++ b/server/core/src/https/extractors/mod.rs @@ -5,7 +5,6 @@ use axum::{ http::{ header::HeaderName, header::AUTHORIZATION as AUTHORISATION, request::Parts, StatusCode, }, - serve::IncomingStream, RequestPartsExt, }; @@ -40,7 +39,8 @@ impl FromRequestParts<ServerState> for TrustedClientIp { state: &ServerState, ) -> Result<Self, Self::Rejection> { let ConnectInfo(ClientConnInfo { - addr, + connection_addr, + client_addr, client_cert: _, }) = parts .extract::<ConnectInfo<ClientConnInfo>>() @@ -53,7 +53,13 @@ impl FromRequestParts<ServerState> for TrustedClientIp { ) })?; - let ip_addr = if state.trust_x_forward_for { + let trust_x_forward_for = state + .trust_x_forward_for_ips + .as_ref() + .map(|range| range.contains(&connection_addr.ip())) + .unwrap_or_default(); + + let ip_addr = if trust_x_forward_for { if let Some(x_forward_for) = parts.headers.get(X_FORWARDED_FOR_HEADER) { // X forward for may be comma separated. let first = x_forward_for @@ -75,10 +81,14 @@ impl FromRequestParts<ServerState> for TrustedClientIp { ) })? } else { - addr.ip() + client_addr.ip() } } else { - addr.ip() + // This can either be the client_addr == connection_addr if there are + // no ip address trust sources, or this is the value as reported by + // proxy protocol header. If the proxy protocol header is used, then + // trust_x_forward_for can never have been true so we catch here. + client_addr.ip() }; Ok(TrustedClientIp(ip_addr)) @@ -97,7 +107,11 @@ impl FromRequestParts<ServerState> for VerifiedClientInformation { parts: &mut Parts, state: &ServerState, ) -> Result<Self, Self::Rejection> { - let ConnectInfo(ClientConnInfo { addr, client_cert }) = parts + let ConnectInfo(ClientConnInfo { + connection_addr, + client_addr, + client_cert, + }) = parts .extract::<ConnectInfo<ClientConnInfo>>() .await .map_err(|_| { @@ -108,7 +122,13 @@ impl FromRequestParts<ServerState> for VerifiedClientInformation { ) })?; - let ip_addr = if state.trust_x_forward_for { + let trust_x_forward_for = state + .trust_x_forward_for_ips + .as_ref() + .map(|range| range.contains(&connection_addr.ip())) + .unwrap_or_default(); + + let ip_addr = if trust_x_forward_for { if let Some(x_forward_for) = parts.headers.get(X_FORWARDED_FOR_HEADER) { // X forward for may be comma separated. let first = x_forward_for @@ -130,10 +150,10 @@ impl FromRequestParts<ServerState> for VerifiedClientInformation { ) })? } else { - addr.ip() + client_addr.ip() } } else { - addr.ip() + client_addr.ip() }; let (basic_authz, bearer_token) = if let Some(header) = parts.headers.get(AUTHORISATION) { @@ -201,30 +221,30 @@ impl FromRequestParts<ServerState> for DomainInfo { #[derive(Debug, Clone)] pub struct ClientConnInfo { - pub addr: SocketAddr, + /// This is the address that is *connected* to Kanidm right now + /// for this operation. + #[allow(dead_code)] + pub connection_addr: SocketAddr, + /// This is the client address as reported by a remote IP source + /// such as x-forward-for or the PROXY protocol header + pub client_addr: SocketAddr, // Only set if the certificate is VALID pub client_cert: Option<ClientCertInfo>, } +// This is the normal way that our extractors get the ip info impl Connected<ClientConnInfo> for ClientConnInfo { fn connect_info(target: ClientConnInfo) -> Self { target } } +// This is only used for plaintext http - in other words, integration tests only. impl Connected<SocketAddr> for ClientConnInfo { - fn connect_info(addr: SocketAddr) -> Self { + fn connect_info(connection_addr: SocketAddr) -> Self { ClientConnInfo { - addr, - client_cert: None, - } - } -} - -impl Connected<IncomingStream<'_>> for ClientConnInfo { - fn connect_info(target: IncomingStream<'_>) -> Self { - ClientConnInfo { - addr: target.remote_addr(), + client_addr: connection_addr, + connection_addr, client_cert: None, } } diff --git a/server/core/src/https/mod.rs b/server/core/src/https/mod.rs index 645f35202..1af317b03 100644 --- a/server/core/src/https/mod.rs +++ b/server/core/src/https/mod.rs @@ -17,9 +17,8 @@ mod views; use self::extractors::ClientConnInfo; use self::javascript::*; use crate::actors::{QueryServerReadV1, QueryServerWriteV1}; -use crate::config::{Configuration, ServerRole}; +use crate::config::{AddressSet, Configuration, ServerRole}; use crate::CoreAction; - use axum::{ body::Body, extract::connect_info::IntoMakeServiceWithConnectInfo, @@ -29,22 +28,28 @@ use axum::{ routing::*, Router, }; - use axum_extra::extract::cookie::CookieJar; use compact_jwt::{error::JwtError, JwsCompact, JwsHs256Signer, JwsVerifier}; use futures::pin_mut; +use haproxy_protocol::{ProxyHdrV2, RemoteAddress}; +use hashbrown::HashSet; use hyper::body::Incoming; use hyper_util::rt::{TokioExecutor, TokioIo}; +use kanidm_lib_crypto::x509_cert::{der::Decode, x509_public_key_s256, Certificate}; use kanidm_proto::{constants::KSESSIONID, internal::COOKIE_AUTH_SESSION_ID}; use kanidmd_lib::{idm::ClientCertInfo, status::StatusActor}; use openssl::ssl::{Ssl, SslAcceptor}; - -use kanidm_lib_crypto::x509_cert::{der::Decode, x509_public_key_s256, Certificate}; - use serde::de::DeserializeOwned; use sketching::*; use std::fmt::Write; +use std::io::ErrorKind; +use std::net::IpAddr; +use std::path::PathBuf; +use std::pin::Pin; +use std::sync::Arc; +use std::{net::SocketAddr, str::FromStr}; use tokio::{ + io::{AsyncRead, AsyncWrite}, net::{TcpListener, TcpStream}, sync::broadcast, sync::mpsc, @@ -56,11 +61,6 @@ use tower_http::{services::ServeDir, trace::TraceLayer}; use url::Url; use uuid::Uuid; -use std::io::ErrorKind; -use std::path::PathBuf; -use std::pin::Pin; -use std::{net::SocketAddr, str::FromStr}; - #[derive(Clone)] pub struct ServerState { pub(crate) status_ref: &'static StatusActor, @@ -68,7 +68,7 @@ pub struct ServerState { pub(crate) qe_r_ref: &'static QueryServerReadV1, // Store the token management parts. pub(crate) jws_signer: JwsHs256Signer, - pub(crate) trust_x_forward_for: bool, + pub(crate) trust_x_forward_for_ips: Option<Arc<AddressSet>>, pub(crate) csp_header: HeaderValue, pub(crate) origin: Url, pub(crate) domain: String, @@ -211,7 +211,15 @@ pub async fn create_https_server( error!(?err, "Unable to generate content security policy"); })?; - let trust_x_forward_for = config.trust_x_forward_for; + let trust_x_forward_for_ips = config + .http_client_address_info + .trusted_x_forward_for() + .map(Arc::new); + + let trusted_proxy_v2_ips = config + .http_client_address_info + .trusted_proxy_v2() + .map(Arc::new); let origin = Url::parse(&config.origin) // Should be impossible! @@ -224,7 +232,7 @@ pub async fn create_https_server( qe_w_ref, qe_r_ref, jws_signer, - trust_x_forward_for, + trust_x_forward_for_ips, csp_header, origin, domain: config.domain.clone(), @@ -321,35 +329,41 @@ pub async fn create_https_server( info!("Starting the web server..."); - match maybe_tls_acceptor { - Some(tls_acceptor) => { - let listener = match TcpListener::bind(addr).await { - Ok(l) => l, - Err(err) => { - error!(?err, "Failed to bind tcp listener"); - return Err(()); - } - }; - Ok(task::spawn(server_loop( - tls_acceptor, - listener, - app, - rx, - server_message_tx, - tls_acceptor_reload_rx, - ))) + let listener = match TcpListener::bind(addr).await { + Ok(l) => l, + Err(err) => { + error!(?err, "Failed to bind tcp listener"); + return Err(()); } - None => Ok(task::spawn(server_loop_plaintext(addr, app, rx))), + }; + + match maybe_tls_acceptor { + Some(tls_acceptor) => Ok(task::spawn(server_tls_loop( + tls_acceptor, + listener, + app, + rx, + server_message_tx, + tls_acceptor_reload_rx, + trusted_proxy_v2_ips, + ))), + None => Ok(task::spawn(server_plaintext_loop( + listener, + app, + rx, + trusted_proxy_v2_ips, + ))), } } -async fn server_loop( +async fn server_tls_loop( mut tls_acceptor: SslAcceptor, listener: TcpListener, app: IntoMakeServiceWithConnectInfo<Router, ClientConnInfo>, mut rx: broadcast::Receiver<CoreAction>, server_message_tx: broadcast::Sender<CoreAction>, mut tls_acceptor_reload_rx: mpsc::Receiver<SslAcceptor>, + trusted_proxy_v2_ips: Option<Arc<HashSet<IpAddr>>>, ) { pin_mut!(listener); @@ -365,7 +379,7 @@ async fn server_loop( Ok((stream, addr)) => { let tls_acceptor = tls_acceptor.clone(); let app = app.clone(); - task::spawn(handle_conn(tls_acceptor, stream, app, addr)); + task::spawn(handle_tls_conn(tls_acceptor, stream, app, addr, trusted_proxy_v2_ips.clone())); } Err(err) => { error!("Web server exited with {:?}", err); @@ -386,24 +400,33 @@ async fn server_loop( info!("Stopped {}", super::TaskName::HttpsServer); } -async fn server_loop_plaintext( - addr: SocketAddr, +async fn server_plaintext_loop( + listener: TcpListener, app: IntoMakeServiceWithConnectInfo<Router, ClientConnInfo>, mut rx: broadcast::Receiver<CoreAction>, + trusted_proxy_v2_ips: Option<Arc<HashSet<IpAddr>>>, ) { - let listener = axum_server::bind(addr).serve(app); - pin_mut!(listener); loop { tokio::select! { Ok(action) = rx.recv() => { match action { - CoreAction::Shutdown => - break, + CoreAction::Shutdown => break, + } + } + accept = listener.accept() => { + match accept { + Ok((stream, addr)) => { + let app = app.clone(); + task::spawn(handle_conn(stream, app, addr, trusted_proxy_v2_ips.clone())); + } + Err(err) => { + error!("Web server exited with {:?}", err); + break; + } } } - _ = &mut listener => {} } } @@ -412,11 +435,38 @@ async fn server_loop_plaintext( /// This handles an individual connection. pub(crate) async fn handle_conn( + stream: TcpStream, + app: IntoMakeServiceWithConnectInfo<Router, ClientConnInfo>, + connection_addr: SocketAddr, + trusted_proxy_v2_ips: Option<Arc<HashSet<IpAddr>>>, +) -> Result<(), std::io::Error> { + let (stream, client_addr) = + process_client_addr(stream, connection_addr, trusted_proxy_v2_ips).await?; + + let client_conn_info = ClientConnInfo { + connection_addr, + client_addr, + client_cert: None, + }; + + // Hyper has its own `AsyncRead` and `AsyncWrite` traits and doesn't use tokio. + // `TokioIo` converts between them. + let stream = TokioIo::new(stream); + + process_client_hyper(stream, app, client_conn_info).await +} + +/// This handles an individual connection. +pub(crate) async fn handle_tls_conn( acceptor: SslAcceptor, stream: TcpStream, - mut app: IntoMakeServiceWithConnectInfo<Router, ClientConnInfo>, - addr: SocketAddr, + app: IntoMakeServiceWithConnectInfo<Router, ClientConnInfo>, + connection_addr: SocketAddr, + trusted_proxy_v2_ips: Option<Arc<HashSet<IpAddr>>>, ) -> Result<(), std::io::Error> { + let (stream, client_addr) = + process_client_addr(stream, connection_addr, trusted_proxy_v2_ips).await?; + let ssl = Ssl::new(acceptor.context()).map_err(|e| { error!("Failed to create TLS context: {:?}", e); std::io::Error::from(ErrorKind::ConnectionAborted) @@ -459,42 +509,17 @@ pub(crate) async fn handle_conn( None }; - let client_conn_info = ClientConnInfo { addr, client_cert }; - - debug!(?client_conn_info); - - let svc = axum_server::service::MakeService::<ClientConnInfo, hyper::Request<Body>>::make_service( - &mut app, - client_conn_info, - ); - - let svc = svc.await.map_err(|e| { - error!("Failed to build HTTP response: {:?}", e); - std::io::Error::from(ErrorKind::Other) - })?; + let client_conn_info = ClientConnInfo { + connection_addr, + client_addr, + client_cert, + }; // Hyper has its own `AsyncRead` and `AsyncWrite` traits and doesn't use tokio. // `TokioIo` converts between them. let stream = TokioIo::new(tls_stream); - // Hyper also has its own `Service` trait and doesn't use tower. We can use - // `hyper::service::service_fn` to create a hyper `Service` that calls our app through - // `tower::Service::call`. - let hyper_service = hyper::service::service_fn(move |request: Request<Incoming>| { - // We have to clone `tower_service` because hyper's `Service` uses `&self` whereas - // tower's `Service` requires `&mut self`. - // - // We don't need to call `poll_ready` since `Router` is always ready. - svc.clone().call(request) - }); - - hyper_util::server::conn::auto::Builder::new(TokioExecutor::new()) - .serve_connection_with_upgrades(stream, hyper_service) - .await - .map_err(|e| { - debug!("Failed to complete connection: {:?}", e); - std::io::Error::from(ErrorKind::ConnectionAborted) - }) + process_client_hyper(stream, app, client_conn_info).await } Err(error) => { trace!("Failed to handle connection: {:?}", error); @@ -502,3 +527,83 @@ pub(crate) async fn handle_conn( } } } + +async fn process_client_addr( + stream: TcpStream, + connection_addr: SocketAddr, + trusted_proxy_v2_ips: Option<Arc<HashSet<IpAddr>>>, +) -> Result<(TcpStream, SocketAddr), std::io::Error> { + let enable_proxy_v2_hdr = trusted_proxy_v2_ips + .map(|trusted| trusted.contains(&connection_addr.ip())) + .unwrap_or_default(); + + let (stream, client_addr) = if enable_proxy_v2_hdr { + match ProxyHdrV2::parse_from_read(stream).await { + Ok((stream, hdr)) => { + let remote_socket_addr = match hdr.to_remote_addr() { + RemoteAddress::Local => { + debug!("PROXY protocol liveness check - will not contain client data"); + return Err(std::io::Error::from(ErrorKind::ConnectionAborted)); + } + RemoteAddress::TcpV4 { src, dst: _ } => SocketAddr::from(src), + RemoteAddress::TcpV6 { src, dst: _ } => SocketAddr::from(src), + remote_addr => { + error!(?remote_addr, "remote address in proxy header is invalid"); + return Err(std::io::Error::from(ErrorKind::ConnectionAborted)); + } + }; + + (stream, remote_socket_addr) + } + Err(err) => { + error!(?connection_addr, ?err, "Unable to process proxy v2 header"); + return Err(std::io::Error::from(ErrorKind::ConnectionAborted)); + } + } + } else { + (stream, connection_addr) + }; + + Ok((stream, client_addr)) +} + +async fn process_client_hyper<T>( + stream: TokioIo<T>, + mut app: IntoMakeServiceWithConnectInfo<Router, ClientConnInfo>, + client_conn_info: ClientConnInfo, +) -> Result<(), std::io::Error> +where + T: AsyncRead + AsyncWrite + std::marker::Unpin + std::marker::Send + 'static, +{ + debug!(?client_conn_info); + + let svc = + axum_server::service::MakeService::<ClientConnInfo, hyper::Request<Body>>::make_service( + &mut app, + client_conn_info, + ); + + let svc = svc.await.map_err(|e| { + error!("Failed to build HTTP response: {:?}", e); + std::io::Error::from(ErrorKind::Other) + })?; + + // Hyper also has its own `Service` trait and doesn't use tower. We can use + // `hyper::service::service_fn` to create a hyper `Service` that calls our app through + // `tower::Service::call`. + let hyper_service = hyper::service::service_fn(move |request: Request<Incoming>| { + // We have to clone `tower_service` because hyper's `Service` uses `&self` whereas + // tower's `Service` requires `&mut self`. + // + // We don't need to call `poll_ready` since `Router` is always ready. + svc.clone().call(request) + }); + + hyper_util::server::conn::auto::Builder::new(TokioExecutor::new()) + .serve_connection_with_upgrades(stream, hyper_service) + .await + .map_err(|e| { + debug!("Failed to complete connection: {:?}", e); + std::io::Error::from(ErrorKind::ConnectionAborted) + }) +} diff --git a/server/core/src/ldaps.rs b/server/core/src/ldaps.rs index ca57a7e1b..9ce9f01b7 100644 --- a/server/core/src/ldaps.rs +++ b/server/core/src/ldaps.rs @@ -2,14 +2,17 @@ use crate::actors::QueryServerReadV1; use crate::CoreAction; use futures_util::sink::SinkExt; use futures_util::stream::StreamExt; +use haproxy_protocol::{ProxyHdrV2, RemoteAddress}; +use hashbrown::HashSet; use kanidmd_lib::idm::ldap::{LdapBoundToken, LdapResponseState}; use kanidmd_lib::prelude::*; use ldap3_proto::proto::LdapMsg; use ldap3_proto::LdapCodec; use openssl::ssl::{Ssl, SslAcceptor}; -use std::net; +use std::net::{IpAddr, SocketAddr}; use std::pin::Pin; use std::str::FromStr; +use std::sync::Arc; use tokio::io::{AsyncRead, AsyncWrite}; use tokio::net::{TcpListener, TcpStream}; use tokio::sync::broadcast; @@ -33,7 +36,7 @@ impl LdapSession { #[instrument(name = "ldap-request", skip(client_address, qe_r_ref))] async fn client_process_msg( uat: Option<LdapBoundToken>, - client_address: net::SocketAddr, + client_address: SocketAddr, protomsg: LdapMsg, qe_r_ref: &'static QueryServerReadV1, ) -> Option<LdapResponseState> { @@ -50,7 +53,8 @@ async fn client_process_msg( async fn client_process<STREAM>( stream: STREAM, - client_address: net::SocketAddr, + client_address: SocketAddr, + connection_address: SocketAddr, qe_r_ref: &'static QueryServerReadV1, ) where STREAM: AsyncRead + AsyncWrite, @@ -67,6 +71,8 @@ async fn client_process<STREAM>( let uat = session.uat.clone(); let caddr = client_address; + debug!(?client_address, ?connection_address); + match client_process_msg(uat, caddr, protomsg, qe_r_ref).await { // I'd really have liked to have put this near the [LdapResponseState::Bind] but due // to the handing of `audit` it isn't possible due to borrows, etc. @@ -112,28 +118,65 @@ async fn client_process<STREAM>( } async fn client_tls_accept( - tcpstream: TcpStream, + stream: TcpStream, tls_acceptor: SslAcceptor, - client_socket_addr: net::SocketAddr, + connection_addr: SocketAddr, qe_r_ref: &'static QueryServerReadV1, + trusted_proxy_v2_ips: Option<Arc<HashSet<IpAddr>>>, ) { + let enable_proxy_v2_hdr = trusted_proxy_v2_ips + .map(|trusted| trusted.contains(&connection_addr.ip())) + .unwrap_or_default(); + + let (stream, client_addr) = if enable_proxy_v2_hdr { + match ProxyHdrV2::parse_from_read(stream).await { + Ok((stream, hdr)) => { + let remote_socket_addr = match hdr.to_remote_addr() { + RemoteAddress::Local => { + debug!("PROXY protocol liveness check - will not contain client data"); + return; + } + RemoteAddress::TcpV4 { src, dst: _ } => SocketAddr::from(src), + RemoteAddress::TcpV6 { src, dst: _ } => SocketAddr::from(src), + remote_addr => { + error!(?remote_addr, "remote address in proxy header is invalid"); + return; + } + }; + + (stream, remote_socket_addr) + } + Err(err) => { + error!(?connection_addr, ?err, "Unable to process proxy v2 header"); + return; + } + } + } else { + (stream, connection_addr) + }; + // Start the event // From the parameters we need to create an SslContext. let mut tlsstream = match Ssl::new(tls_acceptor.context()) - .and_then(|tls_obj| SslStream::new(tls_obj, tcpstream)) + .and_then(|tls_obj| SslStream::new(tls_obj, stream)) { Ok(ta) => ta, Err(err) => { - error!(?err, %client_socket_addr, "LDAP TLS setup error"); + error!(?err, %client_addr, %connection_addr, "LDAP TLS setup error"); return; } }; if let Err(err) = SslStream::accept(Pin::new(&mut tlsstream)).await { - error!(?err, %client_socket_addr, "LDAP TLS accept error"); + error!(?err, %client_addr, %connection_addr, "LDAP TLS accept error"); return; }; - tokio::spawn(client_process(tlsstream, client_socket_addr, qe_r_ref)); + tokio::spawn(client_process( + tlsstream, + client_addr, + connection_addr, + qe_r_ref, + )); } /// TLS LDAP Listener, hands off to [client_tls_accept] @@ -143,6 +186,7 @@ async fn ldap_tls_acceptor( qe_r_ref: &'static QueryServerReadV1, mut rx: broadcast::Receiver<CoreAction>, mut tls_acceptor_reload_rx: mpsc::Receiver<SslAcceptor>, + trusted_proxy_v2_ips: Option<Arc<HashSet<IpAddr>>>, ) { loop { tokio::select! { @@ -155,7 +199,7 @@ async fn ldap_tls_acceptor( match accept_result { Ok((tcpstream, client_socket_addr)) => { let clone_tls_acceptor = tls_acceptor.clone(); - tokio::spawn(client_tls_accept(tcpstream, clone_tls_acceptor, client_socket_addr, qe_r_ref)); + tokio::spawn(client_tls_accept(tcpstream, clone_tls_acceptor, client_socket_addr, qe_r_ref, trusted_proxy_v2_ips.clone())); } Err(err) => { warn!(?err, "LDAP acceptor error, continuing"); @@ -187,7 +231,7 @@ async fn ldap_plaintext_acceptor( accept_result = listener.accept() => { match accept_result { Ok((tcpstream, client_socket_addr)) => { - tokio::spawn(client_process(tcpstream, client_socket_addr, qe_r_ref)); + tokio::spawn(client_process(tcpstream, client_socket_addr, client_socket_addr, qe_r_ref)); } Err(e) => { error!("LDAP acceptor error, continuing -> {:?}", e); @@ -205,6 +249,7 @@ pub(crate) async fn create_ldap_server( qe_r_ref: &'static QueryServerReadV1, rx: broadcast::Receiver<CoreAction>, tls_acceptor_reload_rx: mpsc::Receiver<SslAcceptor>, + trusted_proxy_v2_ips: Option<HashSet<IpAddr>>, ) -> Result<tokio::task::JoinHandle<()>, ()> { if address.starts_with(":::") { // takes :::xxxx to xxxx @@ -212,7 +257,7 @@ pub(crate) async fn create_ldap_server( error!("Address '{}' looks like an attempt to wildcard bind with IPv6 on port {} - please try using ldapbindaddress = '[::]:{}'", address, port, port); }; - let addr = net::SocketAddr::from_str(address).map_err(|e| { + let addr = SocketAddr::from_str(address).map_err(|e| { error!("Could not parse LDAP server address {} -> {:?}", address, e); })?; @@ -223,6 +268,8 @@ pub(crate) async fn create_ldap_server( ); })?; + let trusted_proxy_v2_ips = trusted_proxy_v2_ips.map(Arc::new); + let ldap_acceptor_handle = match opt_ssl_acceptor { Some(ssl_acceptor) => { info!("Starting LDAPS interface ldaps://{} ...", address); @@ -233,6 +280,7 @@ pub(crate) async fn create_ldap_server( qe_r_ref, rx, tls_acceptor_reload_rx, + trusted_proxy_v2_ips, )) } None => tokio::spawn(ldap_plaintext_acceptor(listener, qe_r_ref, rx)), diff --git a/server/core/src/lib.rs b/server/core/src/lib.rs index 1117f446a..392668ba8 100644 --- a/server/core/src/lib.rs +++ b/server/core/src/lib.rs @@ -1087,6 +1087,7 @@ pub async fn create_server_core( server_read_ref, broadcast_tx.subscribe(), ldap_tls_acceptor_reload_rx, + config.ldap_client_address_info.trusted_proxy_v2(), ) .await?; Some(h) diff --git a/server/lib/src/be/mod.rs b/server/lib/src/be/mod.rs index e2ce78acc..e6b68ea18 100644 --- a/server/lib/src/be/mod.rs +++ b/server/lib/src/be/mod.rs @@ -2417,7 +2417,7 @@ mod tests { let lims = Limits::unlimited(); let r = be.search(&lims, &filt); - assert!(r.expect("Search failed!").len() == 0); + assert!(r.expect("Search failed!").is_empty()); }); } diff --git a/server/lib/src/credential/totp.rs b/server/lib/src/credential/totp.rs index f6ae0fd1e..e887f90e9 100644 --- a/server/lib/src/credential/totp.rs +++ b/server/lib/src/credential/totp.rs @@ -145,8 +145,8 @@ impl Totp { // Create a new token with secure key and algo. pub fn generate_secure(step: u64) -> Self { - let mut rng = rand::thread_rng(); - let secret: Vec<u8> = (0..SECRET_SIZE_BYTES).map(|_| rng.gen()).collect(); + let mut rng = rand::rng(); + let secret: Vec<u8> = (0..SECRET_SIZE_BYTES).map(|_| rng.random()).collect(); let algo = TotpAlgo::Sha256; let digits = TotpDigits::Six; Totp { diff --git a/server/lib/src/idm/credupdatesession.rs b/server/lib/src/idm/credupdatesession.rs index 0453fdbdc..c30518d50 100644 --- a/server/lib/src/idm/credupdatesession.rs +++ b/server/lib/src/idm/credupdatesession.rs @@ -17,6 +17,7 @@ use webauthn_rs::prelude::{ AttestedPasskey as AttestedPasskeyV4, AttestedPasskeyRegistration, CreationChallengeResponse, Passkey as PasskeyV4, PasskeyRegistration, RegisterPublicKeyCredential, WebauthnError, }; +use zxcvbn::{zxcvbn, Score}; use crate::credential::totp::{Totp, TOTP_DEFAULT_STEP}; use crate::credential::{BackupCodes, Credential}; @@ -1663,23 +1664,14 @@ impl IdmServerCredUpdateTransaction<'_> { } // does the password pass zxcvbn? - let entropy = zxcvbn::zxcvbn(cleartext, related_inputs).map_err(|e| { - admin_error!("zxcvbn check failure (password empty?) {:?}", e); - // Return some generic feedback when the password is this bad. - PasswordQuality::Feedback(vec![ - PasswordFeedback::UseAFewWordsAvoidCommonPhrases, - PasswordFeedback::AddAnotherWordOrTwo, - PasswordFeedback::NoNeedForSymbolsDigitsOrUppercaseLetters, - ]) - })?; + let entropy = zxcvbn(cleartext, related_inputs); // PW's should always be enforced as strong as possible. - if entropy.score() < 4 { + if entropy.score() < Score::Four { // The password is too week as per: // https://docs.rs/zxcvbn/2.0.0/zxcvbn/struct.Entropy.html let feedback: zxcvbn::feedback::Feedback = entropy .feedback() - .as_ref() .ok_or(OperationError::InvalidState) .cloned() .map_err(|e| { @@ -3405,7 +3397,7 @@ mod tests { assert!( matches!( c_status.mfaregstate, - MfaRegStateStatus::TotpNameTryAgain(ref val) if val == "" + MfaRegStateStatus::TotpNameTryAgain(ref val) if val.is_empty() ), "{:?}", c_status.mfaregstate diff --git a/server/lib/src/idm/ldap.rs b/server/lib/src/idm/ldap.rs index 4b7a4e37e..6275c987b 100644 --- a/server/lib/src/idm/ldap.rs +++ b/server/lib/src/idm/ldap.rs @@ -1209,7 +1209,7 @@ mod tests { // Searching a malformed spn shouldn't cause the query to fail let sr = SearchRequest { msgid: 1, - base: format!("dc=example,dc=com"), + base: "dc=example,dc=com".to_string(), scope: LdapSearchScope::Subtree, filter: LdapFilter::Or(vec![ LdapFilter::Equality(Attribute::Name.to_string(), usr_name.to_string()), @@ -1232,7 +1232,7 @@ mod tests { let sr = SearchRequest { msgid: 1, - base: format!("dc=example,dc=com"), + base: "dc=example,dc=com".to_string(), scope: LdapSearchScope::Subtree, filter: LdapFilter::And(vec![ LdapFilter::Equality(Attribute::Name.to_string(), usr_name.to_string()), diff --git a/server/lib/src/idm/oauth2.rs b/server/lib/src/idm/oauth2.rs index 9628e1ea4..2905abbd2 100644 --- a/server/lib/src/idm/oauth2.rs +++ b/server/lib/src/idm/oauth2.rs @@ -2992,11 +2992,12 @@ fn validate_scopes(req_scopes: &BTreeSet<String>) -> Result<(), Oauth2Error> { #[cfg(any(feature = "dev-oauth2-device-flow", test))] #[allow(dead_code)] fn gen_device_code() -> Result<[u8; 16], Oauth2Error> { - let mut rng = rand::thread_rng(); + use rand::TryRngCore; + + let mut rng = rand::rng(); let mut result = [0u8; 16]; // doing it here because of feature-shenanigans. - use rand::Rng; - if let Err(err) = rng.try_fill(&mut result) { + if let Err(err) = rng.try_fill_bytes(&mut result) { error!("Failed to generate device code! {:?}", err); return Err(Oauth2Error::ServerError(OperationError::Backend)); } @@ -3009,8 +3010,8 @@ fn gen_device_code() -> Result<[u8; 16], Oauth2Error> { /// Returns (xxx-yyy-zzz, digits) where one's the human-facing code, the other is what we store in the DB. fn gen_user_code() -> (String, u32) { use rand::Rng; - let mut rng = rand::thread_rng(); - let num: u32 = rng.gen_range(0..=999999999); + let mut rng = rand::rng(); + let num: u32 = rng.random_range(0..=999999999); let result = format!("{:09}", num); ( format!("{}-{}-{}", &result[0..3], &result[3..6], &result[6..9]), @@ -3100,6 +3101,7 @@ mod tests { $code_challenge:expr, $scope:expr ) => {{ + #[allow(clippy::unnecessary_to_owned)] let scope: BTreeSet<String> = $scope.split(" ").map(|s| s.to_string()).collect(); let auth_req = AuthorisationRequest { @@ -7312,10 +7314,7 @@ mod tests { &Url::parse(example_is_not_local) .expect("Failed to parse example.com as a host?") .host() - .expect(&format!( - "Couldn't get a host from {}", - example_is_not_local - )) + .unwrap_or_else(|| panic!("Couldn't get a host from {}", example_is_not_local)) )); let test_urls = [ diff --git a/server/lib/src/idm/server.rs b/server/lib/src/idm/server.rs index ed55cf69c..d047bb319 100644 --- a/server/lib/src/idm/server.rs +++ b/server/lib/src/idm/server.rs @@ -21,6 +21,7 @@ use tokio::sync::{Mutex, Semaphore}; use tracing::trace; use url::Url; use webauthn_rs::prelude::{Webauthn, WebauthnBuilder}; +use zxcvbn::{zxcvbn, Score}; use super::event::ReadBackupCodeEvent; use super::ldap::{LdapBoundToken, LdapSession}; @@ -235,7 +236,7 @@ impl IdmServer { let qs_read = self.qs.read().await?; let mut sid = [0; 4]; - let mut rng = StdRng::from_entropy(); + let mut rng = StdRng::from_os_rng(); rng.fill(&mut sid); Ok(IdmServerAuthTransaction { @@ -278,7 +279,7 @@ impl IdmServer { let qs_write = self.qs.write(ts).await?; let mut sid = [0; 4]; - let mut rng = StdRng::from_entropy(); + let mut rng = StdRng::from_os_rng(); rng.fill(&mut sid); Ok(IdmServerProxyWriteTransaction { @@ -1657,18 +1658,14 @@ impl IdmServerProxyWriteTransaction<'_> { // does the password pass zxcvbn? - let entropy = zxcvbn::zxcvbn(cleartext, related_inputs).map_err(|e| { - admin_error!("zxcvbn check failure (password empty?) {:?}", e); - OperationError::PasswordQuality(vec![PasswordFeedback::TooShort(PW_MIN_LENGTH)]) - })?; + let entropy = zxcvbn(cleartext, related_inputs); // Unix PW's are a single factor, so we enforce good pws - if entropy.score() < 4 { + if entropy.score() < Score::Four { // The password is too week as per: // https://docs.rs/zxcvbn/2.0.0/zxcvbn/struct.Entropy.html let feedback: zxcvbn::feedback::Feedback = entropy .feedback() - .as_ref() .ok_or(OperationError::InvalidState) .cloned() .inspect_err(|err| { diff --git a/server/lib/src/repl/entry.rs b/server/lib/src/repl/entry.rs index 4f7c26dfb..75fed5f50 100644 --- a/server/lib/src/repl/entry.rs +++ b/server/lib/src/repl/entry.rs @@ -217,9 +217,9 @@ impl EntryChangeState { } #[cfg(test)] - pub(crate) fn get_attr_cid(&self, attr: Attribute) -> Option<&Cid> { + pub(crate) fn get_attr_cid(&self, attr: &Attribute) -> Option<&Cid> { match &self.st { - State::Live { at: _, changes } => changes.get(&attr), + State::Live { at: _, changes } => changes.get(attr), State::Tombstone { at: _ } => None, } } diff --git a/server/lib/src/repl/tests.rs b/server/lib/src/repl/tests.rs index 5da89fd53..951df6254 100644 --- a/server/lib/src/repl/tests.rs +++ b/server/lib/src/repl/tests.rs @@ -705,7 +705,7 @@ async fn test_repl_increment_basic_deleted_attr(server_a: &QueryServer, server_b let e1_cs = e1.get_changestate(); let e2_cs = e2.get_changestate(); assert_eq!(e1_cs, e2_cs); - assert!(e1_cs.get_attr_cid(Attribute::Description).is_some()); + assert!(e1_cs.get_attr_cid(&Attribute::Description).is_some()); server_b_txn.commit().expect("Failed to commit"); drop(server_a_txn); diff --git a/server/lib/src/server/scim.rs b/server/lib/src/server/scim.rs index be529e8be..ef375f523 100644 --- a/server/lib/src/server/scim.rs +++ b/server/lib/src/server/scim.rs @@ -186,7 +186,7 @@ mod tests { match desc { ScimValueKanidm::String(gdesc) if gdesc == "Group Description" => {} - _ => assert!(false), + _ => unreachable!("Expected a string"), }; // null removes attr @@ -201,7 +201,7 @@ mod tests { .expect("Failed to resolve data type"); let updated_entry = server_txn.scim_put(put_event).expect("Failed to put"); - assert!(updated_entry.attrs.get(&Attribute::Description).is_none()); + assert!(!updated_entry.attrs.contains_key(&Attribute::Description)); // set one let put = ScimEntryPutKanidm { @@ -234,7 +234,7 @@ mod tests { value: "extra_1@example.com".to_string(), })); } - _ => assert!(false), + _ => unreachable!("Expected 1 member"), }; // set many @@ -285,7 +285,7 @@ mod tests { value: "extra_3@example.com".to_string(), })); } - _ => assert!(false), + _ => unreachable!("Expected 3 members"), }; // set many with a removal @@ -333,7 +333,7 @@ mod tests { value: "extra_2@example.com".to_string(), })); } - _ => assert!(false), + _ => unreachable!("Expected 2 members"), }; // empty set removes attr @@ -348,6 +348,6 @@ mod tests { .expect("Failed to resolve data type"); let updated_entry = server_txn.scim_put(put_event).expect("Failed to put"); - assert!(updated_entry.attrs.get(&Attribute::Member).is_none()); + assert!(!updated_entry.attrs.contains_key(&Attribute::Member)); } } diff --git a/server/lib/src/testkit.rs b/server/lib/src/testkit.rs index 98342020b..2eb24abf8 100644 --- a/server/lib/src/testkit.rs +++ b/server/lib/src/testkit.rs @@ -4,12 +4,19 @@ use crate::schema::Schema; pub struct TestConfiguration { pub domain_level: DomainVersion, + // This is literally here to make clippy happy, just leave it alone! + // if you don't believe me then remove it and run 'cargo clippy --all-targets' it'll complain + // about "struct update has no effect, all the fields in the struct have already been specified" + // because the domain_level was set, then we ..Default::default() the "rest" + #[allow(dead_code)] + pub ignore_this_field: bool, } impl Default for TestConfiguration { fn default() -> Self { TestConfiguration { domain_level: DOMAIN_TGT_LEVEL, + ignore_this_field: false, } } } diff --git a/server/lib/src/utils.rs b/server/lib/src/utils.rs index 1c762b027..bd033fa42 100644 --- a/server/lib/src/utils.rs +++ b/server/lib/src/utils.rs @@ -2,8 +2,8 @@ use crate::prelude::*; use hashbrown::HashSet; -use rand::distributions::{Distribution, Uniform}; -use rand::{thread_rng, Rng}; +use rand::distr::{Distribution, Uniform}; +use rand::{rng, Rng}; use std::ops::Range; #[derive(Debug)] @@ -35,7 +35,7 @@ pub fn uuid_from_duration(d: Duration, sid: Sid) -> Uuid { } pub(crate) fn password_from_random_len(len: u32) -> String { - thread_rng() + rng() .sample_iter(&DistinctAlpha) .take(len as usize) .collect::<String>() @@ -52,7 +52,7 @@ pub fn backup_code_from_random() -> HashSet<String> { pub fn readable_password_from_random() -> String { // 2^112 bits, means we need at least 55^20 to have as many bits of entropy. // this leads us to 4 groups of 5 to create 55^20 - let mut trng = thread_rng(); + let mut trng = rng(); format!( "{}-{}-{}-{}", (&mut trng) @@ -80,8 +80,9 @@ impl Distribution<char> for DistinctAlpha { const GEN_ASCII_STR_CHARSET: &[u8] = b"ABCDEFGHJKLMNPQRSTUVWXYZ\ abcdefghjkpqrstuvwxyz\ 0123456789"; - - let range = Uniform::new(0, RANGE); + // TODO: this needs to handle the error, maybe? + #[allow(clippy::expect_used)] + let range = Uniform::new(0, RANGE).expect("Failed to get a uniform range"); let n = range.sample(rng); GEN_ASCII_STR_CHARSET[n as usize] as char diff --git a/server/lib/src/valueset/address.rs b/server/lib/src/valueset/address.rs index f50d1b58c..03c50ed22 100644 --- a/server/lib/src/valueset/address.rs +++ b/server/lib/src/valueset/address.rs @@ -683,10 +683,10 @@ mod tests { "value": "claire@example.com" } ]"#; - crate::valueset::scim_json_reflexive(vs.clone(), data); + crate::valueset::scim_json_reflexive(&vs, data); // Test that we can parse json values into a valueset. - crate::valueset::scim_json_put_reflexive::<ValueSetEmailAddress>(vs, &[]) + crate::valueset::scim_json_put_reflexive::<ValueSetEmailAddress>(&vs, &[]) } #[test] @@ -711,9 +711,9 @@ mod tests { } ]"#; - crate::valueset::scim_json_reflexive(vs.clone(), data); + crate::valueset::scim_json_reflexive(&vs, data); // Test that we can parse json values into a valueset. - crate::valueset::scim_json_put_reflexive::<ValueSetAddress>(vs, &[]) + crate::valueset::scim_json_put_reflexive::<ValueSetAddress>(&vs, &[]) } } diff --git a/server/lib/src/valueset/apppwd.rs b/server/lib/src/valueset/apppwd.rs index 1885b12d8..499ae3f70 100644 --- a/server/lib/src/valueset/apppwd.rs +++ b/server/lib/src/valueset/apppwd.rs @@ -381,6 +381,6 @@ mod tests { } ] "#; - crate::valueset::scim_json_reflexive(vs, data); + crate::valueset::scim_json_reflexive(&vs, data); } } diff --git a/server/lib/src/valueset/auditlogstring.rs b/server/lib/src/valueset/auditlogstring.rs index 5795a23ee..839ccc10e 100644 --- a/server/lib/src/valueset/auditlogstring.rs +++ b/server/lib/src/valueset/auditlogstring.rs @@ -400,6 +400,6 @@ mod tests { } ] "#; - crate::valueset::scim_json_reflexive(vs, data); + crate::valueset::scim_json_reflexive(&vs, data); } } diff --git a/server/lib/src/valueset/bool.rs b/server/lib/src/valueset/bool.rs index 6f8f05fac..998365d85 100644 --- a/server/lib/src/valueset/bool.rs +++ b/server/lib/src/valueset/bool.rs @@ -185,9 +185,9 @@ mod tests { #[test] fn test_scim_boolean() { let vs: ValueSet = ValueSetBool::new(true); - crate::valueset::scim_json_reflexive(vs.clone(), "true"); + crate::valueset::scim_json_reflexive(&vs, "true"); // Test that we can parse json values into a valueset. - crate::valueset::scim_json_put_reflexive::<ValueSetBool>(vs, &[]) + crate::valueset::scim_json_put_reflexive::<ValueSetBool>(&vs, &[]) } } diff --git a/server/lib/src/valueset/certificate.rs b/server/lib/src/valueset/certificate.rs index 596a349d7..c558bd2b1 100644 --- a/server/lib/src/valueset/certificate.rs +++ b/server/lib/src/valueset/certificate.rs @@ -352,6 +352,6 @@ raBy6edj7W0EIH+yQxkDEwIhAI0nVKaI6duHLAvtKW6CfEQFG6jKg7dyk37YYiRD assert_eq!(cert.s256, expect_s256); // Test that we can parse json values into a valueset. - crate::valueset::scim_json_put_reflexive::<ValueSetCertificate>(vs, &[]) + crate::valueset::scim_json_put_reflexive::<ValueSetCertificate>(&vs, &[]) } } diff --git a/server/lib/src/valueset/cid.rs b/server/lib/src/valueset/cid.rs index 105dccabe..589d75b73 100644 --- a/server/lib/src/valueset/cid.rs +++ b/server/lib/src/valueset/cid.rs @@ -193,6 +193,6 @@ mod tests { let vs: ValueSet = ValueSetCid::new(Cid::new_zero()); let data = r#""00000000000000000000000000000000-00000000-0000-0000-0000-000000000000""#; - crate::valueset::scim_json_reflexive(vs, data); + crate::valueset::scim_json_reflexive(&vs, data); } } diff --git a/server/lib/src/valueset/cred.rs b/server/lib/src/valueset/cred.rs index 09205fb16..ceb914fb7 100644 --- a/server/lib/src/valueset/cred.rs +++ b/server/lib/src/valueset/cred.rs @@ -1183,15 +1183,15 @@ mod tests { } ] "#; - crate::valueset::scim_json_reflexive(vs, data); + crate::valueset::scim_json_reflexive(&vs, data); } #[test] fn test_scim_credential_type() { let vs: ValueSet = ValueSetCredentialType::new(CredentialType::Mfa); - crate::valueset::scim_json_reflexive(vs.clone(), r#""mfa""#); + crate::valueset::scim_json_reflexive(&vs, r#""mfa""#); // Test that we can parse json values into a valueset. - crate::valueset::scim_json_put_reflexive::<ValueSetCredentialType>(vs, &[]) + crate::valueset::scim_json_put_reflexive::<ValueSetCredentialType>(&vs, &[]) } } diff --git a/server/lib/src/valueset/datetime.rs b/server/lib/src/valueset/datetime.rs index b5e5c9d0e..8a3d31ebe 100644 --- a/server/lib/src/valueset/datetime.rs +++ b/server/lib/src/valueset/datetime.rs @@ -210,9 +210,9 @@ mod tests { let odt = OffsetDateTime::UNIX_EPOCH + Duration::from_secs(69_420); let vs: ValueSet = ValueSetDateTime::new(odt); - crate::valueset::scim_json_reflexive(vs.clone(), r#""1970-01-01T19:17:00Z""#); + crate::valueset::scim_json_reflexive(&vs, r#""1970-01-01T19:17:00Z""#); // Test that we can parse json values into a valueset. - crate::valueset::scim_json_put_reflexive::<ValueSetDateTime>(vs, &[]) + crate::valueset::scim_json_put_reflexive::<ValueSetDateTime>(&vs, &[]) } } diff --git a/server/lib/src/valueset/hexstring.rs b/server/lib/src/valueset/hexstring.rs index f1ea14224..059514c23 100644 --- a/server/lib/src/valueset/hexstring.rs +++ b/server/lib/src/valueset/hexstring.rs @@ -183,9 +183,6 @@ mod tests { fn test_scim_hexstring() { let vs: ValueSet = ValueSetHexString::new("D68475C760A7A0F6A924C28F095573A967F600D6".to_string()); - crate::valueset::scim_json_reflexive( - vs.clone(), - r#""D68475C760A7A0F6A924C28F095573A967F600D6""#, - ); + crate::valueset::scim_json_reflexive(&vs, r#""D68475C760A7A0F6A924C28F095573A967F600D6""#); } } diff --git a/server/lib/src/valueset/image/jpg.rs b/server/lib/src/valueset/image/jpg.rs index 47f579aec..55a52293c 100644 --- a/server/lib/src/valueset/image/jpg.rs +++ b/server/lib/src/valueset/image/jpg.rs @@ -1,3 +1,5 @@ +use std::io::Cursor; + use image::codecs::jpeg::JpegDecoder; use image::ImageDecoder; use sketching::*; @@ -79,9 +81,9 @@ pub fn has_trailer(contents: &Vec<u8>) -> Result<bool, ImageValidationError> { pub fn validate_decoding( filename: &str, contents: &[u8], - limits: image::io::Limits, + limits: image::Limits, ) -> Result<(), ImageValidationError> { - let mut decoder = match JpegDecoder::new(contents) { + let mut decoder = match JpegDecoder::new(Cursor::new(contents)) { Ok(val) => val, Err(err) => { return Err(ImageValidationError::InvalidImage(format!( diff --git a/server/lib/src/valueset/image/mod.rs b/server/lib/src/valueset/image/mod.rs index 634d8ae55..66e554253 100644 --- a/server/lib/src/valueset/image/mod.rs +++ b/server/lib/src/valueset/image/mod.rs @@ -1,6 +1,7 @@ #![allow(dead_code)] use crate::valueset::ScimResolveStatus; use std::fmt::Display; +use std::io::Cursor; use crate::be::dbvalue::DbValueImage; use crate::prelude::*; @@ -37,8 +38,8 @@ pub trait ImageValueThings { /// A sha256 of the filename/type/contents fn hash_imagevalue(&self) -> String; - fn get_limits(&self) -> image::io::Limits { - let mut limits = image::io::Limits::default(); + fn get_limits(&self) -> image::Limits { + let mut limits = image::Limits::default(); limits.max_image_height = Some(MAX_IMAGE_HEIGHT); limits.max_image_width = Some(MAX_IMAGE_WIDTH); limits @@ -148,7 +149,7 @@ impl ImageValueThings for ImageValue { /// validate the GIF file contents, and that it's actually a GIF fn validate_is_gif(&self) -> Result<(), ImageValidationError> { - let Ok(mut decoder) = GifDecoder::new(&self.contents[..]) else { + let Ok(mut decoder) = GifDecoder::new(Cursor::new(&self.contents[..])) else { return Err(ImageValidationError::InvalidImage( "Failed to parse GIF".to_string(), )); @@ -189,7 +190,7 @@ impl ImageValueThings for ImageValue { )); } - let Ok(mut decoder) = WebPDecoder::new(&self.contents[..]) else { + let Ok(mut decoder) = WebPDecoder::new(Cursor::new(&self.contents[..])) else { return Err(ImageValidationError::InvalidImage( "Failed to parse WebP file".to_string(), )); @@ -532,7 +533,7 @@ mod tests { "142dc7984dd548dd5dacfe2ad30f8473e3217e39b3b6c8d17a0cf6e4e24b02e0" ]"#; - crate::valueset::scim_json_reflexive(vs.clone(), data); + crate::valueset::scim_json_reflexive(&vs, data); } */ } diff --git a/server/lib/src/valueset/iname.rs b/server/lib/src/valueset/iname.rs index 52b6d9a44..1b80fa17b 100644 --- a/server/lib/src/valueset/iname.rs +++ b/server/lib/src/valueset/iname.rs @@ -226,9 +226,9 @@ mod tests { #[test] fn test_scim_iname() { let vs: ValueSet = ValueSetIname::new("stevo"); - crate::valueset::scim_json_reflexive(vs.clone(), r#""stevo""#); + crate::valueset::scim_json_reflexive(&vs, r#""stevo""#); // Test that we can parse json values into a valueset. - crate::valueset::scim_json_put_reflexive::<ValueSetIname>(vs, &[]) + crate::valueset::scim_json_put_reflexive::<ValueSetIname>(&vs, &[]) } } diff --git a/server/lib/src/valueset/index.rs b/server/lib/src/valueset/index.rs index fc367a4a4..44438d9fd 100644 --- a/server/lib/src/valueset/index.rs +++ b/server/lib/src/valueset/index.rs @@ -183,9 +183,9 @@ mod tests { #[test] fn test_scim_index() { let vs: ValueSet = ValueSetIndex::new(IndexType::Equality); - crate::valueset::scim_json_reflexive(vs.clone(), r#"["EQUALITY"]"#); + crate::valueset::scim_json_reflexive(&vs, r#"["EQUALITY"]"#); // Test that we can parse json values into a valueset. - crate::valueset::scim_json_put_reflexive::<ValueSetIndex>(vs, &[]) + crate::valueset::scim_json_put_reflexive::<ValueSetIndex>(&vs, &[]) } } diff --git a/server/lib/src/valueset/iutf8.rs b/server/lib/src/valueset/iutf8.rs index 6bc82490e..577019fc9 100644 --- a/server/lib/src/valueset/iutf8.rs +++ b/server/lib/src/valueset/iutf8.rs @@ -227,9 +227,9 @@ mod tests { #[test] fn test_scim_iutf8() { let vs: ValueSet = ValueSetIutf8::new("lowercase string"); - crate::valueset::scim_json_reflexive(vs.clone(), r#""lowercase string""#); + crate::valueset::scim_json_reflexive(&vs, r#""lowercase string""#); // Test that we can parse json values into a valueset. - crate::valueset::scim_json_put_reflexive::<ValueSetIutf8>(vs, &[]) + crate::valueset::scim_json_put_reflexive::<ValueSetIutf8>(&vs, &[]) } } diff --git a/server/lib/src/valueset/json.rs b/server/lib/src/valueset/json.rs index fc069cd54..a72d7ca16 100644 --- a/server/lib/src/valueset/json.rs +++ b/server/lib/src/valueset/json.rs @@ -205,9 +205,9 @@ mod tests { "{\"pres\":\"class\"}" ] "#; - crate::valueset::scim_json_reflexive(vs.clone(), data); + crate::valueset::scim_json_reflexive(&vs, data); // Test that we can parse json values into a valueset. - // crate::valueset::scim_json_put_reflexive::<ValueSetJsonFilter>(vs, &[]) + // crate::valueset::scim_json_put_reflexive::<ValueSetJsonFilter>(&vs, &[]) } } diff --git a/server/lib/src/valueset/key_internal.rs b/server/lib/src/valueset/key_internal.rs index 7e08c0cc1..fb5034595 100644 --- a/server/lib/src/valueset/key_internal.rs +++ b/server/lib/src/valueset/key_internal.rs @@ -649,6 +649,6 @@ mod tests { } ] "#; - crate::valueset::scim_json_reflexive(vs, data); + crate::valueset::scim_json_reflexive(&vs, data); } } diff --git a/server/lib/src/valueset/mod.rs b/server/lib/src/valueset/mod.rs index 0cb86a506..3463e0156 100644 --- a/server/lib/src/valueset/mod.rs +++ b/server/lib/src/valueset/mod.rs @@ -995,7 +995,7 @@ pub fn from_db_valueset_v2(dbvs: DbValueSetV2) -> Result<ValueSet, OperationErro } #[cfg(test)] -pub(crate) fn scim_json_reflexive(vs: ValueSet, data: &str) { +pub(crate) fn scim_json_reflexive(vs: &ValueSet, data: &str) { let scim_value = vs.to_scim_value().unwrap().assume_resolved(); let strout = serde_json::to_string_pretty(&scim_value).unwrap(); @@ -1012,25 +1012,27 @@ pub(crate) fn scim_json_reflexive(vs: ValueSet, data: &str) { #[cfg(test)] pub(crate) fn scim_json_reflexive_unresolved( write_txn: &mut QueryServerWriteTransaction, - vs: ValueSet, + vs: &ValueSet, data: &str, ) { let scim_int_value = vs.to_scim_value().unwrap().assume_unresolved(); let scim_value = write_txn.resolve_scim_interim(scim_int_value).unwrap(); - let strout = serde_json::to_string_pretty(&scim_value).unwrap(); + let strout = serde_json::to_string_pretty(&scim_value).expect("Failed to serialize"); eprintln!("{}", strout); - let json_value: serde_json::Value = serde_json::to_value(&scim_value).unwrap(); + let json_value: serde_json::Value = + serde_json::to_value(&scim_value).expect("Failed to convert to JSON"); - let expect: serde_json::Value = serde_json::from_str(data).unwrap(); + let expect: serde_json::Value = + serde_json::from_str(data).expect("Failed to parse expected JSON"); assert_eq!(json_value, expect); } #[cfg(test)] pub(crate) fn scim_json_put_reflexive<T: ValueSetScimPut>( - expect_vs: ValueSet, + expect_vs: &ValueSet, additional_tests: &[(JsonValue, ValueSet)], ) { let scim_value = expect_vs.to_scim_value().unwrap().assume_resolved(); @@ -1041,7 +1043,7 @@ pub(crate) fn scim_json_put_reflexive<T: ValueSetScimPut>( let generic = serde_json::to_value(scim_value).unwrap(); // Check that we can turn back into a vs from the generic version. let vs = T::from_scim_json_put(generic).unwrap().assume_resolved(); - assert_eq!(&vs, &expect_vs); + assert_eq!(&vs, expect_vs); // For each additional check, assert they work as expected. for (jv, expect_vs) in additional_tests { @@ -1053,7 +1055,7 @@ pub(crate) fn scim_json_put_reflexive<T: ValueSetScimPut>( #[cfg(test)] pub(crate) fn scim_json_put_reflexive_unresolved<T: ValueSetScimPut>( write_txn: &mut QueryServerWriteTransaction, - expect_vs: ValueSet, + expect_vs: &ValueSet, additional_tests: &[(JsonValue, ValueSet)], ) { let scim_int_value = expect_vs.to_scim_value().unwrap().assume_unresolved(); @@ -1063,7 +1065,7 @@ pub(crate) fn scim_json_put_reflexive_unresolved<T: ValueSetScimPut>( // Check that we can turn back into a vs from the generic version. let vs_inter = T::from_scim_json_put(generic).unwrap().assume_unresolved(); let vs = write_txn.resolve_valueset_intermediate(vs_inter).unwrap(); - assert_eq!(&vs, &expect_vs); + assert_eq!(&vs, expect_vs); // For each additional check, assert they work as expected. for (jv, expect_vs) in additional_tests { diff --git a/server/lib/src/valueset/nsuniqueid.rs b/server/lib/src/valueset/nsuniqueid.rs index ab0746266..85db71062 100644 --- a/server/lib/src/valueset/nsuniqueid.rs +++ b/server/lib/src/valueset/nsuniqueid.rs @@ -189,12 +189,9 @@ mod tests { fn test_scim_nsuniqueid() { let vs: ValueSet = ValueSetNsUniqueId::new("3a163ca0-47624620-a18806b7-50c84c86".to_string()); - crate::valueset::scim_json_reflexive( - vs.clone(), - r#""3a163ca0-47624620-a18806b7-50c84c86""#, - ); + crate::valueset::scim_json_reflexive(&vs, r#""3a163ca0-47624620-a18806b7-50c84c86""#); // Test that we can parse json values into a valueset. - crate::valueset::scim_json_put_reflexive::<ValueSetNsUniqueId>(vs, &[]) + crate::valueset::scim_json_put_reflexive::<ValueSetNsUniqueId>(&vs, &[]) } } diff --git a/server/lib/src/valueset/oauth.rs b/server/lib/src/valueset/oauth.rs index 0b6ec0f2d..ec0c0050b 100644 --- a/server/lib/src/valueset/oauth.rs +++ b/server/lib/src/valueset/oauth.rs @@ -898,10 +898,10 @@ mod tests { fn test_scim_oauth2_scope() { let vs: ValueSet = ValueSetOauthScope::new("fully_sick_scope_m8".to_string()); let data = r#"["fully_sick_scope_m8"]"#; - crate::valueset::scim_json_reflexive(vs.clone(), data); + crate::valueset::scim_json_reflexive(&vs, data); // Test that we can parse json values into a valueset. - crate::valueset::scim_json_put_reflexive::<ValueSetOauthScope>(vs, &[]) + crate::valueset::scim_json_put_reflexive::<ValueSetOauthScope>(&vs, &[]) } #[qs_test] @@ -930,12 +930,12 @@ mod tests { } ] "#; - crate::valueset::scim_json_reflexive_unresolved(&mut write_txn, vs.clone(), data); + crate::valueset::scim_json_reflexive_unresolved(&mut write_txn, &vs, data); // Test that we can parse json values into a valueset. crate::valueset::scim_json_put_reflexive_unresolved::<ValueSetOauthScopeMap>( &mut write_txn, - vs, + &vs, &[], ); @@ -970,12 +970,12 @@ mod tests { } ] "#; - crate::valueset::scim_json_reflexive_unresolved(&mut write_txn, vs.clone(), data); + crate::valueset::scim_json_reflexive_unresolved(&mut write_txn, &vs, data); // Test that we can parse json values into a valueset. crate::valueset::scim_json_put_reflexive_unresolved::<ValueSetOauthClaimMap>( &mut write_txn, - vs, + &vs, &[], ); diff --git a/server/lib/src/valueset/restricted.rs b/server/lib/src/valueset/restricted.rs index 29ddb9747..d099a3415 100644 --- a/server/lib/src/valueset/restricted.rs +++ b/server/lib/src/valueset/restricted.rs @@ -205,6 +205,6 @@ mod tests { #[test] fn test_scim_restricted() { let vs: ValueSet = ValueSetRestricted::new("Test".to_string()); - crate::valueset::scim_json_reflexive(vs, r#""Test""#); + crate::valueset::scim_json_reflexive(&vs, r#""Test""#); } } diff --git a/server/lib/src/valueset/session.rs b/server/lib/src/valueset/session.rs index 84d350dd2..d10e7ebd7 100644 --- a/server/lib/src/valueset/session.rs +++ b/server/lib/src/valueset/session.rs @@ -1920,7 +1920,7 @@ mod tests { } ] "#; - crate::valueset::scim_json_reflexive(vs, data); + crate::valueset::scim_json_reflexive(&vs, data); } #[test] @@ -1948,6 +1948,6 @@ mod tests { ] "#; - crate::valueset::scim_json_reflexive(vs, data); + crate::valueset::scim_json_reflexive(&vs, data); } } diff --git a/server/lib/src/valueset/spn.rs b/server/lib/src/valueset/spn.rs index 2bdea78a5..774771f50 100644 --- a/server/lib/src/valueset/spn.rs +++ b/server/lib/src/valueset/spn.rs @@ -188,6 +188,6 @@ mod tests { #[test] fn test_scim_spn() { let vs: ValueSet = ValueSetSpn::new(("claire".to_string(), "example.com".to_string())); - crate::valueset::scim_json_reflexive(vs, r#""claire@example.com""#); + crate::valueset::scim_json_reflexive(&vs, r#""claire@example.com""#); } } diff --git a/server/lib/src/valueset/ssh.rs b/server/lib/src/valueset/ssh.rs index aa9bd6f7e..b9c114f9c 100644 --- a/server/lib/src/valueset/ssh.rs +++ b/server/lib/src/valueset/ssh.rs @@ -247,9 +247,9 @@ mod tests { } ] "#; - crate::valueset::scim_json_reflexive(vs.clone(), data); + crate::valueset::scim_json_reflexive(&vs, data); // Test that we can parse json values into a valueset. - crate::valueset::scim_json_put_reflexive::<ValueSetSshKey>(vs, &[]) + crate::valueset::scim_json_put_reflexive::<ValueSetSshKey>(&vs, &[]) } } diff --git a/server/lib/src/valueset/syntax.rs b/server/lib/src/valueset/syntax.rs index 53df1c77b..c8fb640a2 100644 --- a/server/lib/src/valueset/syntax.rs +++ b/server/lib/src/valueset/syntax.rs @@ -188,9 +188,9 @@ mod tests { #[test] fn test_scim_syntax() { let vs: ValueSet = ValueSetSyntax::new(SyntaxType::Uuid); - crate::valueset::scim_json_reflexive(vs.clone(), r#""UUID""#); + crate::valueset::scim_json_reflexive(&vs, r#""UUID""#); // Test that we can parse json values into a valueset. - crate::valueset::scim_json_put_reflexive::<ValueSetSyntax>(vs, &[]) + crate::valueset::scim_json_put_reflexive::<ValueSetSyntax>(&vs, &[]) } } diff --git a/server/lib/src/valueset/uihint.rs b/server/lib/src/valueset/uihint.rs index e00a8d588..2c2ea8ce7 100644 --- a/server/lib/src/valueset/uihint.rs +++ b/server/lib/src/valueset/uihint.rs @@ -163,9 +163,9 @@ mod tests { #[test] fn test_scim_uihint() { let vs: ValueSet = ValueSetUiHint::new(UiHint::PosixAccount); - crate::valueset::scim_json_reflexive(vs.clone(), r#"["posixaccount"]"#); + crate::valueset::scim_json_reflexive(&vs, r#"["posixaccount"]"#); // Test that we can parse json values into a valueset. - crate::valueset::scim_json_put_reflexive::<ValueSetUiHint>(vs, &[]) + crate::valueset::scim_json_put_reflexive::<ValueSetUiHint>(&vs, &[]) } } diff --git a/server/lib/src/valueset/uint32.rs b/server/lib/src/valueset/uint32.rs index 410db12e9..9ae32e9e5 100644 --- a/server/lib/src/valueset/uint32.rs +++ b/server/lib/src/valueset/uint32.rs @@ -196,9 +196,9 @@ mod tests { #[test] fn test_scim_uint32() { let vs: ValueSet = ValueSetUint32::new(69); - crate::valueset::scim_json_reflexive(vs.clone(), "69"); + crate::valueset::scim_json_reflexive(&vs, "69"); // Test that we can parse json values into a valueset. - crate::valueset::scim_json_put_reflexive::<ValueSetUint32>(vs, &[]) + crate::valueset::scim_json_put_reflexive::<ValueSetUint32>(&vs, &[]) } } diff --git a/server/lib/src/valueset/url.rs b/server/lib/src/valueset/url.rs index eece0dacb..db60e16c5 100644 --- a/server/lib/src/valueset/url.rs +++ b/server/lib/src/valueset/url.rs @@ -183,9 +183,9 @@ mod tests { fn test_scim_url() { let u = Url::parse("https://idm.example.com").unwrap(); let vs: ValueSet = ValueSetUrl::new(u); - crate::valueset::scim_json_reflexive(vs.clone(), r#""https://idm.example.com/""#); + crate::valueset::scim_json_reflexive(&vs, r#""https://idm.example.com/""#); // Test that we can parse json values into a valueset. - crate::valueset::scim_json_put_reflexive::<ValueSetUrl>(vs, &[]) + crate::valueset::scim_json_put_reflexive::<ValueSetUrl>(&vs, &[]) } } diff --git a/server/lib/src/valueset/utf8.rs b/server/lib/src/valueset/utf8.rs index 89a704aa7..43dd49570 100644 --- a/server/lib/src/valueset/utf8.rs +++ b/server/lib/src/valueset/utf8.rs @@ -246,9 +246,9 @@ mod tests { fn test_scim_utf8() { let vs: ValueSet = ValueSetUtf8::new("Test".to_string()); // Test that the output json matches some known str - crate::valueset::scim_json_reflexive(vs.clone(), r#""Test""#); + crate::valueset::scim_json_reflexive(&vs, r#""Test""#); // Test that we can parse json values into a valueset. - crate::valueset::scim_json_put_reflexive::<ValueSetUtf8>(vs, &[]) + crate::valueset::scim_json_put_reflexive::<ValueSetUtf8>(&vs, &[]) } } diff --git a/server/lib/src/valueset/uuid.rs b/server/lib/src/valueset/uuid.rs index 1c3e109e6..0c9f97588 100644 --- a/server/lib/src/valueset/uuid.rs +++ b/server/lib/src/valueset/uuid.rs @@ -422,10 +422,10 @@ mod tests { let data = r#""4d21d04a-dc0e-42eb-b850-34dd180b107f""#; - crate::valueset::scim_json_reflexive(vs.clone(), data); + crate::valueset::scim_json_reflexive(&vs, data); // Test that we can parse json values into a valueset. - crate::valueset::scim_json_put_reflexive::<ValueSetUuid>(vs, &[]) + crate::valueset::scim_json_put_reflexive::<ValueSetUuid>(&vs, &[]) } #[qs_test] @@ -449,12 +449,12 @@ mod tests { let data = r#"[{"uuid": "4d21d04a-dc0e-42eb-b850-34dd180b107f", "value": "testperson1@example.com"}]"#; - crate::valueset::scim_json_reflexive_unresolved(&mut write_txn, vs.clone(), data); + crate::valueset::scim_json_reflexive_unresolved(&mut write_txn, &vs, data); // Test that we can parse json values into a valueset. crate::valueset::scim_json_put_reflexive_unresolved::<ValueSetRefer>( &mut write_txn, - vs, + &vs, &[], ); diff --git a/server/testkit-macros/src/entry.rs b/server/testkit-macros/src/entry.rs index 81e1ef701..6fa1b9a48 100644 --- a/server/testkit-macros/src/entry.rs +++ b/server/testkit-macros/src/entry.rs @@ -10,16 +10,17 @@ const ALLOWED_ATTRIBUTES: &[&str] = &[ "threads", "db_path", "maximum_request", - "trust_x_forward_for", + "http_client_address_info", "role", "output_mode", "log_level", "ldap", + "with_test_env", ]; #[derive(Default)] struct Flags { - ldap: bool, + target_wants_test_env: bool, } fn parse_attributes( @@ -60,8 +61,11 @@ fn parse_attributes( .unwrap_or_default() .as_str() { + "with_test_env" => { + flags.target_wants_test_env = true; + } "ldap" => { - flags.ldap = true; + flags.target_wants_test_env = true; field_modifications.extend(quote! { ldapbindaddress: Some("on".to_string()),}) } @@ -134,7 +138,7 @@ pub(crate) fn test(args: TokenStream, item: TokenStream) -> TokenStream { #[::core::prelude::v1::test] }; - let test_fn_args = if flags.ldap { + let test_fn_args = if flags.target_wants_test_env { quote! { &test_env } diff --git a/server/testkit-macros/src/lib.rs b/server/testkit-macros/src/lib.rs index 6ca72af02..fb1d94da2 100644 --- a/server/testkit-macros/src/lib.rs +++ b/server/testkit-macros/src/lib.rs @@ -44,7 +44,7 @@ pub fn cli_kanidm(_input: TokenStream) -> TokenStream { .run() .unwrap(); let mut kanidm = kanidm.command(); - kanidm.env("KANIDM_URL", &rsclient.get_url().to_string()); + kanidm.env("KANIDM_URL", rsclient.get_url().to_string()); kanidm.env("KANIDM_TOKEN_CACHE_PATH", &token_cache_path); kanidm } diff --git a/server/testkit/Cargo.toml b/server/testkit/Cargo.toml index 6689649a2..83f87bf50 100644 --- a/server/testkit/Cargo.toml +++ b/server/testkit/Cargo.toml @@ -53,6 +53,10 @@ escargot = "0.5.13" # used for webdriver testing fantoccini = { version = "0.21.5" } futures = { workspace = true } +hex = { workspace = true } +hyper = { workspace = true } +http-body-util = { workspace = true } +hyper-util = { workspace = true } ldap3_client = { workspace = true } oauth2_ext = { workspace = true, default-features = false, features = [ "reqwest", diff --git a/server/testkit/src/lib.rs b/server/testkit/src/lib.rs index 7eef97a25..ec35a2199 100644 --- a/server/testkit/src/lib.rs +++ b/server/testkit/src/lib.rs @@ -15,7 +15,7 @@ use kanidm_proto::internal::{Filter, Modify, ModifyList}; use kanidmd_core::config::{Configuration, IntegrationTestConfig}; use kanidmd_core::{create_server_core, CoreHandle}; use kanidmd_lib::prelude::{Attribute, NAME_SYSTEM_ADMINS}; -use std::net::TcpStream; +use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream}; use std::sync::atomic::{AtomicU16, Ordering}; use tokio::task; use tracing::error; @@ -64,6 +64,7 @@ fn port_loop() -> u16 { pub struct AsyncTestEnvironment { pub rsclient: KanidmClient, + pub http_sock_addr: SocketAddr, pub core_handle: CoreHandle, pub ldap_url: Option<Url>, } @@ -86,8 +87,9 @@ pub async fn setup_async_test(mut config: Configuration) -> AsyncTestEnvironment let ldap_url = if config.ldapbindaddress.is_some() { let ldapport = port_loop(); - config.ldapbindaddress = Some(format!("127.0.0.1:{}", ldapport)); - Url::parse(&format!("ldap://127.0.0.1:{}", ldapport)) + let ldap_sock_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), ldapport); + config.ldapbindaddress = Some(ldap_sock_addr.to_string()); + Url::parse(&format!("ldap://{}", ldap_sock_addr)) .inspect_err(|err| error!(?err, "ldap address setup")) .ok() } else { @@ -95,7 +97,9 @@ pub async fn setup_async_test(mut config: Configuration) -> AsyncTestEnvironment }; // Setup the address and origin.. - config.address = format!("127.0.0.1:{}", port); + let http_sock_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port); + + config.address = http_sock_addr.to_string(); config.integration_test_config = Some(int_config); config.domain = "localhost".to_string(); config.origin.clone_from(&addr); @@ -123,6 +127,7 @@ pub async fn setup_async_test(mut config: Configuration) -> AsyncTestEnvironment AsyncTestEnvironment { rsclient, + http_sock_addr, core_handle, ldap_url, } diff --git a/server/testkit/tests/testkit/group.rs b/server/testkit/tests/testkit/group.rs index 4eaeb30a0..a4e7cb8da 100644 --- a/server/testkit/tests/testkit/group.rs +++ b/server/testkit/tests/testkit/group.rs @@ -10,7 +10,7 @@ async fn test_v1_group_id_patch(rsclient: &KanidmClient) { .await; assert!(res.is_ok()); - create_user(&rsclient, "foo", "foogroup").await; + create_user(rsclient, "foo", "foogroup").await; let post_body = serde_json::json!({"attrs": { ATTR_DESCRIPTION : ["Fancy group change"]}}); @@ -31,7 +31,7 @@ async fn test_v1_group_id_attr_post(rsclient: &KanidmClient) { .await; assert!(res.is_ok()); - create_user(&rsclient, "foo", "foogroup").await; + create_user(rsclient, "foo", "foogroup").await; let post_body = serde_json::json!(["foo"]); diff --git a/server/testkit/tests/testkit/https_extractors.rs b/server/testkit/tests/testkit/https_extractors.rs deleted file mode 100644 index b664517cb..000000000 --- a/server/testkit/tests/testkit/https_extractors.rs +++ /dev/null @@ -1,193 +0,0 @@ -use std::{ - net::{IpAddr, Ipv4Addr}, - str::FromStr, -}; - -use kanidm_client::KanidmClient; -use kanidm_proto::constants::X_FORWARDED_FOR; - -const DEFAULT_IP_ADDRESS: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); - -// *test where we don't trust the x-forwarded-for header - -#[kanidmd_testkit::test(trust_x_forward_for = false)] -async fn dont_trust_xff_send_header(rsclient: &KanidmClient) { - let client = rsclient.client(); - - let res = client - .get(rsclient.make_url("/v1/debug/ipinfo")) - .header( - X_FORWARDED_FOR, - "An invalid header that will get through!!!", - ) - .send() - .await - .unwrap(); - let ip_res: IpAddr = res - .json() - .await - .expect("Failed to parse response as IpAddr"); - - assert_eq!(ip_res, DEFAULT_IP_ADDRESS); -} - -#[kanidmd_testkit::test(trust_x_forward_for = false)] -async fn dont_trust_xff_dont_send_header(rsclient: &KanidmClient) { - let client = rsclient.client(); - - let res = client - .get(rsclient.make_url("/v1/debug/ipinfo")) - .header( - X_FORWARDED_FOR, - "An invalid header that will get through!!!", - ) - .send() - .await - .unwrap(); - let body = res.bytes().await.unwrap(); - let ip_res: IpAddr = serde_json::from_slice(&body).unwrap_or_else(|op| { - panic!( - "Failed to parse response as IpAddr: {:?} body: {:?}", - op, body, - ) - }); - eprintln!("Body: {:?}", body); - assert_eq!(ip_res, DEFAULT_IP_ADDRESS); -} - -// *test where we trust the x-forwarded-for header - -#[kanidmd_testkit::test(trust_x_forward_for = true)] -async fn trust_xff_send_invalid_header_single_value(rsclient: &KanidmClient) { - let client = rsclient.client(); - - let res = client - .get(rsclient.make_url("/v1/debug/ipinfo")) - .header( - X_FORWARDED_FOR, - "An invalid header that will get through!!!", - ) - .send() - .await - .unwrap(); - - assert_eq!(res.status(), 400); -} - -// TODO: Right now we reject the request only if the leftmost address is invalid. In the future that could change so we could also have a test -// with a valid leftmost address and an invalid address later in the list. Right now it wouldn't work. -// -#[kanidmd_testkit::test(trust_x_forward_for = true)] -async fn trust_xff_send_invalid_header_multiple_values(rsclient: &KanidmClient) { - let client = rsclient.client(); - - let res = client - .get(rsclient.make_url("/v1/debug/ipinfo")) - .header( - X_FORWARDED_FOR, - "203.0.113.195_noooo_my_ip_address, 2001:db8:85a3:8d3:1319:8a2e:370:7348", - ) - .send() - .await - .unwrap(); - - assert_eq!(res.status(), 400); -} - -#[kanidmd_testkit::test(trust_x_forward_for = true)] -async fn trust_xff_send_valid_header_single_ipv4_address(rsclient: &KanidmClient) { - let ip_addr = "2001:db8:85a3:8d3:1319:8a2e:370:7348"; - - let client = rsclient.client(); - - let res = client - .get(rsclient.make_url("/v1/debug/ipinfo")) - .header(X_FORWARDED_FOR, ip_addr) - .send() - .await - .unwrap(); - let ip_res: IpAddr = res - .json() - .await - .expect("Failed to parse response as Vec<IpAddr>"); - - assert_eq!(ip_res, IpAddr::from_str(ip_addr).unwrap()); -} - -#[kanidmd_testkit::test(trust_x_forward_for = true)] -async fn trust_xff_send_valid_header_single_ipv6_address(rsclient: &KanidmClient) { - let ip_addr = "203.0.113.195"; - - let client = rsclient.client(); - - let res = client - .get(rsclient.make_url("/v1/debug/ipinfo")) - .header(X_FORWARDED_FOR, ip_addr) - .send() - .await - .unwrap(); - let ip_res: IpAddr = res - .json() - .await - .expect("Failed to parse response as Vec<IpAddr>"); - - assert_eq!(ip_res, IpAddr::from_str(ip_addr).unwrap()); -} - -#[kanidmd_testkit::test(trust_x_forward_for = true)] -async fn trust_xff_send_valid_header_multiple_address(rsclient: &KanidmClient) { - let first_ip_addr = "203.0.113.195, 2001:db8:85a3:8d3:1319:8a2e:370:7348"; - - let client = rsclient.client(); - - let res = client - .get(rsclient.make_url("/v1/debug/ipinfo")) - .header(X_FORWARDED_FOR, first_ip_addr) - .send() - .await - .unwrap(); - let ip_res: IpAddr = res - .json() - .await - .expect("Failed to parse response as Vec<IpAddr>"); - - assert_eq!( - ip_res, - IpAddr::from_str(first_ip_addr.split(",").collect::<Vec<&str>>()[0]).unwrap() - ); - - let second_ip_addr = "2001:db8:85a3:8d3:1319:8a2e:370:7348, 198.51.100.178, 203.0.113.195"; - - let res = client - .get(rsclient.make_url("/v1/debug/ipinfo")) - .header(X_FORWARDED_FOR, second_ip_addr) - .send() - .await - .unwrap(); - let ip_res: IpAddr = res - .json() - .await - .expect("Failed to parse response as Vec<IpAddr>"); - - assert_eq!( - ip_res, - IpAddr::from_str(second_ip_addr.split(",").collect::<Vec<&str>>()[0]).unwrap() - ); -} - -#[kanidmd_testkit::test(trust_x_forward_for = true)] -async fn trust_xff_dont_send_header(rsclient: &KanidmClient) { - let client = rsclient.client(); - - let res = client - .get(rsclient.make_url("/v1/debug/ipinfo")) - .send() - .await - .unwrap(); - let ip_res: IpAddr = res - .json() - .await - .expect("Failed to parse response as Vec<IpAddr>"); - - assert_eq!(ip_res, DEFAULT_IP_ADDRESS); -} diff --git a/server/testkit/tests/testkit/identity_verification_tests.rs b/server/testkit/tests/testkit/identity_verification_tests.rs index 2ed48a816..9f702042c 100644 --- a/server/testkit/tests/testkit/identity_verification_tests.rs +++ b/server/testkit/tests/testkit/identity_verification_tests.rs @@ -17,8 +17,8 @@ static USER_B_NAME: &str = "valid_user_b"; #[kanidmd_testkit::test] async fn test_not_authenticated(rsclient: &KanidmClient) { // basically here we try a bit of all the possible combinations while unauthenticated to check it's not working - setup_server(&rsclient).await; - create_user(&rsclient, USER_A_NAME).await; + setup_server(rsclient).await; + create_user(rsclient, USER_A_NAME).await; let _ = rsclient.logout().await; let res = rsclient .idm_person_identify_user(USER_A_NAME, IdentifyUserRequest::Start) @@ -47,11 +47,11 @@ async fn test_not_authenticated(rsclient: &KanidmClient) { #[kanidmd_testkit::test] async fn test_non_existing_user_id(rsclient: &KanidmClient) { - setup_server(&rsclient).await; - create_user(&rsclient, USER_A_NAME).await; - create_user(&rsclient, USER_B_NAME).await; + setup_server(rsclient).await; + create_user(rsclient, USER_A_NAME).await; + create_user(rsclient, USER_B_NAME).await; let non_existing_user = "non_existing_user"; - login_with_user(&rsclient, USER_A_NAME).await; + login_with_user(rsclient, USER_A_NAME).await; let res: Result<IdentifyUserResponse, kanidm_client::ClientError> = rsclient .idm_person_identify_user(non_existing_user, IdentifyUserRequest::Start) .await; @@ -87,9 +87,9 @@ async fn test_non_existing_user_id(rsclient: &KanidmClient) { // Each tests is named like `test_{api input}_response_{expected api output}_or_{expected api output}` #[kanidmd_testkit::test] async fn test_start_response_identity_verification_available(rsclient: &KanidmClient) { - setup_server(&rsclient).await; - create_user(&rsclient, USER_A_NAME).await; - login_with_user(&rsclient, USER_A_NAME).await; + setup_server(rsclient).await; + create_user(rsclient, USER_A_NAME).await; + login_with_user(rsclient, USER_A_NAME).await; let response = rsclient .idm_person_identify_user(USER_A_NAME, IdentifyUserRequest::Start) @@ -106,10 +106,10 @@ async fn test_start_response_identity_verification_available(rsclient: &KanidmCl // `Start`, that is WaitForCode or ProvideCode #[kanidmd_testkit::test] async fn test_start_response_wait_for_code_or_provide_code(rsclient: &KanidmClient) { - setup_server(&rsclient).await; - let user_a_uuid = create_user(&rsclient, USER_A_NAME).await; - let user_b_uuid = create_user(&rsclient, USER_B_NAME).await; - login_with_user(&rsclient, USER_A_NAME).await; + setup_server(rsclient).await; + let user_a_uuid = create_user(rsclient, USER_A_NAME).await; + let user_b_uuid = create_user(rsclient, USER_B_NAME).await; + login_with_user(rsclient, USER_A_NAME).await; let response = rsclient .idm_person_identify_user(USER_B_NAME, IdentifyUserRequest::Start) .await; @@ -130,10 +130,10 @@ async fn test_start_response_wait_for_code_or_provide_code(rsclient: &KanidmClie #[kanidmd_testkit::test] async fn test_provide_code_response_code_failure_or_provide_code(rsclient: &KanidmClient) { - setup_server(&rsclient).await; - let user_a_uuid = create_user(&rsclient, USER_A_NAME).await; - let user_b_uuid = create_user(&rsclient, USER_B_NAME).await; - login_with_user(&rsclient, USER_A_NAME).await; + setup_server(rsclient).await; + let user_a_uuid = create_user(rsclient, USER_A_NAME).await; + let user_b_uuid = create_user(rsclient, USER_B_NAME).await; + login_with_user(rsclient, USER_A_NAME).await; let response = rsclient .idm_person_identify_user( USER_B_NAME, @@ -158,12 +158,12 @@ async fn test_provide_code_response_code_failure_or_provide_code(rsclient: &Kani // here we actually test the full idm flow by duplicating the server #[kanidmd_testkit::test] async fn test_full_identification_flow(rsclient: &KanidmClient) { - setup_server(&rsclient).await; - let user_a_uuid = create_user(&rsclient, USER_A_NAME).await; - let user_b_uuid = create_user(&rsclient, USER_B_NAME).await; + setup_server(rsclient).await; + let user_a_uuid = create_user(rsclient, USER_A_NAME).await; + let user_b_uuid = create_user(rsclient, USER_B_NAME).await; //user A session let valid_user_a_client = rsclient; - login_with_user(&valid_user_a_client, USER_A_NAME).await; + login_with_user(valid_user_a_client, USER_A_NAME).await; //user B session let valid_user_b_client = valid_user_a_client.new_session().unwrap(); login_with_user(&valid_user_b_client, USER_B_NAME).await; diff --git a/server/testkit/tests/testkit/integration.rs b/server/testkit/tests/testkit/integration.rs index 4b2b2eac7..b9fd3e099 100644 --- a/server/testkit/tests/testkit/integration.rs +++ b/server/testkit/tests/testkit/integration.rs @@ -76,10 +76,10 @@ async fn test_webdriver_user_login(rsclient: &KanidmClient) { use fantoccini::Locator; use kanidmd_testkit::*; use std::time::Duration; - login_put_admin_idm_admins(&rsclient).await; + login_put_admin_idm_admins(rsclient).await; create_user_with_all_attrs( - &rsclient, + rsclient, NOT_ADMIN_TEST_USERNAME, Some(NOT_ADMIN_TEST_PASSWORD), ) @@ -89,7 +89,7 @@ async fn test_webdriver_user_login(rsclient: &KanidmClient) { handle_error!( c, - c.goto(&rsclient.get_url().to_string()).await, + c.goto(rsclient.get_url().to_string()).await, "Couldn't get URL" ); @@ -207,7 +207,7 @@ async fn test_webdriver_user_login(rsclient: &KanidmClient) { #[kanidmd_testkit::test] async fn test_domain_reset_token_key(rsclient: &KanidmClient) { - login_put_admin_idm_admins(&rsclient).await; + login_put_admin_idm_admins(rsclient).await; let token = rsclient.get_token().await.expect("No bearer token present"); @@ -220,7 +220,7 @@ async fn test_domain_reset_token_key(rsclient: &KanidmClient) { #[kanidmd_testkit::test] async fn test_idm_domain_set_ldap_basedn(rsclient: &KanidmClient) { - login_put_admin_idm_admins(&rsclient).await; + login_put_admin_idm_admins(rsclient).await; assert!(rsclient .idm_domain_set_ldap_basedn("dc=krabsarekool,dc=example,dc=com") .await @@ -233,7 +233,7 @@ async fn test_idm_domain_set_ldap_basedn(rsclient: &KanidmClient) { #[kanidmd_testkit::test] async fn test_idm_domain_set_ldap_max_queryable_attrs(rsclient: &KanidmClient) { - login_put_admin_idm_admins(&rsclient).await; + login_put_admin_idm_admins(rsclient).await; assert!(rsclient .idm_domain_set_ldap_max_queryable_attrs(20) .await @@ -247,7 +247,7 @@ async fn test_idm_domain_set_ldap_max_queryable_attrs(rsclient: &KanidmClient) { #[kanidmd_testkit::test] /// Checks that a built-in group idm_all_persons has the "builtin" class as expected. async fn test_all_persons_has_builtin_class(rsclient: &KanidmClient) { - login_put_admin_idm_admins(&rsclient).await; + login_put_admin_idm_admins(rsclient).await; let res = rsclient .idm_group_get("idm_all_persons") .await @@ -306,7 +306,7 @@ async fn test_all_persons_has_builtin_class(rsclient: &KanidmClient) { /// Testing the CLI doing things. async fn test_integration_with_assert_cmd(rsclient: KanidmClient) { // setup the admin things - login_put_admin_idm_admins(&rsclient).await; + login_put_admin_idm_admins(rsclient).await; rsclient .idm_person_account_primary_credential_set_password( @@ -334,13 +334,13 @@ async fn test_integration_with_assert_cmd(rsclient: KanidmClient) { assert!(anon_whoami.status.success()); println!("Output: {:?}", anon_whoami); - test_cmd_admin(&token_cache_path, &rsclient, "login -D admin"); + test_cmd_admin(&token_cache_path, rsclient, "login -D admin"); // login as idm_admin - test_cmd_idm_admin(&token_cache_path, &rsclient, "login -D idm_admin"); + test_cmd_idm_admin(&token_cache_path, rsclient, "login -D idm_admin"); test_cmd_admin_split( &token_cache_path, - &rsclient, + rsclient, &[ "service-account", "create", @@ -355,13 +355,13 @@ async fn test_integration_with_assert_cmd(rsclient: KanidmClient) { test_cmd_admin( &token_cache_path, - &rsclient, + rsclient, &format!("service-account get -D admin {}", NOT_ADMIN_TEST_USERNAME), ); // updating the display name test_cmd_admin( &token_cache_path, - &rsclient, + rsclient, &format!( "service-account update -D admin {} --displayname cheeseballs", NOT_ADMIN_TEST_USERNAME @@ -370,7 +370,7 @@ async fn test_integration_with_assert_cmd(rsclient: KanidmClient) { // updating the email test_cmd_admin( &token_cache_path, - &rsclient, + rsclient, &format!( "service-account update -D admin {} --mail foo@bar.com", NOT_ADMIN_TEST_USERNAME @@ -380,7 +380,7 @@ async fn test_integration_with_assert_cmd(rsclient: KanidmClient) { // checking the email was changed let sad = test_cmd_admin( &token_cache_path, - &rsclient, + rsclient, &format!( "service-account get -D admin -o json {}", NOT_ADMIN_TEST_USERNAME diff --git a/server/testkit/tests/testkit/ip_addr_extractors.rs b/server/testkit/tests/testkit/ip_addr_extractors.rs new file mode 100644 index 000000000..0d7642a43 --- /dev/null +++ b/server/testkit/tests/testkit/ip_addr_extractors.rs @@ -0,0 +1,324 @@ +use kanidm_client::KanidmClient; +use kanidm_proto::constants::X_FORWARDED_FOR; +use kanidmd_core::config::HttpAddressInfo; +use kanidmd_testkit::AsyncTestEnvironment; +use std::{ + net::{IpAddr, Ipv4Addr, SocketAddr}, + str::FromStr, +}; +use tracing::error; + +const DEFAULT_IP_ADDRESS: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); + +// ===================================================== +// *test where we don't trust the x-forwarded-for header + +#[kanidmd_testkit::test(http_client_address_info = HttpAddressInfo::None)] +async fn dont_trust_xff_send_header(rsclient: &KanidmClient) { + let client = rsclient.client(); + + // Send an invalid header to x forwdr for + let res = client + .get(rsclient.make_url("/v1/debug/ipinfo")) + .header(X_FORWARDED_FOR, "a.b.c.d") + .send() + .await + .unwrap(); + + let ip_res: IpAddr = res + .json() + .await + .expect("Failed to parse response as IpAddr"); + + assert_eq!(ip_res, DEFAULT_IP_ADDRESS); + + // Send a valid header for xforward for, but we don't trust it. + let res = client + .get(rsclient.make_url("/v1/debug/ipinfo")) + .header(X_FORWARDED_FOR, "203.0.113.195") + .send() + .await + .unwrap(); + + let ip_res: IpAddr = res + .json() + .await + .expect("Failed to parse response as IpAddr"); + + assert_eq!(ip_res, DEFAULT_IP_ADDRESS); +} + +// ===================================================== +// *test where we do trust the x-forwarded-for header + +#[kanidmd_testkit::test(http_client_address_info = HttpAddressInfo::XForwardFor ( [DEFAULT_IP_ADDRESS].into() ))] +async fn trust_xff_address_set(rsclient: &KanidmClient) { + inner_test_trust_xff(rsclient).await; +} + +#[kanidmd_testkit::test(http_client_address_info = HttpAddressInfo::XForwardForAllSourcesTrusted)] +async fn trust_xff_all_addresses_trusted(rsclient: &KanidmClient) { + inner_test_trust_xff(rsclient).await; +} + +async fn inner_test_trust_xff(rsclient: &KanidmClient) { + let client = rsclient.client(); + + // An invalid address. + let res = client + .get(rsclient.make_url("/v1/debug/ipinfo")) + .header(X_FORWARDED_FOR, "a.b.c.d") + .send() + .await + .unwrap(); + + // Header was invalid + assert_eq!(res.status(), 400); + + // An invalid address - what follows doesn't matter, even if it was valid. We only + // care about the left most address anyway. + let res = client + .get(rsclient.make_url("/v1/debug/ipinfo")) + .header( + X_FORWARDED_FOR, + "203.0.113.195_noooo_my_ip_address, 2001:db8:85a3:8d3:1319:8a2e:370:7348", + ) + .send() + .await + .unwrap(); + + assert_eq!(res.status(), 400); + + // A valid ipv6 address was provided. + let ip_addr = "2001:db8:85a3:8d3:1319:8a2e:370:7348"; + + let res = client + .get(rsclient.make_url("/v1/debug/ipinfo")) + .header(X_FORWARDED_FOR, ip_addr) + .send() + .await + .unwrap(); + let ip_res: IpAddr = res + .json() + .await + .expect("Failed to parse response as Vec<IpAddr>"); + + assert_eq!(ip_res, IpAddr::from_str(ip_addr).unwrap()); + + // A valid ipv4 address was provided. + let ip_addr = "203.0.113.195"; + + let client = rsclient.client(); + + let res = client + .get(rsclient.make_url("/v1/debug/ipinfo")) + .header(X_FORWARDED_FOR, ip_addr) + .send() + .await + .unwrap(); + let ip_res: IpAddr = res + .json() + .await + .expect("Failed to parse response as Vec<IpAddr>"); + + assert_eq!(ip_res, IpAddr::from_str(ip_addr).unwrap()); + + // A valid ipv4 address in the leftmost field. + let first_ip_addr = "203.0.113.195, 2001:db8:85a3:8d3:1319:8a2e:370:7348"; + + let res = client + .get(rsclient.make_url("/v1/debug/ipinfo")) + .header(X_FORWARDED_FOR, first_ip_addr) + .send() + .await + .unwrap(); + let ip_res: IpAddr = res + .json() + .await + .expect("Failed to parse response as Vec<IpAddr>"); + + assert_eq!( + ip_res, + IpAddr::from_str(first_ip_addr.split(",").collect::<Vec<&str>>()[0]).unwrap() + ); + + // A valid ipv6 address in the left most field. + let second_ip_addr = "2001:db8:85a3:8d3:1319:8a2e:370:7348, 198.51.100.178, 203.0.113.195"; + + let res = client + .get(rsclient.make_url("/v1/debug/ipinfo")) + .header(X_FORWARDED_FOR, second_ip_addr) + .send() + .await + .unwrap(); + let ip_res: IpAddr = res + .json() + .await + .expect("Failed to parse response as Vec<IpAddr>"); + + assert_eq!( + ip_res, + IpAddr::from_str(second_ip_addr.split(",").collect::<Vec<&str>>()[0]).unwrap() + ); + + // If no header is sent, then the connection IP is used. + let res = client + .get(rsclient.make_url("/v1/debug/ipinfo")) + .send() + .await + .unwrap(); + let ip_res: IpAddr = res + .json() + .await + .expect("Failed to parse response as Vec<IpAddr>"); + + assert_eq!(ip_res, DEFAULT_IP_ADDRESS); +} + +// ===================================================== +// *test where we do trust the PROXY protocol header +// +// NOTE: This is MUCH HARDER TO TEST because we can't just stuff this address +// in front of a reqwest call. We have to open raw connections and write the +// requests to them. +// +// As a result, we are pretty much forced to manually dump binary headers and then +// manually craft get reqs, followed by parsing them. + +#[derive(Debug, PartialEq)] +enum ProxyV2Error { + TcpStream, + TcpWrite, + TornWrite, + HttpHandshake, + HttpRequestBuild, + HttpRequest, + HttpBadRequest, +} + +async fn proxy_v2_make_request( + http_sock_addr: SocketAddr, + hdr: &[u8], +) -> Result<IpAddr, ProxyV2Error> { + use http_body_util::BodyExt; + use http_body_util::Empty; + use hyper::body::Bytes; + use hyper::Request; + use hyper_util::rt::TokioIo; + use tokio::io::AsyncWriteExt as _; + use tokio::net::TcpStream; + + let url = format!("http://{}/v1/debug/ipinfo", http_sock_addr) + .as_str() + .parse::<hyper::Uri>() + .unwrap(); + + let mut stream = TcpStream::connect(http_sock_addr).await.map_err(|err| { + error!(?err); + ProxyV2Error::TcpStream + })?; + + // Write the proxyv2 header + let nbytes = stream.write(hdr).await.map_err(|err| { + error!(?err); + ProxyV2Error::TcpWrite + })?; + + if nbytes != hdr.len() { + return Err(ProxyV2Error::TornWrite); + } + + let io = TokioIo::new(stream); + + let (mut sender, conn) = hyper::client::conn::http1::handshake(io) + .await + .map_err(|err| { + error!(?err); + ProxyV2Error::HttpHandshake + })?; + + // Spawn a task to poll the connection, driving the HTTP state + tokio::task::spawn(async move { + if let Err(err) = conn.await { + println!("Connection failed: {:?}", err); + } + }); + + let authority = url.authority().unwrap().clone(); + + // Create an HTTP request with an empty body and a HOST header + let req = Request::builder() + .uri(url) + .header(hyper::header::HOST, authority.as_str()) + .body(Empty::<Bytes>::new()) + .map_err(|err| { + error!(?err); + ProxyV2Error::HttpRequestBuild + })?; + + // Await the response... + let mut res = sender.send_request(req).await.map_err(|err| { + error!(?err); + ProxyV2Error::HttpRequest + })?; + + println!("Response status: {}", res.status()); + + if res.status() != 200 { + return Err(ProxyV2Error::HttpBadRequest); + } + + let mut data: Vec<u8> = Vec::new(); + + while let Some(next) = res.frame().await { + let frame = next.unwrap(); + if let Some(chunk) = frame.data_ref() { + data.write_all(chunk).await.unwrap(); + } + } + + tracing::info!(?data); + let ip_res: IpAddr = serde_json::from_slice(&data).unwrap(); + tracing::info!(?ip_res); + + Ok(ip_res) +} + +#[kanidmd_testkit::test(with_test_env = true, http_client_address_info = HttpAddressInfo::ProxyV2 ( [DEFAULT_IP_ADDRESS].into() ))] +async fn trust_proxy_v2_address_set(test_env: &AsyncTestEnvironment) { + // Send with no header - with proxy v2, a header is ALWAYS required + let proxy_hdr: [u8; 0] = []; + + let res = proxy_v2_make_request(test_env.http_sock_addr, &proxy_hdr) + .await + .unwrap_err(); + + // Can't send http request because proxy wasn't sent. + assert_eq!(res, ProxyV2Error::HttpRequest); + + // Send with a valid header + let proxy_hdr = + hex::decode("0d0a0d0a000d0a515549540a2111000cac180c76ac180b8fcdcb027d").unwrap(); + + let res = proxy_v2_make_request(test_env.http_sock_addr, &proxy_hdr) + .await + .unwrap(); + + // The header was valid + assert_eq!(res, IpAddr::V4(Ipv4Addr::new(172, 24, 12, 118))); +} + +#[kanidmd_testkit::test(with_test_env = true, http_client_address_info = HttpAddressInfo::ProxyV2 ( [ IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)) ].into() ))] +async fn trust_proxy_v2_untrusted(test_env: &AsyncTestEnvironment) { + // Send with a valid header, but we aren't a trusted source. + let proxy_hdr = + hex::decode("0d0a0d0a000d0a515549540a2111000cac180c76ac180b8fcdcb027d").unwrap(); + + let res = proxy_v2_make_request(test_env.http_sock_addr, &proxy_hdr) + .await + .unwrap_err(); + + // Can't send http request because we aren't trusted to send it, so this + // ends up falling into a http request that is REJECTED. + assert_eq!(res, ProxyV2Error::HttpBadRequest); +} diff --git a/server/testkit/tests/testkit/mod.rs b/server/testkit/tests/testkit/mod.rs index 2784a85ed..766a5fdb5 100644 --- a/server/testkit/tests/testkit/mod.rs +++ b/server/testkit/tests/testkit/mod.rs @@ -2,10 +2,10 @@ mod apidocs; mod domain; mod group; mod http_manifest; -mod https_extractors; mod https_middleware; mod identity_verification_tests; mod integration; +mod ip_addr_extractors; mod ldap_basic; mod mtls_test; mod oauth2_test; diff --git a/server/testkit/tests/testkit/person.rs b/server/testkit/tests/testkit/person.rs index 621cec3d4..e45a74992 100644 --- a/server/testkit/tests/testkit/person.rs +++ b/server/testkit/tests/testkit/person.rs @@ -10,7 +10,7 @@ async fn test_v1_person_id_patch(rsclient: &KanidmClient) { .await; assert!(res.is_ok()); - create_user(&rsclient, "foo", "foogroup").await; + create_user(rsclient, "foo", "foogroup").await; let post_body = serde_json::json!({"attrs": { ATTR_MAIL : ["crab@example.com"]}}); @@ -31,7 +31,7 @@ async fn test_v1_person_id_ssh_pubkeys_post(rsclient: &KanidmClient) { .await; assert!(res.is_ok()); - create_user(&rsclient, "foo", "foogroup").await; + create_user(rsclient, "foo", "foogroup").await; let post_body = serde_json::json!([ "ssh-key-tag-goes-here", diff --git a/server/testkit/tests/testkit/proto_v1_test.rs b/server/testkit/tests/testkit/proto_v1_test.rs index 590b9910d..b55dfcd30 100644 --- a/server/testkit/tests/testkit/proto_v1_test.rs +++ b/server/testkit/tests/testkit/proto_v1_test.rs @@ -1366,7 +1366,7 @@ async fn setup_demo_account_password( #[kanidmd_testkit::test] async fn test_server_credential_update_session_passkey(rsclient: &KanidmClient) { - let mut wa = setup_demo_account_passkey(&rsclient).await; + let mut wa = setup_demo_account_passkey(rsclient).await; let res = rsclient .auth_passkey_begin("demo_account") @@ -1690,7 +1690,7 @@ async fn test_server_user_auth_token_lifecycle(rsclient: &KanidmClient) { #[kanidmd_testkit::test] async fn test_server_user_auth_reauthentication(rsclient: &KanidmClient) { - let mut wa = setup_demo_account_passkey(&rsclient).await; + let mut wa = setup_demo_account_passkey(rsclient).await; let res = rsclient .auth_passkey_begin("demo_account") @@ -1869,12 +1869,12 @@ async fn start_password_session( #[kanidmd_testkit::test] async fn test_server_user_auth_unprivileged(rsclient: &KanidmClient) { - let (account_name, account_pass) = setup_demo_account_password(&rsclient) + let (account_name, account_pass) = setup_demo_account_password(rsclient) .await .expect("Failed to setup demo_account"); let uat = start_password_session( - &rsclient, + rsclient, account_name.as_str(), account_pass.as_str(), false, @@ -1892,18 +1892,13 @@ async fn test_server_user_auth_unprivileged(rsclient: &KanidmClient) { #[kanidmd_testkit::test] async fn test_server_user_auth_privileged_shortcut(rsclient: &KanidmClient) { - let (account_name, account_pass) = setup_demo_account_password(&rsclient) + let (account_name, account_pass) = setup_demo_account_password(rsclient) .await .expect("Failed to setup demo_account"); - let uat = start_password_session( - &rsclient, - account_name.as_str(), - account_pass.as_str(), - true, - ) - .await - .expect("Failed to start session"); + let uat = start_password_session(rsclient, account_name.as_str(), account_pass.as_str(), true) + .await + .expect("Failed to start session"); match uat.purpose { UatPurpose::ReadOnly => panic!("Unexpected uat purpose"), diff --git a/server/testkit/tests/testkit/unix.rs b/server/testkit/tests/testkit/unix.rs index bc0d86300..9b3b06d59 100644 --- a/server/testkit/tests/testkit/unix.rs +++ b/server/testkit/tests/testkit/unix.rs @@ -6,10 +6,10 @@ use kanidmd_testkit::*; async fn account_id_unix_token(rsclient: &KanidmClient) { login_put_admin_idm_admins(rsclient).await; - create_user(&rsclient, "group_manager", "idm_group_manage_priv").await; + create_user(rsclient, "group_manager", "idm_group_manage_priv").await; // create test user without creating new groups - create_user(&rsclient, NOT_ADMIN_TEST_USERNAME, NAME_IDM_ADMINS).await; - login_account(&rsclient, "group_manager").await; + create_user(rsclient, NOT_ADMIN_TEST_USERNAME, NAME_IDM_ADMINS).await; + login_account(rsclient, "group_manager").await; let response = rsclient .idm_account_unix_token_get(NOT_ADMIN_TEST_USERNAME) @@ -32,7 +32,7 @@ async fn account_id_unix_token(rsclient: &KanidmClient) { assert!(format!("{:?}", val).contains("400")); } - login_put_admin_idm_admins(&rsclient).await; + login_put_admin_idm_admins(rsclient).await; rsclient .idm_person_account_unix_extend(NOT_ADMIN_TEST_USERNAME, None, None) diff --git a/tools/cli/src/cli/system_config/badlist.rs b/tools/cli/src/cli/system_config/badlist.rs index 9669be47c..f2c2f926d 100644 --- a/tools/cli/src/cli/system_config/badlist.rs +++ b/tools/cli/src/cli/system_config/badlist.rs @@ -5,6 +5,7 @@ use crate::{handle_client_error, PwBadlistOpt}; use std::fs::File; use std::io::Read; use tokio::task; +use zxcvbn::Score; const CHUNK_SIZE: usize = 1000; @@ -67,10 +68,6 @@ impl PwBadlistOpt { info!("Have {} unique passwords to process", pwset.len()); // Break the list into chunks per thread availability - // let par_count = thread::available_parallelism() - // .expect("Failed to determine available parallelism") - // .get(); - let task_handles: Vec<_> = pwset .chunks(CHUNK_SIZE) .map(|chunk| chunk.to_vec()) @@ -82,18 +79,7 @@ impl PwBadlistOpt { if v.len() < 10 { return false; } - match zxcvbn::zxcvbn(v.as_str(), &[]) { - Ok(r) => r.score() >= 4, - Err(e) => { - error!( - "zxcvbn unable to process '{}' - {:?}", - v.as_str(), - e - ); - error!("adding to badlist anyway ..."); - true - } - } + zxcvbn::zxcvbn(v.as_str(), &[]).score() >= Score::Four }) .map(|s| s.to_string()) .collect::<Vec<_>>(); diff --git a/tools/orca/src/error.rs b/tools/orca/src/error.rs index a2cc9697d..f73d3913b 100644 --- a/tools/orca/src/error.rs +++ b/tools/orca/src/error.rs @@ -8,4 +8,6 @@ pub enum Error { Interrupt, Crossbeam, InvalidState, + #[allow(dead_code)] + RandomNumber(String), } diff --git a/tools/orca/src/generate.rs b/tools/orca/src/generate.rs index afd779ff2..b34bf5fd8 100644 --- a/tools/orca/src/generate.rs +++ b/tools/orca/src/generate.rs @@ -4,8 +4,9 @@ use crate::model::ActorRole; use crate::profile::Profile; use crate::state::{Credential, Flag, Group, GroupName, Person, PreflightState, State}; use hashbrown::HashMap; -use rand::distributions::{Alphanumeric, DistString, Uniform}; -use rand::seq::{index, SliceRandom}; +use rand::distr::{Alphanumeric, SampleString, Uniform}; +use rand::seq::{index, IndexedRandom}; + use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha8Rng; @@ -171,7 +172,8 @@ pub async fn populate(_client: &KanidmOrcaClient, profile: Profile) -> Result<St let baseline = persons.len() / 3; let inverse = persons.len() - baseline; // Randomly add extra from the inverse - let extra = Uniform::new(0, inverse); + let extra = + Uniform::new(0, inverse).map_err(|err| Error::RandomNumber(err.to_string()))?; baseline + seeded_rng.sample(extra) } }; diff --git a/tools/orca/src/models/basic.rs b/tools/orca/src/models/basic.rs index c362906a4..23f4c3142 100644 --- a/tools/orca/src/models/basic.rs +++ b/tools/orca/src/models/basic.rs @@ -27,7 +27,7 @@ impl ActorBasic { pub fn new(mut cha_rng: ChaCha8Rng, warmup_time_ms: u64) -> Self { let max_backoff_time_in_ms = 2 * warmup_time_ms / 3; let randomised_backoff_time = - Duration::from_millis(cha_rng.gen_range(0..max_backoff_time_in_ms)); + Duration::from_millis(cha_rng.random_range(0..max_backoff_time_in_ms)); ActorBasic { state: State::Unauthenticated, randomised_backoff_time, diff --git a/tools/orca/src/models/latency_measurer.rs b/tools/orca/src/models/latency_measurer.rs index 6601fac09..d681186a3 100644 --- a/tools/orca/src/models/latency_measurer.rs +++ b/tools/orca/src/models/latency_measurer.rs @@ -76,7 +76,7 @@ impl ActorLatencyMeasurer { let max_backoff_time_in_ms = 2 * warmup_time_ms / 3; let randomised_backoff_time = - Duration::from_millis(cha_rng.gen_range(0..max_backoff_time_in_ms)); + Duration::from_millis(cha_rng.random_range(0..max_backoff_time_in_ms)); Ok(ActorLatencyMeasurer { state: State::Unauthenticated, randomised_backoff_time, diff --git a/tools/orca/src/models/read.rs b/tools/orca/src/models/read.rs index f11eb9984..b280f0b5d 100644 --- a/tools/orca/src/models/read.rs +++ b/tools/orca/src/models/read.rs @@ -25,7 +25,7 @@ impl ActorReader { pub fn new(mut cha_rng: ChaCha8Rng, warmup_time_ms: u64) -> Self { let max_backoff_time_in_ms = warmup_time_ms - 1000; let randomised_backoff_time = - Duration::from_millis(cha_rng.gen_range(0..max_backoff_time_in_ms)); + Duration::from_millis(cha_rng.random_range(0..max_backoff_time_in_ms)); ActorReader { state: State::Unauthenticated, randomised_backoff_time, diff --git a/tools/orca/src/models/write.rs b/tools/orca/src/models/write.rs index 969f153ff..09da27c1a 100644 --- a/tools/orca/src/models/write.rs +++ b/tools/orca/src/models/write.rs @@ -26,7 +26,7 @@ impl ActorWriter { pub fn new(mut cha_rng: ChaCha8Rng, warmup_time_ms: u64) -> Self { let max_backoff_time_in_ms = 2 * warmup_time_ms / 3; let randomised_backoff_time = - Duration::from_millis(cha_rng.gen_range(0..max_backoff_time_in_ms)); + Duration::from_millis(cha_rng.random_range(0..max_backoff_time_in_ms)); ActorWriter { state: State::Unauthenticated, randomised_backoff_time, diff --git a/tools/orca/src/profile.rs b/tools/orca/src/profile.rs index 9b080d81d..d39a4c77b 100644 --- a/tools/orca/src/profile.rs +++ b/tools/orca/src/profile.rs @@ -1,6 +1,6 @@ use crate::error::Error; use crate::state::{GroupName, Model}; -use rand::{thread_rng, Rng}; +use rand::{rng, Rng}; use serde::de::{value, IntoDeserializer}; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -200,8 +200,8 @@ impl ProfileBuilder { } = self; let seed: u64 = seed.unwrap_or_else(|| { - let mut rng = thread_rng(); - rng.gen() + let mut rng = rng(); + rng.random() }); //TODO: Allow to specify group properties from the CLI diff --git a/tools/orca/src/run.rs b/tools/orca/src/run.rs index 393e6981f..c95187828 100644 --- a/tools/orca/src/run.rs +++ b/tools/orca/src/run.rs @@ -187,7 +187,7 @@ pub async fn execute(state: State, control_rx: broadcast::Receiver<Signal>) -> R }) }) .collect::<Result<Vec<_>, _>>()?; - let main_client_index = seeded_rng.gen_range(0..cloned_clients.len()); + let main_client_index = seeded_rng.random_range(0..cloned_clients.len()); let main_client = cloned_clients.remove(main_client_index); //note that cloned_clients now contains all other clients except the first one diff --git a/unix_integration/common/src/unix_config.rs b/unix_integration/common/src/unix_config.rs index 09dd7d069..3e89fbab5 100644 --- a/unix_integration/common/src/unix_config.rs +++ b/unix_integration/common/src/unix_config.rs @@ -694,7 +694,7 @@ mod tests { for file in PathBuf::from(&examples_dir) .canonicalize() - .expect(&format!("Can't find examples dir at {}", examples_dir)) + .unwrap_or_else(|_| panic!("Can't find examples dir at {}", examples_dir)) .read_dir() .expect("Can't read examples dir!") { diff --git a/unix_integration/nss_kanidm/src/tests.rs b/unix_integration/nss_kanidm/src/tests.rs index 59629026e..e3448e7c0 100644 --- a/unix_integration/nss_kanidm/src/tests.rs +++ b/unix_integration/nss_kanidm/src/tests.rs @@ -71,32 +71,32 @@ impl RequestOptions { fn nss_fallback_unavail() { let req_opt = RequestOptions::fallback_unavail(); let Response::Unavail = core::get_all_user_entries(req_opt) else { - unreachable!(); + panic!("unrecoverable"); }; let req_opt = RequestOptions::fallback_unavail(); let Response::Unavail = core::get_user_entry_by_uid(0, req_opt) else { - unreachable!(); + panic!("unrecoverable"); }; let req_opt = RequestOptions::fallback_unavail(); let Response::Unavail = core::get_user_entry_by_name("root".to_string(), req_opt) else { - unreachable!(); + panic!("unrecoverable"); }; let req_opt = RequestOptions::fallback_unavail(); let Response::Unavail = core::get_all_group_entries(req_opt) else { - unreachable!(); + panic!("unrecoverable"); }; let req_opt = RequestOptions::fallback_unavail(); let Response::Unavail = core::get_group_entry_by_gid(0, req_opt) else { - unreachable!(); + panic!("unrecoverable"); }; let req_opt = RequestOptions::fallback_unavail(); let Response::Unavail = core::get_group_entry_by_name("root".to_string(), req_opt) else { - unreachable!(); + panic!("unrecoverable"); }; } @@ -105,7 +105,7 @@ fn nss_fallback_all_user_entries() { let req_opt = RequestOptions::fallback_fixture(); let Response::Success(users) = core::get_all_user_entries(req_opt) else { - unreachable!(); + panic!("Failed to get all user entries"); }; assert_eq!(users.len(), 3); @@ -129,7 +129,7 @@ fn nss_fallback_all_user_entries() { fn nss_fallback_user_entry_by_uid() { let req_opt = RequestOptions::fallback_fixture(); let Response::Success(user) = core::get_user_entry_by_uid(0, req_opt) else { - unreachable!(); + panic!("Failed to get user entry by uid"); }; assert_eq!(user.name, "root"); @@ -139,7 +139,7 @@ fn nss_fallback_user_entry_by_uid() { let req_opt = RequestOptions::fallback_fixture(); let Response::Success(user) = core::get_user_entry_by_uid(1000, req_opt) else { - unreachable!(); + panic!("Failed to get user entry by uid"); }; assert_eq!(user.name, "tobias"); @@ -149,7 +149,7 @@ fn nss_fallback_user_entry_by_uid() { let req_opt = RequestOptions::fallback_fixture(); let Response::NotFound = core::get_user_entry_by_uid(10, req_opt) else { - unreachable!(); + panic!("Wrong result"); }; } @@ -157,7 +157,7 @@ fn nss_fallback_user_entry_by_uid() { fn nss_fallback_user_entry_by_name() { let req_opt = RequestOptions::fallback_fixture(); let Response::Success(user) = core::get_user_entry_by_name("root".to_string(), req_opt) else { - unreachable!(); + panic!("Failed to get user entry by name"); }; assert_eq!(user.name, "root"); @@ -167,7 +167,7 @@ fn nss_fallback_user_entry_by_name() { let req_opt = RequestOptions::fallback_fixture(); let Response::Success(user) = core::get_user_entry_by_name("ellie".to_string(), req_opt) else { - unreachable!(); + panic!("Failed to get user entry by name"); }; assert_eq!(user.name, "ellie"); @@ -177,7 +177,7 @@ fn nss_fallback_user_entry_by_name() { let req_opt = RequestOptions::fallback_fixture(); let Response::NotFound = core::get_user_entry_by_name("william".to_string(), req_opt) else { - unreachable!(); + panic!("Wrong result"); }; } @@ -186,7 +186,7 @@ fn nss_fallback_all_group_entries() { let req_opt = RequestOptions::fallback_fixture(); let Response::Success(groups) = core::get_all_group_entries(req_opt) else { - unreachable!(); + panic!("Failed to get all group entries"); }; assert_eq!(groups.len(), 3); @@ -207,7 +207,7 @@ fn nss_fallback_all_group_entries() { fn nss_fallback_group_entry_by_uid() { let req_opt = RequestOptions::fallback_fixture(); let Response::Success(group) = core::get_group_entry_by_gid(0, req_opt) else { - unreachable!(); + panic!("Failed to get group entry by gid"); }; assert_eq!(group.name, "root"); @@ -216,7 +216,7 @@ fn nss_fallback_group_entry_by_uid() { let req_opt = RequestOptions::fallback_fixture(); let Response::Success(group) = core::get_group_entry_by_gid(1000, req_opt) else { - unreachable!(); + panic!("Failed to get group entry by gid"); }; assert_eq!(group.name, "tobias"); @@ -225,7 +225,7 @@ fn nss_fallback_group_entry_by_uid() { let req_opt = RequestOptions::fallback_fixture(); let Response::NotFound = core::get_group_entry_by_gid(10, req_opt) else { - unreachable!(); + panic!("Wrong result"); }; } @@ -234,7 +234,7 @@ fn nss_fallback_group_entry_by_name() { let req_opt = RequestOptions::fallback_fixture(); let Response::Success(group) = core::get_group_entry_by_name("root".to_string(), req_opt) else { - unreachable!(); + panic!("Failed to get group entry by name"); }; assert_eq!(group.name, "root"); @@ -244,7 +244,7 @@ fn nss_fallback_group_entry_by_name() { let req_opt = RequestOptions::fallback_fixture(); let Response::Success(group) = core::get_group_entry_by_name("ellie".to_string(), req_opt) else { - unreachable!(); + panic!("Failed to get group entry by name"); }; assert_eq!(group.name, "ellie"); @@ -253,6 +253,6 @@ fn nss_fallback_group_entry_by_name() { let req_opt = RequestOptions::fallback_fixture(); let Response::NotFound = core::get_group_entry_by_name("william".to_string(), req_opt) else { - unreachable!(); + panic!("Wrong result"); }; } diff --git a/unix_integration/pam_kanidm/src/tests.rs b/unix_integration/pam_kanidm/src/tests.rs index 6ecf7a08c..31291997f 100644 --- a/unix_integration/pam_kanidm/src/tests.rs +++ b/unix_integration/pam_kanidm/src/tests.rs @@ -121,23 +121,17 @@ impl PamHandler for TestHandler { /// Display a message to the user. fn message(&self, _prompt: &str) -> PamResult<()> { let mut q = self.response_queue.lock().unwrap(); - match q.pop_front() { - e => { - eprintln!("{:?}", e); - panic!("Invalid event transition"); - } - } + let e = q.pop_front(); + eprintln!("{:?}", e); + panic!("Invalid event transition message"); } /// Display a device grant request to the user. fn message_device_grant(&self, _data: &DeviceAuthorizationResponse) -> PamResult<()> { let mut q = self.response_queue.lock().unwrap(); - match q.pop_front() { - e => { - eprintln!("{:?}", e); - panic!("Invalid event transition"); - } - } + let e = q.pop_front(); + eprintln!("{:?}", e); + panic!("Invalid event transition message_device_grant"); } /// Request a password from the user. @@ -154,22 +148,16 @@ impl PamHandler for TestHandler { fn prompt_for_pin(&self, _msg: Option<&str>) -> PamResult<Option<String>> { let mut q = self.response_queue.lock().unwrap(); - match q.pop_front() { - e => { - eprintln!("{:?}", e); - panic!("Invalid event transition"); - } - } + let e = q.pop_front(); + eprintln!("{:?}", e); + panic!("Invalid event transition prompt_for_pin"); } fn prompt_for_mfacode(&self) -> PamResult<Option<String>> { - let mut q = self.response_queue.lock().unwrap(); - match q.pop_front() { - e => { - eprintln!("{:?}", e); - panic!("Invalid event transition"); - } - } + let mut q = self.response_queue.lock().expect("Failed to lock mutex"); + let e = q.pop_front(); + eprintln!("{:?}", e); + panic!("Invalid event transition prompt_for_mfacode"); } } diff --git a/unix_integration/resolver/tests/cache_layer_test.rs b/unix_integration/resolver/tests/cache_layer_test.rs index ad85f48ec..30b659e07 100644 --- a/unix_integration/resolver/tests/cache_layer_test.rs +++ b/unix_integration/resolver/tests/cache_layer_test.rs @@ -1189,7 +1189,7 @@ async fn test_cache_extend_group_members() { assert!(groups.iter().any(|group| { group.name == "extensible_group" && group.members.as_slice() - == &[ + == [ "local_account".to_string(), "testaccount1@idm.example.com".to_string(), ]