mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 20:47:01 +01:00
20220911 api tokens (#1071)
This commit is contained in:
parent
ad468f0dfa
commit
082464f786
201
Cargo.lock
generated
201
Cargo.lock
generated
|
@ -75,18 +75,18 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "0.7.18"
|
version = "0.7.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "android_system_properties"
|
name = "android_system_properties"
|
||||||
version = "0.1.4"
|
version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d7ed72e1635e121ca3e79420540282af22da58be50de153d36f81ddc6b83aa9e"
|
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
@ -108,9 +108,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.62"
|
version = "1.0.65"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1485d4d2cc45e7b201ee3767015c96faa5904387c9d87c6efdd0fb511f12d305"
|
checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anymap2"
|
name = "anymap2"
|
||||||
|
@ -251,9 +251,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-io"
|
name = "async-io"
|
||||||
version = "1.8.0"
|
version = "1.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0ab006897723d9352f63e2b13047177c3982d8d79709d713ce7747a8f19fd1b0"
|
checksum = "83e21f3a490c72b3b0cf44962180e60045de2925d8dff97918f7ee43c8f637c7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"concurrent-queue",
|
"concurrent-queue",
|
||||||
|
@ -429,7 +429,7 @@ dependencies = [
|
||||||
"serde_bytes",
|
"serde_bytes",
|
||||||
"serde_cbor",
|
"serde_cbor",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2 0.10.2",
|
"sha2 0.10.6",
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -542,9 +542,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
version = "0.10.2"
|
version = "0.10.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
|
checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"generic-array 0.14.6",
|
"generic-array 0.14.6",
|
||||||
]
|
]
|
||||||
|
@ -755,12 +755,13 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "compact_jwt"
|
name = "compact_jwt"
|
||||||
version = "0.2.4"
|
version = "0.2.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f9417bb4f581b7a5e08fabb4398b910064363bbfd7b75a10d1da3bfff3ef9b36"
|
checksum = "5656b98b1584764a52906e67caec20dfb9b0179ac2052d0d5937b083bc39a120"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.13.0",
|
"base64 0.13.0",
|
||||||
"base64urlsafedata",
|
"base64urlsafedata",
|
||||||
|
"hex",
|
||||||
"openssl",
|
"openssl",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -771,16 +772,18 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "concread"
|
name = "concread"
|
||||||
version = "0.3.7"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "91896ebca83fd5ac051ee12ab048a4bcfd8c887397127c8f883b77b05288e935"
|
checksum = "6acd004617e219ee07c26b7f6f5f6b4d489c5595e432b87d0bbd8d88db1eebd3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"crossbeam",
|
"crossbeam",
|
||||||
"crossbeam-epoch",
|
"crossbeam-epoch",
|
||||||
"lru 0.7.8",
|
"lru 0.7.8",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
|
"sptr",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -890,9 +893,9 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.4"
|
version = "0.2.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc948ebb96241bb40ab73effeb80d9f93afaad49359d159a5e61be51619fe813"
|
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
@ -924,7 +927,7 @@ dependencies = [
|
||||||
"ciborium",
|
"ciborium",
|
||||||
"clap",
|
"clap",
|
||||||
"criterion-plot",
|
"criterion-plot",
|
||||||
"itertools 0.10.3",
|
"itertools 0.10.5",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"oorandom",
|
"oorandom",
|
||||||
|
@ -945,7 +948,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
|
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cast",
|
"cast",
|
||||||
"itertools 0.10.3",
|
"itertools 0.10.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1234,11 +1237,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.10.3"
|
version = "0.10.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506"
|
checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block-buffer 0.10.2",
|
"block-buffer 0.10.3",
|
||||||
"crypto-common",
|
"crypto-common",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1376,9 +1379,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fernet"
|
name = "fernet"
|
||||||
version = "0.1.4"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "93804560e638370a8be6d59ce71ed803e55e230abdbf42598e666b41adda9b1f"
|
checksum = "c6dedfc944f4ac38cac8b74cb1c7b4fb73c175db232d6fa98e9bd1fd81908b89"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.13.0",
|
"base64 0.13.0",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
|
@ -1838,9 +1841,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashlink"
|
name = "hashlink"
|
||||||
version = "0.8.0"
|
version = "0.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d452c155cb93fecdfb02a73dd57b5d8e442c2063bd7aac72f1bc5e4263a43086"
|
checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
@ -1860,6 +1863,12 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hex"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hkdf"
|
name = "hkdf"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
|
@ -1948,9 +1957,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httparse"
|
name = "httparse"
|
||||||
version = "1.7.1"
|
version = "1.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c"
|
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httpdate"
|
name = "httpdate"
|
||||||
|
@ -1997,9 +2006,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iana-time-zone"
|
name = "iana-time-zone"
|
||||||
version = "0.1.46"
|
version = "0.1.50"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ad2bfd338099682614d3ee3fe0cd72e0b6a41ca6a87f6a74a3bd593c91650501"
|
checksum = "fd911b35d940d2bd0bea0f9100068e5b97b51a1cbe13d13382f132e0365257a0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android_system_properties",
|
"android_system_properties",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
|
@ -2094,9 +2103,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.10.3"
|
version = "0.10.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
|
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"either",
|
"either",
|
||||||
]
|
]
|
||||||
|
@ -2115,9 +2124,9 @@ checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jobserver"
|
name = "jobserver"
|
||||||
version = "0.1.24"
|
version = "0.1.25"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa"
|
checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
@ -2198,6 +2207,7 @@ dependencies = [
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"time 0.2.27",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
@ -2365,9 +2375,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.132"
|
version = "0.2.133"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
|
checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libgit2-sys"
|
name = "libgit2-sys"
|
||||||
|
@ -2459,9 +2469,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.8"
|
version = "0.4.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390"
|
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"scopeguard",
|
"scopeguard",
|
||||||
|
@ -2558,9 +2568,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.5.3"
|
version = "0.5.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
|
checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"adler",
|
"adler",
|
||||||
]
|
]
|
||||||
|
@ -2717,7 +2727,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_path_to_error",
|
"serde_path_to_error",
|
||||||
"sha2 0.10.2",
|
"sha2 0.10.6",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
@ -2733,9 +2743,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.13.1"
|
version = "1.15.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e"
|
checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oncemutex"
|
name = "oncemutex"
|
||||||
|
@ -2975,9 +2985,9 @@ checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "plotters"
|
name = "plotters"
|
||||||
version = "0.3.3"
|
version = "0.3.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "716b4eeb6c4a1d3ecc956f75b43ec2e8e8ba80026413e70a3f41fd3313d3492b"
|
checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"plotters-backend",
|
"plotters-backend",
|
||||||
|
@ -3182,7 +3192,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"rand_chacha 0.3.1",
|
"rand_chacha 0.3.1",
|
||||||
"rand_core 0.6.3",
|
"rand_core 0.6.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3202,7 +3212,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ppv-lite86",
|
"ppv-lite86",
|
||||||
"rand_core 0.6.3",
|
"rand_core 0.6.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3216,9 +3226,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_core"
|
name = "rand_core"
|
||||||
version = "0.6.3"
|
version = "0.6.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.2.7",
|
"getrandom 0.2.7",
|
||||||
]
|
]
|
||||||
|
@ -3325,9 +3335,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.11.11"
|
version = "0.11.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92"
|
checksum = "431949c384f4e2ae07605ccaa56d1d9d2ecdb5cadd4f9577ccfab29f2e5149fc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.13.0",
|
"base64 0.13.0",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
@ -3343,10 +3353,10 @@ dependencies = [
|
||||||
"hyper-tls",
|
"hyper-tls",
|
||||||
"ipnet",
|
"ipnet",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"lazy_static",
|
|
||||||
"log",
|
"log",
|
||||||
"mime",
|
"mime",
|
||||||
"native-tls",
|
"native-tls",
|
||||||
|
"once_cell",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite 0.2.9",
|
"pin-project-lite 0.2.9",
|
||||||
"proc-macro-hack",
|
"proc-macro-hack",
|
||||||
|
@ -3571,9 +3581,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.144"
|
version = "1.0.145"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860"
|
checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
@ -3621,10 +3631,20 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_cbor_2"
|
||||||
version = "1.0.144"
|
version = "0.12.0-dev"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00"
|
checksum = "b46d75f449e01f1eddbe9b00f432d616fbbd899b809c837d0fbc380496a0dd55"
|
||||||
|
dependencies = [
|
||||||
|
"half",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.145"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -3725,13 +3745,13 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha2"
|
name = "sha2"
|
||||||
version = "0.10.2"
|
version = "0.10.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676"
|
checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"cpufeatures",
|
"cpufeatures",
|
||||||
"digest 0.10.3",
|
"digest 0.10.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3833,14 +3853,20 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.4.6"
|
version = "0.4.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "10c98bba371b9b22a71a9414e420f92ddeb2369239af08200816169d5e2dd7aa"
|
checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sptr"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sshkeys"
|
name = "sshkeys"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
|
@ -3939,9 +3965,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.99"
|
version = "1.0.100"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13"
|
checksum = "52205623b1b0f064a4e71182c3b18ae902267282930c6d5462c91b859668426e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -4001,18 +4027,18 @@ checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.32"
|
version = "1.0.35"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994"
|
checksum = "c53f98874615aea268107765aa1ed8f6116782501d18e53d08b471733bea6c85"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "1.0.32"
|
version = "1.0.35"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21"
|
checksum = "f8b463991b4eab2d801e724172285ec4195c650e8ec79b149e6c2a8e6dd3f783"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -4380,30 +4406,30 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.3"
|
version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
|
checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-normalization"
|
name = "unicode-normalization"
|
||||||
version = "0.1.21"
|
version = "0.1.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6"
|
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"tinyvec",
|
"tinyvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-width"
|
name = "unicode-width"
|
||||||
version = "0.1.9"
|
version = "0.1.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
|
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
version = "0.2.3"
|
version = "0.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
|
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "universal-hash"
|
name = "universal-hash"
|
||||||
|
@ -4677,9 +4703,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webauthn-authenticator-rs"
|
name = "webauthn-authenticator-rs"
|
||||||
version = "0.4.5"
|
version = "0.4.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4f0a0f2b3f25205903b27ecea9012dcff4ad272ee792a41ab46dae6bbabefd4d"
|
checksum = "d30dcdffd0c5dfa110246701399efcc09962c1bb565f61a5d7fe995645ff6f21"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"authenticator-ctap2-2021",
|
"authenticator-ctap2-2021",
|
||||||
"base64urlsafedata",
|
"base64urlsafedata",
|
||||||
|
@ -4687,7 +4713,7 @@ dependencies = [
|
||||||
"openssl",
|
"openssl",
|
||||||
"rpassword 5.0.1",
|
"rpassword 5.0.1",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_cbor",
|
"serde_cbor_2",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tracing",
|
"tracing",
|
||||||
"url",
|
"url",
|
||||||
|
@ -4696,9 +4722,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webauthn-rs"
|
name = "webauthn-rs"
|
||||||
version = "0.4.5"
|
version = "0.4.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4b813b9663ddc0b5594b5c54dec399eba428c199a8bb75ed6fde757ec2deca82"
|
checksum = "8d5984278a28dc397c565fd79ee1aba67b74fa365c4eea489f7258b3f902422f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64urlsafedata",
|
"base64urlsafedata",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -4710,9 +4736,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webauthn-rs-core"
|
name = "webauthn-rs-core"
|
||||||
version = "0.4.5"
|
version = "0.4.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b68452d453abbd5bb7101fa5c97698940dbdea5cdc7f49a4bea1d546f3dc0f46"
|
checksum = "f6528b4769d8fbe020e0e8c2e66bab6035982a2e9c3f8dac384120718f4763f4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.13.0",
|
"base64 0.13.0",
|
||||||
"base64urlsafedata",
|
"base64urlsafedata",
|
||||||
|
@ -4722,7 +4748,7 @@ dependencies = [
|
||||||
"openssl",
|
"openssl",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_cbor",
|
"serde_cbor_2",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
@ -4734,13 +4760,14 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webauthn-rs-proto"
|
name = "webauthn-rs-proto"
|
||||||
version = "0.4.6"
|
version = "0.4.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "adfdd8694503710db9d9948ea380a3c57fec4371cea6a5a906b2d72caf61838d"
|
checksum = "09e9a265574f8d7b8f8c94c4488bb491a82d7b8e183003a4512d3dceb88efd98"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64urlsafedata",
|
"base64urlsafedata",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde-wasm-bindgen 0.4.3",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"url",
|
"url",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
|
@ -4980,7 +5007,7 @@ checksum = "568becce91e872373a4b33f24ddc67e5280ae2536ccb8c9d22a25d398b72c8b0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"derive_builder",
|
"derive_builder",
|
||||||
"fancy-regex",
|
"fancy-regex",
|
||||||
"itertools 0.10.3",
|
"itertools 0.10.5",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"quick-error",
|
"quick-error",
|
||||||
|
|
19
Cargo.toml
19
Cargo.toml
|
@ -25,22 +25,21 @@ exclude = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
[patch.crates-io]
|
[workspace.dependencies]
|
||||||
|
# compact_jwt = { path = "../compact_jwt" }
|
||||||
# concread = { path = "../concread" }
|
# concread = { path = "../concread" }
|
||||||
# concread = { git = "https://github.com/kanidm/concread.git" }
|
|
||||||
|
|
||||||
# idlset = { path = "../idlset" }
|
# idlset = { path = "../idlset" }
|
||||||
|
|
||||||
# ldap3_server = { path = "../ldap3_server" }
|
# ldap3_server = { path = "../ldap3_server" }
|
||||||
|
|
||||||
|
|
||||||
|
webauthn-authenticator-rs = "0.4.7"
|
||||||
|
webauthn-rs = "0.4.7"
|
||||||
|
webauthn-rs-core = "0.4.7"
|
||||||
|
webauthn-rs-proto = "0.4.7"
|
||||||
|
# webauthn-authenticator-rs = { path = "../webauthn-rs/webauthn-authenticator-rs" }
|
||||||
# webauthn-rs = { path = "../webauthn-rs/webauthn-rs" }
|
# webauthn-rs = { path = "../webauthn-rs/webauthn-rs" }
|
||||||
# webauthn-rs-core = { path = "../webauthn-rs/webauthn-rs-core" }
|
# webauthn-rs-core = { path = "../webauthn-rs/webauthn-rs-core" }
|
||||||
# webauthn-rs-proto = { path = "../webauthn-rs/webauthn-rs-proto" }
|
# webauthn-rs-proto = { path = "../webauthn-rs/webauthn-rs-proto" }
|
||||||
# webauthn-authenticator-rs = { path = "../webauthn-rs/webauthn-authenticator-rs" }
|
|
||||||
|
|
||||||
# compact_jwt = { path = "../compact_jwt" }
|
|
||||||
# compact_jwt = { git = "https://github.com/kanidm/compact-jwt.git" }
|
|
||||||
|
|
||||||
|
|
||||||
# enshrinken the WASMs
|
# enshrinken the WASMs
|
||||||
[profile.release.package.kanidmd_web_ui]
|
[profile.release.package.kanidmd_web_ui]
|
||||||
|
@ -48,7 +47,5 @@ exclude = [
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
# optimization for size ( more aggressive )
|
# optimization for size ( more aggressive )
|
||||||
opt-level = 'z'
|
opt-level = 'z'
|
||||||
# optimization for size
|
|
||||||
# opt-level = 's'
|
|
||||||
# link time optimization using using whole-program analysis
|
# link time optimization using using whole-program analysis
|
||||||
# lto = true
|
# lto = true
|
||||||
|
|
4
iam_migrations/freeipa/00config-mod.ldif
Normal file
4
iam_migrations/freeipa/00config-mod.ldif
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
dn: cn=Retro Changelog Plugin,cn=plugins,cn=config
|
||||||
|
changetype: modify
|
||||||
|
add: nsslapd-include-suffix
|
||||||
|
nsslapd-include-suffix: cn=accounts,dc=dev,dc=kanidm,dc=com
|
6
iam_migrations/freeipa/01test-sync.sh
Normal file
6
iam_migrations/freeipa/01test-sync.sh
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
ldapsearch -H ldap://localhost -D 'cn=Directory Manager' -w $(cat ipa.pw) -b 'cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au' -x -E \!sync=ro
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -177,7 +177,52 @@ kanidm service-account create demo_service "Demonstration Service" --name admin
|
||||||
kanidm service-account get demo_service --name admin
|
kanidm service-account get demo_service --name admin
|
||||||
```
|
```
|
||||||
|
|
||||||
## Resetting Service Account Credentials
|
## Using API Tokens with Service Accounts
|
||||||
|
|
||||||
|
Service accounts can have api tokens generated and associated with them. These tokens can be used for
|
||||||
|
identification of the service account, and for granting extended access rights where the service
|
||||||
|
account may previously have not had the access. Additionally service accounts can have expiry times
|
||||||
|
and other auditing information attached.
|
||||||
|
|
||||||
|
To show api tokens for a service account:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
kanidm service-account api-token status --name admin ACCOUNT_ID
|
||||||
|
kanidm service-account api-token status --name admin demo_service
|
||||||
|
```
|
||||||
|
|
||||||
|
To generate a new api token:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
kanidm service-account api-token generate --name admin ACCOUNT_ID LABEL [EXPIRY]
|
||||||
|
kanidm service-account api-token generate --name admin demo_service "Test Token"
|
||||||
|
kanidm service-account api-token generate --name admin demo_service "Test Token" 2020-09-25T11:22:02+10:00
|
||||||
|
```
|
||||||
|
|
||||||
|
To destroy (revoke) an api token you will need it's token id. This can be shown with the "status"
|
||||||
|
command.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
kanidm service-account api-token destroy --name admin ACCOUNT_ID TOKEN_ID
|
||||||
|
kanidm service-account api-token destroy --name admin demo_service 4de2a4e9-e06a-4c5e-8a1b-33f4e7dd5dc7
|
||||||
|
```
|
||||||
|
|
||||||
|
Api tokens can also be used to gain extended search permissions with LDAP. To do this you can bind
|
||||||
|
with a dn of "" (empty string) and provide the api token in the password.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
ldapwhoami -H ldaps://URL -x -D "" -w "TOKEN"
|
||||||
|
ldapwhoami -H ldaps://idm.example.com -x -D "" -w "..."
|
||||||
|
# u: demo_service@idm.example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
## Resetting Service Account Credentials (Deprecated)
|
||||||
|
|
||||||
|
{{#template
|
||||||
|
templates/kani-warning.md
|
||||||
|
imagepath=images
|
||||||
|
text=Api Tokens are a better method to manage credentials for service accounts, and passwords may be removed in the future!
|
||||||
|
}}
|
||||||
|
|
||||||
Service accounts can not have their credentials interactively updated in the same manner as
|
Service accounts can not have their credentials interactively updated in the same manner as
|
||||||
persons. Service accounts may only have server side generated high entropy passwords.
|
persons. Service accounts may only have server side generated high entropy passwords.
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
name = "kanidm_client"
|
name = "kanidm_client"
|
||||||
version = "1.1.0-alpha.9"
|
version = "1.1.0-alpha.9"
|
||||||
authors = ["William Brown <william@blackhats.net.au>"]
|
authors = ["William Brown <william@blackhats.net.au>"]
|
||||||
rust-version = "1.59"
|
rust-version = "1.64"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
description = "Kanidm Client Library"
|
description = "Kanidm Client Library"
|
||||||
|
@ -16,9 +16,10 @@ reqwest = { version = "^0.11.11", features=["cookies", "json", "native-tls"] }
|
||||||
kanidm_proto = { path = "../kanidm_proto", version = "1.1.0-alpha.8" }
|
kanidm_proto = { path = "../kanidm_proto", version = "1.1.0-alpha.8" }
|
||||||
serde = { version = "^1.0.142", features = ["derive"] }
|
serde = { version = "^1.0.142", features = ["derive"] }
|
||||||
serde_json = "^1.0.83"
|
serde_json = "^1.0.83"
|
||||||
|
time = { version = "=0.2.27", features = ["serde", "std"] }
|
||||||
tokio = { version = "^1.21.1", features = ["rt", "net", "time", "macros", "sync", "signal"] }
|
tokio = { version = "^1.21.1", features = ["rt", "net", "time", "macros", "sync", "signal"] }
|
||||||
toml = "^0.5.9"
|
toml = "^0.5.9"
|
||||||
uuid = { version = "^1.1.2", features = ["serde", "v4"] }
|
uuid = { version = "^1.1.2", features = ["serde", "v4"] }
|
||||||
url = { version = "^2.3.1", features = ["serde"] }
|
url = { version = "^2.3.1", features = ["serde"] }
|
||||||
webauthn-rs-proto = { version = "0.4.6", features = ["wasm"] }
|
webauthn-rs-proto = { workspace = true, features = ["wasm"] }
|
||||||
|
|
||||||
|
|
|
@ -1168,7 +1168,7 @@ impl KanidmClient {
|
||||||
self.perform_get_request("/v1/auth/valid").await
|
self.perform_get_request("/v1/auth/valid").await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn whoami(&self) -> Result<Option<(Entry, UserAuthToken)>, ClientError> {
|
pub async fn whoami(&self) -> Result<Option<Entry>, ClientError> {
|
||||||
let whoami_dest = [self.addr.as_str(), "/v1/self"].concat();
|
let whoami_dest = [self.addr.as_str(), "/v1/self"].concat();
|
||||||
// format!("{}/v1/self", self.addr);
|
// format!("{}/v1/self", self.addr);
|
||||||
debug!("{:?}", whoami_dest);
|
debug!("{:?}", whoami_dest);
|
||||||
|
@ -1211,7 +1211,7 @@ impl KanidmClient {
|
||||||
.await
|
.await
|
||||||
.map_err(|e| ClientError::JsonDecode(e, opid))?;
|
.map_err(|e| ClientError::JsonDecode(e, opid))?;
|
||||||
|
|
||||||
Ok(Some((r.youare, r.uat)))
|
Ok(Some(r.youare))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Raw DB actions
|
// Raw DB actions
|
||||||
|
|
|
@ -3,7 +3,10 @@ use crate::KanidmClient;
|
||||||
use kanidm_proto::v1::AccountUnixExtend;
|
use kanidm_proto::v1::AccountUnixExtend;
|
||||||
use kanidm_proto::v1::CredentialStatus;
|
use kanidm_proto::v1::CredentialStatus;
|
||||||
use kanidm_proto::v1::Entry;
|
use kanidm_proto::v1::Entry;
|
||||||
|
use kanidm_proto::v1::{ApiToken, ApiTokenGenerate};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
impl KanidmClient {
|
impl KanidmClient {
|
||||||
pub async fn idm_service_account_list(&self) -> Result<Vec<Entry>, ClientError> {
|
pub async fn idm_service_account_list(&self) -> Result<Vec<Entry>, ClientError> {
|
||||||
|
@ -193,4 +196,45 @@ impl KanidmClient {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn idm_service_account_list_api_token(
|
||||||
|
&self,
|
||||||
|
id: &str,
|
||||||
|
) -> Result<Vec<ApiToken>, ClientError> {
|
||||||
|
self.perform_get_request(format!("/v1/service_account/{}/_api_token", id).as_str())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn idm_service_account_generate_api_token(
|
||||||
|
&self,
|
||||||
|
id: &str,
|
||||||
|
label: &str,
|
||||||
|
expiry: Option<OffsetDateTime>,
|
||||||
|
) -> Result<String, ClientError> {
|
||||||
|
let new_token = ApiTokenGenerate {
|
||||||
|
label: label.to_string(),
|
||||||
|
expiry,
|
||||||
|
};
|
||||||
|
self.perform_post_request(
|
||||||
|
format!("/v1/service_account/{}/_api_token", id).as_str(),
|
||||||
|
new_token,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn idm_service_account_destroy_api_token(
|
||||||
|
&self,
|
||||||
|
id: &str,
|
||||||
|
token_id: Uuid,
|
||||||
|
) -> Result<(), ClientError> {
|
||||||
|
self.perform_delete_request(
|
||||||
|
format!(
|
||||||
|
"/v1/service_account/{}/_api_token/{}",
|
||||||
|
id,
|
||||||
|
&token_id.to_string()
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
name = "kanidm_proto"
|
name = "kanidm_proto"
|
||||||
version = "1.1.0-alpha.9"
|
version = "1.1.0-alpha.9"
|
||||||
authors = ["William Brown <william@blackhats.net.au>"]
|
authors = ["William Brown <william@blackhats.net.au>"]
|
||||||
rust-version = "1.59"
|
rust-version = "1.64"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
description = "Kanidm Protocol Bindings for serde"
|
description = "Kanidm Protocol Bindings for serde"
|
||||||
|
@ -24,7 +24,7 @@ time = { version = "=0.2.27", features = ["serde", "std"] }
|
||||||
url = { version = "^2.3.1", features = ["serde"] }
|
url = { version = "^2.3.1", features = ["serde"] }
|
||||||
urlencoding = "2.1.2"
|
urlencoding = "2.1.2"
|
||||||
uuid = { version = "^1.1.2", features = ["serde"] }
|
uuid = { version = "^1.1.2", features = ["serde"] }
|
||||||
webauthn-rs-proto = "0.4.6"
|
webauthn-rs-proto.workspace = true
|
||||||
|
|
||||||
[target.'cfg(not(target_family = "wasm"))'.dependencies]
|
[target.'cfg(not(target_family = "wasm"))'.dependencies]
|
||||||
last-git-commit = "0.2.0"
|
last-git-commit = "0.2.0"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use std::collections::BTreeSet;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use webauthn_rs_proto::{
|
use webauthn_rs_proto::{
|
||||||
|
@ -238,6 +239,7 @@ pub enum OperationError {
|
||||||
ResourceLimit,
|
ResourceLimit,
|
||||||
QueueDisconnected,
|
QueueDisconnected,
|
||||||
Webauthn,
|
Webauthn,
|
||||||
|
#[serde(with = "time::serde::timestamp")]
|
||||||
Wait(time::OffsetDateTime),
|
Wait(time::OffsetDateTime),
|
||||||
ReplReplayFailure,
|
ReplReplayFailure,
|
||||||
ReplEntryNotChanged,
|
ReplEntryNotChanged,
|
||||||
|
@ -261,13 +263,13 @@ impl PartialEq for OperationError {
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Group {
|
pub struct Group {
|
||||||
pub name: String,
|
pub spn: String,
|
||||||
pub uuid: String,
|
pub uuid: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Group {
|
impl fmt::Display for Group {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "[ name: {}, ", self.name)?;
|
write!(f, "[ spn: {}, ", self.spn)?;
|
||||||
write!(f, "uuid: {} ]", self.uuid)
|
write!(f, "uuid: {} ]", self.uuid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -313,37 +315,36 @@ impl fmt::Display for AuthType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum UiHint {
|
||||||
|
PosixAccount,
|
||||||
|
}
|
||||||
|
|
||||||
/// The currently authenticated user, and any required metadata for them
|
/// The currently authenticated user, and any required metadata for them
|
||||||
/// to properly authorise them. This is similar in nature to oauth and the krb
|
/// to properly authorise them. This is similar in nature to oauth and the krb
|
||||||
/// PAC/PAD structures. Currently we only use this internally, but we should
|
/// PAC/PAD structures. This information is transparent to clients and CAN
|
||||||
/// consider making it "parseable" by the client so they can have per-session
|
/// be parsed by them!
|
||||||
/// group/authorisation data.
|
|
||||||
///
|
///
|
||||||
/// This structure and how it works will *very much* change over time from this
|
/// This structure and how it works will *very much* change over time from this
|
||||||
/// point onward!
|
/// point onward! This means on updates, that sessions will invalidate in many
|
||||||
///
|
/// cases.
|
||||||
/// It's likely that this must have a relationship to the server's user structure
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
/// and to the Entry so that filters or access controls can be applied.
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub struct UserAuthToken {
|
pub struct UserAuthToken {
|
||||||
pub session_id: Uuid,
|
pub session_id: Uuid,
|
||||||
pub auth_type: AuthType,
|
pub auth_type: AuthType,
|
||||||
// When this token should be considered expired. Interpretation
|
// When this token should be considered expired. Interpretation
|
||||||
// may depend on the client application.
|
// may depend on the client application.
|
||||||
|
#[serde(with = "time::serde::timestamp")]
|
||||||
pub expiry: time::OffsetDateTime,
|
pub expiry: time::OffsetDateTime,
|
||||||
pub uuid: Uuid,
|
pub uuid: Uuid,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub displayname: String,
|
pub displayname: String,
|
||||||
pub spn: String,
|
pub spn: String,
|
||||||
pub mail_primary: Option<String>,
|
pub mail_primary: Option<String>,
|
||||||
// pub groups: Vec<Group>,
|
pub groups: Vec<Group>,
|
||||||
// Should we just retrieve these inside the server instead of in the uat?
|
pub ui_hints: BTreeSet<UiHint>,
|
||||||
// or do we want per-session limit capabilities?
|
|
||||||
pub lim_uidx: bool,
|
|
||||||
pub lim_rmax: usize,
|
|
||||||
pub lim_pmax: usize,
|
|
||||||
pub lim_fmax: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for UserAuthToken {
|
impl fmt::Display for UserAuthToken {
|
||||||
|
@ -351,11 +352,11 @@ impl fmt::Display for UserAuthToken {
|
||||||
// writeln!(f, "name: {}", self.name)?;
|
// writeln!(f, "name: {}", self.name)?;
|
||||||
writeln!(f, "spn: {}", self.spn)?;
|
writeln!(f, "spn: {}", self.spn)?;
|
||||||
writeln!(f, "uuid: {}", self.uuid)?;
|
writeln!(f, "uuid: {}", self.uuid)?;
|
||||||
/*
|
|
||||||
writeln!(f, "display: {}", self.displayname)?;
|
writeln!(f, "display: {}", self.displayname)?;
|
||||||
for group in &self.groups {
|
for group in &self.groups {
|
||||||
writeln!(f, "group: {:?}", group.name)?;
|
writeln!(f, "group: {:?}", group.spn)?;
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
for claim in &self.claims {
|
for claim in &self.claims {
|
||||||
writeln!(f, "claim: {:?}", claim)?;
|
writeln!(f, "claim: {:?}", claim)?;
|
||||||
}
|
}
|
||||||
|
@ -364,6 +365,65 @@ impl fmt::Display for UserAuthToken {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq for UserAuthToken {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.session_id == other.session_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for UserAuthToken {}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub struct ApiToken {
|
||||||
|
// The account this is associated with.
|
||||||
|
pub account_id: Uuid,
|
||||||
|
pub token_id: Uuid,
|
||||||
|
pub label: String,
|
||||||
|
#[serde(with = "time::serde::timestamp::option")]
|
||||||
|
pub expiry: Option<time::OffsetDateTime>,
|
||||||
|
#[serde(with = "time::serde::timestamp")]
|
||||||
|
pub issued_at: time::OffsetDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ApiToken {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
writeln!(f, "account_id: {}", self.account_id)?;
|
||||||
|
writeln!(f, "token_id: {}", self.token_id)?;
|
||||||
|
writeln!(f, "label: {}", self.label)?;
|
||||||
|
writeln!(f, "issued at: {}", self.issued_at)?;
|
||||||
|
if let Some(expiry) = self.expiry {
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"token expiry: {}",
|
||||||
|
expiry
|
||||||
|
.to_offset(
|
||||||
|
time::UtcOffset::try_current_local_offset().unwrap_or(time::UtcOffset::UTC),
|
||||||
|
)
|
||||||
|
.format(time::Format::Rfc3339)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
writeln!(f, "token expiry: never")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for ApiToken {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.token_id == other.token_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for ApiToken {}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub struct ApiTokenGenerate {
|
||||||
|
pub label: String,
|
||||||
|
#[serde(with = "time::serde::timestamp::option")]
|
||||||
|
pub expiry: Option<time::OffsetDateTime>,
|
||||||
|
}
|
||||||
|
|
||||||
// UAT will need a downcast to Entry, which adds in the claims to the entry
|
// UAT will need a downcast to Entry, which adds in the claims to the entry
|
||||||
// for the purpose of filtering.
|
// for the purpose of filtering.
|
||||||
|
|
||||||
|
@ -981,58 +1041,15 @@ pub struct CUStatus {
|
||||||
pub mfaregstate: CURegState,
|
pub mfaregstate: CURegState,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Recycle Requests area */
|
|
||||||
|
|
||||||
// Only two actions on recycled is possible. Search and Revive.
|
|
||||||
|
|
||||||
/*
|
|
||||||
pub struct SearchRecycledRequest {
|
|
||||||
pub filter: Filter,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SearchRecycledRequest {
|
|
||||||
pub fn new(filter: Filter) -> Self {
|
|
||||||
SearchRecycledRequest { filter }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Need a search response here later.
|
|
||||||
|
|
||||||
/*
|
|
||||||
pub struct ReviveRecycledRequest {
|
|
||||||
pub filter: Filter,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ReviveRecycledRequest {
|
|
||||||
pub fn new(filter: Filter) -> Self {
|
|
||||||
ReviveRecycledRequest { filter }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// This doesn't need seralise because it's only accessed via a "get".
|
|
||||||
/*
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct WhoamiRequest {}
|
|
||||||
|
|
||||||
impl WhoamiRequest {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||||
pub struct WhoamiResponse {
|
pub struct WhoamiResponse {
|
||||||
// Should we just embed the entry? Or destructure it?
|
// Should we just embed the entry? Or destructure it?
|
||||||
pub youare: Entry,
|
pub youare: Entry,
|
||||||
pub uat: UserAuthToken,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WhoamiResponse {
|
impl WhoamiResponse {
|
||||||
pub fn new(e: Entry, uat: UserAuthToken) -> Self {
|
pub fn new(youare: Entry) -> Self {
|
||||||
WhoamiResponse { youare: e, uat }
|
WhoamiResponse { youare }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
name = "kanidm_tools"
|
name = "kanidm_tools"
|
||||||
version = "1.1.0-alpha.9"
|
version = "1.1.0-alpha.9"
|
||||||
authors = ["William Brown <william@blackhats.net.au>"]
|
authors = ["William Brown <william@blackhats.net.au>"]
|
||||||
rust-version = "1.59"
|
rust-version = "1.64"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
default-run = "kanidm"
|
default-run = "kanidm"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
|
@ -47,9 +47,10 @@ tracing-subscriber = { version = "^0.3.14", features = ["env-filter", "fmt"] }
|
||||||
tokio = { version = "^1.21.1", features = ["rt", "macros"] }
|
tokio = { version = "^1.21.1", features = ["rt", "macros"] }
|
||||||
url = { version = "^2.3.1", features = ["serde"] }
|
url = { version = "^2.3.1", features = ["serde"] }
|
||||||
uuid = "^1.1.2"
|
uuid = "^1.1.2"
|
||||||
webauthn-authenticator-rs = { version = "0.4.5", features = ["u2fhid"] }
|
webauthn-authenticator-rs = { workspace = true, features = ["u2fhid"] }
|
||||||
zxcvbn = "^2.2.1"
|
zxcvbn = "^2.2.1"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
clap = { version = "^3.2", features = ["derive"] }
|
clap = { version = "^3.2", features = ["derive"] }
|
||||||
clap_complete = { version = "^3.2.5"}
|
clap_complete = { version = "^3.2.5"}
|
||||||
|
uuid = "^1.1.2"
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
use clap::{CommandFactory, Parser};
|
use clap::{CommandFactory, Parser};
|
||||||
use clap_complete::{generate_to, Shell};
|
use clap_complete::{generate_to, Shell};
|
||||||
|
|
|
@ -115,7 +115,7 @@ impl CommonOpt {
|
||||||
// Is the token (probably) valid?
|
// Is the token (probably) valid?
|
||||||
match jwtu
|
match jwtu
|
||||||
.validate_embeded()
|
.validate_embeded()
|
||||||
.map(|jws: Jws<UserAuthToken>| jws.inner)
|
.map(|jws: Jws<UserAuthToken>| jws.into_inner())
|
||||||
{
|
{
|
||||||
Ok(uat) => {
|
Ok(uat) => {
|
||||||
if time::OffsetDateTime::now_utc() >= uat.expiry {
|
if time::OffsetDateTime::now_utc() >= uat.expiry {
|
||||||
|
@ -126,8 +126,9 @@ impl CommonOpt {
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_e) => {
|
Err(e) => {
|
||||||
error!("Unable to read token for requested user - you may need to login again.");
|
error!("Unable to read token for requested user - you may need to login again.");
|
||||||
|
debug!(?e, "JWT Error");
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
extern crate tracing;
|
extern crate tracing;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
include!("../opt/kanidm.rs");
|
include!("../opt/kanidm.rs");
|
||||||
|
|
||||||
|
@ -43,9 +44,8 @@ impl SelfOpt {
|
||||||
match client.whoami().await {
|
match client.whoami().await {
|
||||||
Ok(o_ent) => {
|
Ok(o_ent) => {
|
||||||
match o_ent {
|
match o_ent {
|
||||||
Some((ent, uat)) => {
|
Some(ent) => {
|
||||||
debug!("{:?}", ent);
|
println!("{}", ent);
|
||||||
println!("{}", uat);
|
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
error!("Authentication with cached token failed, can't query information.");
|
error!("Authentication with cached token failed, can't query information.");
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
AccountSsh, AccountValidity, ServiceAccountCredential, ServiceAccountOpt, ServiceAccountPosix,
|
AccountSsh, AccountValidity, ServiceAccountApiToken, ServiceAccountCredential,
|
||||||
|
ServiceAccountOpt, ServiceAccountPosix,
|
||||||
};
|
};
|
||||||
use kanidm_proto::messages::{AccountChangeMessage, ConsoleOutputMode, MessageStatus};
|
use kanidm_proto::messages::{AccountChangeMessage, ConsoleOutputMode, MessageStatus};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
@ -11,6 +12,11 @@ impl ServiceAccountOpt {
|
||||||
ServiceAccountCredential::Status(apo) => apo.copt.debug,
|
ServiceAccountCredential::Status(apo) => apo.copt.debug,
|
||||||
ServiceAccountCredential::GeneratePw(apo) => apo.copt.debug,
|
ServiceAccountCredential::GeneratePw(apo) => apo.copt.debug,
|
||||||
},
|
},
|
||||||
|
ServiceAccountOpt::ApiToken { commands } => match commands {
|
||||||
|
ServiceAccountApiToken::Status(apo) => apo.copt.debug,
|
||||||
|
ServiceAccountApiToken::Generate { copt, .. } => copt.debug,
|
||||||
|
ServiceAccountApiToken::Destroy { copt, .. } => copt.debug,
|
||||||
|
},
|
||||||
ServiceAccountOpt::Posix { commands } => match commands {
|
ServiceAccountOpt::Posix { commands } => match commands {
|
||||||
ServiceAccountPosix::Show(apo) => apo.copt.debug,
|
ServiceAccountPosix::Show(apo) => apo.copt.debug,
|
||||||
ServiceAccountPosix::Set(apo) => apo.copt.debug,
|
ServiceAccountPosix::Set(apo) => apo.copt.debug,
|
||||||
|
@ -61,11 +67,97 @@ impl ServiceAccountOpt {
|
||||||
println!("Success: {}", new_pw);
|
println!("Success: {}", new_pw);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Error generating service account credential-> {:?}", e);
|
error!("Error generating service account credential -> {:?}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}, // End ServiceAccountOpt::Credential
|
||||||
|
ServiceAccountOpt::ApiToken { commands } => match commands {
|
||||||
|
ServiceAccountApiToken::Status(apo) => {
|
||||||
|
let client = apo.copt.to_client().await;
|
||||||
|
match client
|
||||||
|
.idm_service_account_list_api_token(apo.aopts.account_id.as_str())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(tokens) => {
|
||||||
|
if tokens.is_empty() {
|
||||||
|
println!("No api tokens exist");
|
||||||
|
} else {
|
||||||
|
for token in tokens {
|
||||||
|
println!("token: {}", token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Error listing service account api tokens -> {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ServiceAccountApiToken::Generate {
|
||||||
|
aopts,
|
||||||
|
copt,
|
||||||
|
label,
|
||||||
|
expiry,
|
||||||
|
} => {
|
||||||
|
let expiry_odt = if let Some(t) = expiry {
|
||||||
|
// Convert the time to local timezone.
|
||||||
|
match OffsetDateTime::parse(&t, time::Format::Rfc3339).map(|odt| {
|
||||||
|
odt.to_offset(
|
||||||
|
time::UtcOffset::try_current_local_offset()
|
||||||
|
.unwrap_or(time::UtcOffset::UTC),
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
Ok(odt) => {
|
||||||
|
debug!("valid until: {}", odt);
|
||||||
|
Some(odt)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Error -> {:?}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let client = copt.to_client().await;
|
||||||
|
|
||||||
|
match client
|
||||||
|
.idm_service_account_generate_api_token(
|
||||||
|
aopts.account_id.as_str(),
|
||||||
|
label,
|
||||||
|
expiry_odt,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(new_token) => {
|
||||||
|
println!("Success: This token will only be displayed ONCE");
|
||||||
|
println!("{}", new_token)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Error generating service account api token -> {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ServiceAccountApiToken::Destroy {
|
||||||
|
aopts,
|
||||||
|
copt,
|
||||||
|
token_id,
|
||||||
|
} => {
|
||||||
|
let client = copt.to_client().await;
|
||||||
|
match client
|
||||||
|
.idm_service_account_destroy_api_token(aopts.account_id.as_str(), *token_id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(()) => {
|
||||||
|
println!("Success");
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Error destroying service account token -> {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, // End ServiceAccountOpt::ApiToken
|
||||||
ServiceAccountOpt::Posix { commands } => match commands {
|
ServiceAccountOpt::Posix { commands } => match commands {
|
||||||
ServiceAccountPosix::Show(aopt) => {
|
ServiceAccountPosix::Show(aopt) => {
|
||||||
let client = aopt.copt.to_client().await;
|
let client = aopt.copt.to_client().await;
|
||||||
|
|
|
@ -438,7 +438,7 @@ impl SessionOpt {
|
||||||
error!(?e, "Unable to verify token signature, may be corrupt");
|
error!(?e, "Unable to verify token signature, may be corrupt");
|
||||||
})
|
})
|
||||||
.map(|jwt| {
|
.map(|jwt| {
|
||||||
let uat = jwt.inner;
|
let uat = jwt.into_inner();
|
||||||
(u, (t, uat))
|
(u, (t, uat))
|
||||||
})
|
})
|
||||||
.ok()
|
.ok()
|
||||||
|
|
|
@ -329,14 +329,48 @@ pub enum PersonOpt {
|
||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
pub enum ServiceAccountCredential {
|
pub enum ServiceAccountCredential {
|
||||||
/// Show the status of this accounts credentials.
|
/// Show the status of this accounts password
|
||||||
#[clap(name = "status")]
|
#[clap(name = "status")]
|
||||||
Status(AccountNamedOpt),
|
Status(AccountNamedOpt),
|
||||||
/// Reset and generate a new service account password. This password can NOT
|
/// Reset and generate a new service account password. This password can NOT
|
||||||
/// be used with the LDAP interface.
|
/// be used with the LDAP interface.
|
||||||
#[clap(name = "generate-pw")]
|
#[clap(name = "generate")]
|
||||||
GeneratePw(AccountNamedOpt),
|
GeneratePw(AccountNamedOpt),
|
||||||
// Future - add a token creator / remover.
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub enum ServiceAccountApiToken {
|
||||||
|
/// Show the status of api tokens associated to this service account.
|
||||||
|
#[clap(name = "status")]
|
||||||
|
Status(AccountNamedOpt),
|
||||||
|
/// Generate a new api token for this service account.
|
||||||
|
#[clap(name = "generate")]
|
||||||
|
Generate {
|
||||||
|
#[clap(flatten)]
|
||||||
|
aopts: AccountCommonOpt,
|
||||||
|
#[clap(flatten)]
|
||||||
|
copt: CommonOpt,
|
||||||
|
/// A string describing the token. This is not used to identify the token, it is only
|
||||||
|
/// for human description of the tokens purpose.
|
||||||
|
#[clap(name = "label")]
|
||||||
|
label: String,
|
||||||
|
#[clap(name = "expiry")]
|
||||||
|
/// An optional rfc3339 time of the format "YYYY-MM-DDTHH:MM:SS+TZ", "2020-09-25T11:22:02+10:00".
|
||||||
|
/// After this time the api token will no longer be valid.
|
||||||
|
expiry: Option<String>,
|
||||||
|
},
|
||||||
|
/// Destroy / revoke an api token from this service account. Access to the
|
||||||
|
/// token is NOT required, only the tag/uuid of the token.
|
||||||
|
#[clap(name = "destroy")]
|
||||||
|
Destroy {
|
||||||
|
#[clap(flatten)]
|
||||||
|
aopts: AccountCommonOpt,
|
||||||
|
#[clap(flatten)]
|
||||||
|
copt: CommonOpt,
|
||||||
|
/// The UUID of the token to destroy.
|
||||||
|
#[clap(name = "token_id")]
|
||||||
|
token_id: Uuid,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
|
@ -363,12 +397,18 @@ pub struct ServiceAccountUpdateOpt {
|
||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
pub enum ServiceAccountOpt {
|
pub enum ServiceAccountOpt {
|
||||||
/// Manage generated passwords or access tokens for this service account.
|
/// Manage the generated password of this service account.
|
||||||
#[clap(name = "credential")]
|
#[clap(name = "credential")]
|
||||||
Credential {
|
Credential {
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
commands: ServiceAccountCredential,
|
commands: ServiceAccountCredential,
|
||||||
},
|
},
|
||||||
|
/// Manage api tokens associated to this service account.
|
||||||
|
#[clap(name = "api-token")]
|
||||||
|
ApiToken {
|
||||||
|
#[clap(subcommand)]
|
||||||
|
commands: ServiceAccountApiToken,
|
||||||
|
},
|
||||||
/// Manage posix extensions for this service account allowing access to unix/linux systems
|
/// Manage posix extensions for this service account allowing access to unix/linux systems
|
||||||
#[clap(name = "posix")]
|
#[clap(name = "posix")]
|
||||||
Posix {
|
Posix {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
name = "kanidm_unix_int"
|
name = "kanidm_unix_int"
|
||||||
version = "1.1.0-alpha.9"
|
version = "1.1.0-alpha.9"
|
||||||
authors = ["William Brown <william@blackhats.net.au>"]
|
authors = ["William Brown <william@blackhats.net.au>"]
|
||||||
rust-version = "1.59"
|
rust-version = "1.64"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
description = "Kanidm Unix Integration Clients"
|
description = "Kanidm Unix Integration Clients"
|
||||||
|
|
|
@ -21,9 +21,9 @@ base64 = "^0.13.0"
|
||||||
base64urlsafedata = "0.1.0"
|
base64urlsafedata = "0.1.0"
|
||||||
chrono = "^0.4.20"
|
chrono = "^0.4.20"
|
||||||
compact_jwt = "^0.2.3"
|
compact_jwt = "^0.2.3"
|
||||||
concread = "^0.3.7"
|
concread = "^0.4.0"
|
||||||
dyn-clone = "^1.0.9"
|
dyn-clone = "^1.0.9"
|
||||||
fernet = { version = "^0.1.4", features = ["fernet_danger_timestamps"] }
|
fernet = { version = "^0.2.0", features = ["fernet_danger_timestamps"] }
|
||||||
filetime = "^0.2.17"
|
filetime = "^0.2.17"
|
||||||
futures = "^0.3.21"
|
futures = "^0.3.21"
|
||||||
futures-util = "^0.3.21"
|
futures-util = "^0.3.21"
|
||||||
|
@ -61,8 +61,8 @@ url = { version = "^2.3.1", features = ["serde"] }
|
||||||
urlencoding = "2.1.2"
|
urlencoding = "2.1.2"
|
||||||
uuid = { version = "^1.1.2", features = ["serde", "v4" ] }
|
uuid = { version = "^1.1.2", features = ["serde", "v4" ] }
|
||||||
validator = { version = "^0.16.0", features = ["phone"] }
|
validator = { version = "^0.16.0", features = ["phone"] }
|
||||||
webauthn-rs = { version = "0.4.5", features = ["resident-key-support", "preview-features", "danger-credential-internals"] }
|
webauthn-rs = { workspace = true, features = ["resident-key-support", "preview-features", "danger-credential-internals"] }
|
||||||
webauthn-rs-core = "0.4.2-beta.3"
|
webauthn-rs-core.workspace = true
|
||||||
zxcvbn = "^2.2.1"
|
zxcvbn = "^2.2.1"
|
||||||
|
|
||||||
# because windows really can't build without the bundled one
|
# because windows really can't build without the bundled one
|
||||||
|
@ -87,7 +87,7 @@ users = "^0.11.0"
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = { version = "^0.4.0", features = ["html_reports"] }
|
criterion = { version = "^0.4.0", features = ["html_reports"] }
|
||||||
# For testing webauthn
|
# For testing webauthn
|
||||||
webauthn-authenticator-rs = "0.4.2-beta.3"
|
webauthn-authenticator-rs.workspace = true
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
profiles = { path = "../../profiles" }
|
profiles = { path = "../../profiles" }
|
||||||
|
|
|
@ -406,7 +406,7 @@ pub trait AccessControlsTransaction<'a> {
|
||||||
#[allow(clippy::mut_from_ref)]
|
#[allow(clippy::mut_from_ref)]
|
||||||
fn get_acp_resolve_filter_cache(
|
fn get_acp_resolve_filter_cache(
|
||||||
&self,
|
&self,
|
||||||
) -> &mut ARCacheReadTxn<'a, (IdentityId, Filter<FilterValid>), Filter<FilterValidResolved>>;
|
) -> &mut ARCacheReadTxn<'a, (IdentityId, Filter<FilterValid>), Filter<FilterValidResolved>, ()>;
|
||||||
|
|
||||||
fn search_related_acp<'b>(
|
fn search_related_acp<'b>(
|
||||||
&'b self,
|
&'b self,
|
||||||
|
@ -1303,8 +1303,9 @@ pub struct AccessControlsWriteTransaction<'a> {
|
||||||
inner: CowCellWriteTxn<'a, AccessControlsInner>,
|
inner: CowCellWriteTxn<'a, AccessControlsInner>,
|
||||||
// acp_related_search_cache_wr: ARCacheWriteTxn<'a, Uuid, Vec<Uuid>>,
|
// acp_related_search_cache_wr: ARCacheWriteTxn<'a, Uuid, Vec<Uuid>>,
|
||||||
// acp_related_search_cache: Cell<ARCacheReadTxn<'a, Uuid, Vec<Uuid>>>,
|
// acp_related_search_cache: Cell<ARCacheReadTxn<'a, Uuid, Vec<Uuid>>>,
|
||||||
acp_resolve_filter_cache:
|
acp_resolve_filter_cache: Cell<
|
||||||
Cell<ARCacheReadTxn<'a, (IdentityId, Filter<FilterValid>), Filter<FilterValidResolved>>>,
|
ARCacheReadTxn<'a, (IdentityId, Filter<FilterValid>), Filter<FilterValidResolved>, ()>,
|
||||||
|
>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> AccessControlsWriteTransaction<'a> {
|
impl<'a> AccessControlsWriteTransaction<'a> {
|
||||||
|
@ -1399,7 +1400,7 @@ impl<'a> AccessControlsTransaction<'a> for AccessControlsWriteTransaction<'a> {
|
||||||
|
|
||||||
fn get_acp_resolve_filter_cache(
|
fn get_acp_resolve_filter_cache(
|
||||||
&self,
|
&self,
|
||||||
) -> &mut ARCacheReadTxn<'a, (IdentityId, Filter<FilterValid>), Filter<FilterValidResolved>>
|
) -> &mut ARCacheReadTxn<'a, (IdentityId, Filter<FilterValid>), Filter<FilterValidResolved>, ()>
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
let mptr = self.acp_resolve_filter_cache.as_ptr();
|
let mptr = self.acp_resolve_filter_cache.as_ptr();
|
||||||
|
@ -1408,6 +1409,7 @@ impl<'a> AccessControlsTransaction<'a> for AccessControlsWriteTransaction<'a> {
|
||||||
'a,
|
'a,
|
||||||
(IdentityId, Filter<FilterValid>),
|
(IdentityId, Filter<FilterValid>),
|
||||||
Filter<FilterValidResolved>,
|
Filter<FilterValidResolved>,
|
||||||
|
(),
|
||||||
>
|
>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1420,8 +1422,9 @@ impl<'a> AccessControlsTransaction<'a> for AccessControlsWriteTransaction<'a> {
|
||||||
pub struct AccessControlsReadTransaction<'a> {
|
pub struct AccessControlsReadTransaction<'a> {
|
||||||
inner: CowCellReadTxn<AccessControlsInner>,
|
inner: CowCellReadTxn<AccessControlsInner>,
|
||||||
// acp_related_search_cache: Cell<ARCacheReadTxn<'a, Uuid, Vec<Uuid>>>,
|
// acp_related_search_cache: Cell<ARCacheReadTxn<'a, Uuid, Vec<Uuid>>>,
|
||||||
acp_resolve_filter_cache:
|
acp_resolve_filter_cache: Cell<
|
||||||
Cell<ARCacheReadTxn<'a, (IdentityId, Filter<FilterValid>), Filter<FilterValidResolved>>>,
|
ARCacheReadTxn<'a, (IdentityId, Filter<FilterValid>), Filter<FilterValidResolved>, ()>,
|
||||||
|
>,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl<'a> Sync for AccessControlsReadTransaction<'a> {}
|
unsafe impl<'a> Sync for AccessControlsReadTransaction<'a> {}
|
||||||
|
@ -1456,7 +1459,7 @@ impl<'a> AccessControlsTransaction<'a> for AccessControlsReadTransaction<'a> {
|
||||||
|
|
||||||
fn get_acp_resolve_filter_cache(
|
fn get_acp_resolve_filter_cache(
|
||||||
&self,
|
&self,
|
||||||
) -> &mut ARCacheReadTxn<'a, (IdentityId, Filter<FilterValid>), Filter<FilterValidResolved>>
|
) -> &mut ARCacheReadTxn<'a, (IdentityId, Filter<FilterValid>), Filter<FilterValidResolved>, ()>
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
let mptr = self.acp_resolve_filter_cache.as_ptr();
|
let mptr = self.acp_resolve_filter_cache.as_ptr();
|
||||||
|
@ -1465,6 +1468,7 @@ impl<'a> AccessControlsTransaction<'a> for AccessControlsReadTransaction<'a> {
|
||||||
'a,
|
'a,
|
||||||
(IdentityId, Filter<FilterValid>),
|
(IdentityId, Filter<FilterValid>),
|
||||||
Filter<FilterValidResolved>,
|
Filter<FilterValidResolved>,
|
||||||
|
(),
|
||||||
>
|
>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,11 +24,12 @@ use crate::idm::oauth2::{
|
||||||
JwkKeySet, Oauth2Error, OidcDiscoveryResponse, OidcToken,
|
JwkKeySet, Oauth2Error, OidcDiscoveryResponse, OidcToken,
|
||||||
};
|
};
|
||||||
use crate::idm::server::{IdmServer, IdmServerTransaction};
|
use crate::idm::server::{IdmServer, IdmServerTransaction};
|
||||||
|
use crate::idm::serviceaccount::ListApiTokenEvent;
|
||||||
use crate::ldap::{LdapBoundToken, LdapResponseState, LdapServer};
|
use crate::ldap::{LdapBoundToken, LdapResponseState, LdapServer};
|
||||||
|
|
||||||
use kanidm_proto::v1::Entry as ProtoEntry;
|
use kanidm_proto::v1::Entry as ProtoEntry;
|
||||||
use kanidm_proto::v1::{
|
use kanidm_proto::v1::{
|
||||||
AuthRequest, CURequest, CUSessionToken, CUStatus, CredentialStatus, SearchRequest,
|
ApiToken, AuthRequest, CURequest, CUSessionToken, CUStatus, CredentialStatus, SearchRequest,
|
||||||
SearchResponse, UnixGroupToken, UnixUserToken, WhoamiResponse,
|
SearchResponse, UnixGroupToken, UnixUserToken, WhoamiResponse,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -102,8 +103,7 @@ impl QueryServerReadV1 {
|
||||||
// ! in order to not short-circuit the entire function.
|
// ! in order to not short-circuit the entire function.
|
||||||
let res = spanned!("actors::v1_read::handle<SearchMessage>", {
|
let res = spanned!("actors::v1_read::handle<SearchMessage>", {
|
||||||
let ident = idms_prox_read
|
let ident = idms_prox_read
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
.and_then(|uat| idms_prox_read.process_uat_to_identity(&uat, ct))
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!(?e, "Invalid identity");
|
admin_error!(?e, "Invalid identity");
|
||||||
e
|
e
|
||||||
|
@ -329,13 +329,8 @@ impl QueryServerReadV1 {
|
||||||
// trigger the failure, but if we can manage to work out async
|
// trigger the failure, but if we can manage to work out async
|
||||||
// then move this to core.rs, and don't allow Option<UAT> to get
|
// then move this to core.rs, and don't allow Option<UAT> to get
|
||||||
// this far.
|
// this far.
|
||||||
let (uat, ident) = idms_prox_read
|
let ident = idms_prox_read
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
.and_then(|uat| {
|
|
||||||
idms_prox_read
|
|
||||||
.process_uat_to_identity(&uat, ct)
|
|
||||||
.map(|i| (uat, i))
|
|
||||||
})
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!(?e, "Invalid identity");
|
admin_error!(?e, "Invalid identity");
|
||||||
e
|
e
|
||||||
|
@ -353,7 +348,7 @@ impl QueryServerReadV1 {
|
||||||
|
|
||||||
match entries.pop() {
|
match entries.pop() {
|
||||||
Some(e) if entries.is_empty() => {
|
Some(e) if entries.is_empty() => {
|
||||||
WhoamiResult::new(&idms_prox_read.qs_read, &e, uat).map(WhoamiResult::response)
|
WhoamiResult::new(&idms_prox_read.qs_read, &e).map(WhoamiResult::response)
|
||||||
}
|
}
|
||||||
Some(_) => Err(OperationError::InvalidState), // Somehow matched multiple entries...
|
Some(_) => Err(OperationError::InvalidState), // Somehow matched multiple entries...
|
||||||
_ => Err(OperationError::NoMatchingEntries),
|
_ => Err(OperationError::NoMatchingEntries),
|
||||||
|
@ -379,8 +374,7 @@ impl QueryServerReadV1 {
|
||||||
let idms_prox_read = self.idms.proxy_read_async().await;
|
let idms_prox_read = self.idms.proxy_read_async().await;
|
||||||
let res = spanned!("actors::v1_read::handle<InternalSearchMessage>", {
|
let res = spanned!("actors::v1_read::handle<InternalSearchMessage>", {
|
||||||
let ident = idms_prox_read
|
let ident = idms_prox_read
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
.and_then(|uat| idms_prox_read.process_uat_to_identity(&uat, ct))
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!("Invalid identity: {:?}", e);
|
admin_error!("Invalid identity: {:?}", e);
|
||||||
e
|
e
|
||||||
|
@ -428,8 +422,7 @@ impl QueryServerReadV1 {
|
||||||
|
|
||||||
let res = spanned!("actors::v1_read::handle<InternalSearchRecycledMessage>", {
|
let res = spanned!("actors::v1_read::handle<InternalSearchRecycledMessage>", {
|
||||||
let ident = idms_prox_read
|
let ident = idms_prox_read
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
.and_then(|uat| idms_prox_read.process_uat_to_identity(&uat, ct))
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!("Invalid identity: {:?}", e);
|
admin_error!("Invalid identity: {:?}", e);
|
||||||
e
|
e
|
||||||
|
@ -475,8 +468,7 @@ impl QueryServerReadV1 {
|
||||||
let idms_prox_read = self.idms.proxy_read_async().await;
|
let idms_prox_read = self.idms.proxy_read_async().await;
|
||||||
let res = spanned!("actors::v1_read::handle<InternalRadiusReadMessage>", {
|
let res = spanned!("actors::v1_read::handle<InternalRadiusReadMessage>", {
|
||||||
let ident = idms_prox_read
|
let ident = idms_prox_read
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
.and_then(|uat| idms_prox_read.process_uat_to_identity(&uat, ct))
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!("Invalid identity: {:?}", e);
|
admin_error!("Invalid identity: {:?}", e);
|
||||||
e
|
e
|
||||||
|
@ -541,8 +533,7 @@ impl QueryServerReadV1 {
|
||||||
|
|
||||||
let res = spanned!("actors::v1_read::handle<InternalRadiusTokenReadMessage>", {
|
let res = spanned!("actors::v1_read::handle<InternalRadiusTokenReadMessage>", {
|
||||||
let ident = idms_prox_read
|
let ident = idms_prox_read
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
.and_then(|uat| idms_prox_read.process_uat_to_identity(&uat, ct))
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!("Invalid identity: {:?}", e);
|
admin_error!("Invalid identity: {:?}", e);
|
||||||
e
|
e
|
||||||
|
@ -595,8 +586,7 @@ impl QueryServerReadV1 {
|
||||||
"actors::v1_read::handle<InternalUnixUserTokenReadMessage>",
|
"actors::v1_read::handle<InternalUnixUserTokenReadMessage>",
|
||||||
{
|
{
|
||||||
let ident = idms_prox_read
|
let ident = idms_prox_read
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
.and_then(|uat| idms_prox_read.process_uat_to_identity(&uat, ct))
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!("Invalid identity: {:?}", e);
|
admin_error!("Invalid identity: {:?}", e);
|
||||||
e
|
e
|
||||||
|
@ -649,8 +639,7 @@ impl QueryServerReadV1 {
|
||||||
"actors::v1_read::handle<InternalUnixGroupTokenReadMessage>",
|
"actors::v1_read::handle<InternalUnixGroupTokenReadMessage>",
|
||||||
{
|
{
|
||||||
let ident = idms_prox_read
|
let ident = idms_prox_read
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
.and_then(|uat| idms_prox_read.process_uat_to_identity(&uat, ct))
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!("Invalid identity: {:?}", e);
|
admin_error!("Invalid identity: {:?}", e);
|
||||||
e
|
e
|
||||||
|
@ -701,8 +690,7 @@ impl QueryServerReadV1 {
|
||||||
let idms_prox_read = self.idms.proxy_read_async().await;
|
let idms_prox_read = self.idms.proxy_read_async().await;
|
||||||
let res = spanned!("actors::v1_read::handle<InternalSshKeyReadMessage>", {
|
let res = spanned!("actors::v1_read::handle<InternalSshKeyReadMessage>", {
|
||||||
let ident = idms_prox_read
|
let ident = idms_prox_read
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
.and_then(|uat| idms_prox_read.process_uat_to_identity(&uat, ct))
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!("Invalid identity: {:?}", e);
|
admin_error!("Invalid identity: {:?}", e);
|
||||||
e
|
e
|
||||||
|
@ -769,8 +757,7 @@ impl QueryServerReadV1 {
|
||||||
let idms_prox_read = self.idms.proxy_read_async().await;
|
let idms_prox_read = self.idms.proxy_read_async().await;
|
||||||
let res = spanned!("actors::v1_read::handle<InternalSshKeyTagReadMessage>", {
|
let res = spanned!("actors::v1_read::handle<InternalSshKeyTagReadMessage>", {
|
||||||
let ident = idms_prox_read
|
let ident = idms_prox_read
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
.and_then(|uat| idms_prox_read.process_uat_to_identity(&uat, ct))
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!("Invalid identity: {:?}", e);
|
admin_error!("Invalid identity: {:?}", e);
|
||||||
e
|
e
|
||||||
|
@ -822,6 +809,39 @@ impl QueryServerReadV1 {
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(
|
||||||
|
level = "info",
|
||||||
|
name = "service_account_api_token_get",
|
||||||
|
skip(self, uat, uuid_or_name, eventid)
|
||||||
|
fields(uuid = ?eventid)
|
||||||
|
)]
|
||||||
|
pub async fn handle_service_account_api_token_get(
|
||||||
|
&self,
|
||||||
|
uat: Option<String>,
|
||||||
|
uuid_or_name: String,
|
||||||
|
eventid: Uuid,
|
||||||
|
) -> Result<Vec<ApiToken>, OperationError> {
|
||||||
|
let ct = duration_from_epoch_now();
|
||||||
|
let idms_prox_read = self.idms.proxy_read_async().await;
|
||||||
|
let ident = idms_prox_read
|
||||||
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
|
.map_err(|e| {
|
||||||
|
admin_error!("Invalid identity: {:?}", e);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
let target = idms_prox_read
|
||||||
|
.qs_read
|
||||||
|
.name_to_uuid(uuid_or_name.as_str())
|
||||||
|
.map_err(|e| {
|
||||||
|
admin_error!("Error resolving id to target");
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let lte = ListApiTokenEvent { ident, target };
|
||||||
|
|
||||||
|
idms_prox_read.service_account_list_api_token(<e)
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument(
|
#[instrument(
|
||||||
level = "info",
|
level = "info",
|
||||||
name = "idm_account_unix_auth",
|
name = "idm_account_unix_auth",
|
||||||
|
@ -840,8 +860,7 @@ impl QueryServerReadV1 {
|
||||||
// let res = spanned!("actors::v1_read::handle<IdmAccountUnixAuthMessage>", {
|
// let res = spanned!("actors::v1_read::handle<IdmAccountUnixAuthMessage>", {
|
||||||
// resolve the id
|
// resolve the id
|
||||||
let ident = idm_auth
|
let ident = idm_auth
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
.and_then(|uat| idm_auth.process_uat_to_identity(&uat, ct))
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!(err = ?e, "Invalid identity");
|
admin_error!(err = ?e, "Invalid identity");
|
||||||
e
|
e
|
||||||
|
@ -893,8 +912,7 @@ impl QueryServerReadV1 {
|
||||||
|
|
||||||
let res = spanned!("actors::v1_read::handle<IdmCredentialStatusMessage>", {
|
let res = spanned!("actors::v1_read::handle<IdmCredentialStatusMessage>", {
|
||||||
let ident = idms_prox_read
|
let ident = idms_prox_read
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
.and_then(|uat| idms_prox_read.process_uat_to_identity(&uat, ct))
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!(err = ?e, "Invalid identity");
|
admin_error!(err = ?e, "Invalid identity");
|
||||||
e
|
e
|
||||||
|
@ -944,8 +962,7 @@ impl QueryServerReadV1 {
|
||||||
|
|
||||||
let res = spanned!("actors::v1_read::handle<IdmBackupCodeViewMessage>", {
|
let res = spanned!("actors::v1_read::handle<IdmBackupCodeViewMessage>", {
|
||||||
let ident = idms_prox_read
|
let ident = idms_prox_read
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
.and_then(|uat| idms_prox_read.process_uat_to_identity(&uat, ct))
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!("Invalid identity: {:?}", e);
|
admin_error!("Invalid identity: {:?}", e);
|
||||||
e
|
e
|
||||||
|
|
|
@ -24,6 +24,7 @@ use kanidm_proto::v1::OperationError;
|
||||||
use crate::filter::{Filter, FilterInvalid};
|
use crate::filter::{Filter, FilterInvalid};
|
||||||
use crate::idm::delayed::DelayedAction;
|
use crate::idm::delayed::DelayedAction;
|
||||||
use crate::idm::server::{IdmServer, IdmServerTransaction};
|
use crate::idm::server::{IdmServer, IdmServerTransaction};
|
||||||
|
use crate::idm::serviceaccount::{DestroyApiTokenEvent, GenerateApiTokenEvent};
|
||||||
use crate::utils::duration_from_epoch_now;
|
use crate::utils::duration_from_epoch_now;
|
||||||
|
|
||||||
use kanidm_proto::v1::Entry as ProtoEntry;
|
use kanidm_proto::v1::Entry as ProtoEntry;
|
||||||
|
@ -34,6 +35,8 @@ use kanidm_proto::v1::{
|
||||||
GroupUnixExtend, ModifyRequest,
|
GroupUnixExtend, ModifyRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub struct QueryServerWriteV1 {
|
pub struct QueryServerWriteV1 {
|
||||||
|
@ -72,8 +75,7 @@ impl QueryServerWriteV1 {
|
||||||
let ct = duration_from_epoch_now();
|
let ct = duration_from_epoch_now();
|
||||||
|
|
||||||
let ident = idms_prox_write
|
let ident = idms_prox_write
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
.and_then(|uat| idms_prox_write.process_uat_to_identity(&uat, ct))
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!(err = ?e, "Invalid identity");
|
admin_error!(err = ?e, "Invalid identity");
|
||||||
e
|
e
|
||||||
|
@ -122,8 +124,7 @@ impl QueryServerWriteV1 {
|
||||||
let ct = duration_from_epoch_now();
|
let ct = duration_from_epoch_now();
|
||||||
|
|
||||||
let ident = idms_prox_write
|
let ident = idms_prox_write
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
.and_then(|uat| idms_prox_write.process_uat_to_identity(&uat, ct))
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!(err = ?e, "Invalid identity");
|
admin_error!(err = ?e, "Invalid identity");
|
||||||
e
|
e
|
||||||
|
@ -180,8 +181,7 @@ impl QueryServerWriteV1 {
|
||||||
let ct = duration_from_epoch_now();
|
let ct = duration_from_epoch_now();
|
||||||
|
|
||||||
let ident = idms_prox_write
|
let ident = idms_prox_write
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
.and_then(|uat| idms_prox_write.process_uat_to_identity(&uat, ct))
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!(err = ?e, "Invalid identity");
|
admin_error!(err = ?e, "Invalid identity");
|
||||||
e
|
e
|
||||||
|
@ -222,8 +222,7 @@ impl QueryServerWriteV1 {
|
||||||
let res = spanned!("actors::v1_write::handle<ModifyMessage>", {
|
let res = spanned!("actors::v1_write::handle<ModifyMessage>", {
|
||||||
let ct = duration_from_epoch_now();
|
let ct = duration_from_epoch_now();
|
||||||
let ident = idms_prox_write
|
let ident = idms_prox_write
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
.and_then(|uat| idms_prox_write.process_uat_to_identity(&uat, ct))
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!(err = ?e, "Invalid identity");
|
admin_error!(err = ?e, "Invalid identity");
|
||||||
e
|
e
|
||||||
|
@ -263,8 +262,7 @@ impl QueryServerWriteV1 {
|
||||||
let res = spanned!("actors::v1_write::handle<DeleteMessage>", {
|
let res = spanned!("actors::v1_write::handle<DeleteMessage>", {
|
||||||
let ct = duration_from_epoch_now();
|
let ct = duration_from_epoch_now();
|
||||||
let ident = idms_prox_write
|
let ident = idms_prox_write
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
.and_then(|uat| idms_prox_write.process_uat_to_identity(&uat, ct))
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!(err = ?e, "Invalid identity");
|
admin_error!(err = ?e, "Invalid identity");
|
||||||
e
|
e
|
||||||
|
@ -305,8 +303,7 @@ impl QueryServerWriteV1 {
|
||||||
let res = spanned!("actors::v1_write::handle<InternalPatch>", {
|
let res = spanned!("actors::v1_write::handle<InternalPatch>", {
|
||||||
let ct = duration_from_epoch_now();
|
let ct = duration_from_epoch_now();
|
||||||
let ident = idms_prox_write
|
let ident = idms_prox_write
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
.and_then(|uat| idms_prox_write.process_uat_to_identity(&uat, ct))
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!(err = ?e, "Invalid identity");
|
admin_error!(err = ?e, "Invalid identity");
|
||||||
e
|
e
|
||||||
|
@ -356,8 +353,7 @@ impl QueryServerWriteV1 {
|
||||||
let res = spanned!("actors::v1_write::handle<InternalDeleteMessage>", {
|
let res = spanned!("actors::v1_write::handle<InternalDeleteMessage>", {
|
||||||
let ct = duration_from_epoch_now();
|
let ct = duration_from_epoch_now();
|
||||||
let ident = idms_prox_write
|
let ident = idms_prox_write
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
.and_then(|uat| idms_prox_write.process_uat_to_identity(&uat, ct))
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!(err = ?e, "Invalid identity");
|
admin_error!(err = ?e, "Invalid identity");
|
||||||
e
|
e
|
||||||
|
@ -396,8 +392,7 @@ impl QueryServerWriteV1 {
|
||||||
let res = spanned!("actors::v1_write::handle<ReviveRecycledMessage>", {
|
let res = spanned!("actors::v1_write::handle<ReviveRecycledMessage>", {
|
||||||
let ct = duration_from_epoch_now();
|
let ct = duration_from_epoch_now();
|
||||||
let ident = idms_prox_write
|
let ident = idms_prox_write
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
.and_then(|uat| idms_prox_write.process_uat_to_identity(&uat, ct))
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!(err = ?e, "Invalid identity");
|
admin_error!(err = ?e, "Invalid identity");
|
||||||
e
|
e
|
||||||
|
@ -437,8 +432,7 @@ impl QueryServerWriteV1 {
|
||||||
let mut idms_prox_write = self.idms.proxy_write_async(ct).await;
|
let mut idms_prox_write = self.idms.proxy_write_async(ct).await;
|
||||||
let res = spanned!("actors::v1_write::handle<InternalCredentialSetMessage>", {
|
let res = spanned!("actors::v1_write::handle<InternalCredentialSetMessage>", {
|
||||||
let ident = idms_prox_write
|
let ident = idms_prox_write
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
.and_then(|uat| idms_prox_write.process_uat_to_identity(&uat, ct))
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!(err = ?e, "Invalid identity");
|
admin_error!(err = ?e, "Invalid identity");
|
||||||
e
|
e
|
||||||
|
@ -471,6 +465,90 @@ impl QueryServerWriteV1 {
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(
|
||||||
|
level = "info",
|
||||||
|
name = "service_account_credential_generate",
|
||||||
|
skip(self, uat, uuid_or_name, label, expiry, eventid)
|
||||||
|
fields(uuid = ?eventid)
|
||||||
|
)]
|
||||||
|
pub async fn handle_service_account_api_token_generate(
|
||||||
|
&self,
|
||||||
|
uat: Option<String>,
|
||||||
|
uuid_or_name: String,
|
||||||
|
label: String,
|
||||||
|
expiry: Option<OffsetDateTime>,
|
||||||
|
eventid: Uuid,
|
||||||
|
) -> Result<String, OperationError> {
|
||||||
|
let ct = duration_from_epoch_now();
|
||||||
|
let idms_prox_write = self.idms.proxy_write_async(ct).await;
|
||||||
|
let ident = idms_prox_write
|
||||||
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
|
.map_err(|e| {
|
||||||
|
admin_error!(err = ?e, "Invalid identity");
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let target = idms_prox_write
|
||||||
|
.qs_write
|
||||||
|
.name_to_uuid(uuid_or_name.as_str())
|
||||||
|
.map_err(|e| {
|
||||||
|
admin_error!(err = ?e, "Error resolving id to target");
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let gte = GenerateApiTokenEvent {
|
||||||
|
ident,
|
||||||
|
target,
|
||||||
|
label,
|
||||||
|
expiry,
|
||||||
|
};
|
||||||
|
|
||||||
|
idms_prox_write
|
||||||
|
.service_account_generate_api_token(>e, ct)
|
||||||
|
.and_then(|r| idms_prox_write.commit().map(|_| r))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(
|
||||||
|
level = "info",
|
||||||
|
name = "service_account_credential_generate",
|
||||||
|
skip_all,
|
||||||
|
fields(uuid = ?eventid)
|
||||||
|
)]
|
||||||
|
pub async fn handle_service_account_api_token_destroy(
|
||||||
|
&self,
|
||||||
|
uat: Option<String>,
|
||||||
|
uuid_or_name: String,
|
||||||
|
token_id: Uuid,
|
||||||
|
eventid: Uuid,
|
||||||
|
) -> Result<(), OperationError> {
|
||||||
|
let ct = duration_from_epoch_now();
|
||||||
|
let idms_prox_write = self.idms.proxy_write_async(ct).await;
|
||||||
|
let ident = idms_prox_write
|
||||||
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
|
.map_err(|e| {
|
||||||
|
admin_error!(err = ?e, "Invalid identity");
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let target = idms_prox_write
|
||||||
|
.qs_write
|
||||||
|
.name_to_uuid(uuid_or_name.as_str())
|
||||||
|
.map_err(|e| {
|
||||||
|
admin_error!(err = ?e, "Error resolving id to target");
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let dte = DestroyApiTokenEvent {
|
||||||
|
ident,
|
||||||
|
target,
|
||||||
|
token_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
idms_prox_write
|
||||||
|
.service_account_destroy_api_token(&dte)
|
||||||
|
.and_then(|r| idms_prox_write.commit().map(|_| r))
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument(
|
#[instrument(
|
||||||
level = "info",
|
level = "info",
|
||||||
name = "idm_credential_update",
|
name = "idm_credential_update",
|
||||||
|
@ -487,8 +565,7 @@ impl QueryServerWriteV1 {
|
||||||
let mut idms_prox_write = self.idms.proxy_write_async(ct).await;
|
let mut idms_prox_write = self.idms.proxy_write_async(ct).await;
|
||||||
let res = spanned!("actors::v1_write::handle<IdmCredentialUpdate>", {
|
let res = spanned!("actors::v1_write::handle<IdmCredentialUpdate>", {
|
||||||
let ident = idms_prox_write
|
let ident = idms_prox_write
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
.and_then(|uat| idms_prox_write.process_uat_to_identity(&uat, ct))
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!(err = ?e, "Invalid identity");
|
admin_error!(err = ?e, "Invalid identity");
|
||||||
e
|
e
|
||||||
|
@ -541,8 +618,7 @@ impl QueryServerWriteV1 {
|
||||||
let mut idms_prox_write = self.idms.proxy_write_async(ct).await;
|
let mut idms_prox_write = self.idms.proxy_write_async(ct).await;
|
||||||
let res = spanned!("actors::v1_write::handle<IdmCredentialUpdateIntent>", {
|
let res = spanned!("actors::v1_write::handle<IdmCredentialUpdateIntent>", {
|
||||||
let ident = idms_prox_write
|
let ident = idms_prox_write
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
.and_then(|uat| idms_prox_write.process_uat_to_identity(&uat, ct))
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!(err = ?e, "Invalid identity");
|
admin_error!(err = ?e, "Invalid identity");
|
||||||
e
|
e
|
||||||
|
@ -680,49 +756,6 @@ impl QueryServerWriteV1 {
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
#[instrument(
|
|
||||||
level = "info",
|
|
||||||
name = "idm_account_set_password",
|
|
||||||
skip(self, uat, cleartext, eventid)
|
|
||||||
fields(uuid = ?eventid)
|
|
||||||
)]
|
|
||||||
pub async fn handle_idmaccountsetpassword(
|
|
||||||
&self,
|
|
||||||
uat: Option<String>,
|
|
||||||
cleartext: String,
|
|
||||||
eventid: Uuid,
|
|
||||||
) -> Result<(), OperationError> {
|
|
||||||
let ct = duration_from_epoch_now();
|
|
||||||
let mut idms_prox_write = self.idms.proxy_write_async(ct).await;
|
|
||||||
let res = spanned!("actors::v1_write::handle<IdmAccountSetPasswordMessage>", {
|
|
||||||
idms_prox_write.expire_mfareg_sessions(ct);
|
|
||||||
|
|
||||||
let ident = idms_prox_write
|
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
|
||||||
.and_then(|uat| idms_prox_write.process_uat_to_identity(&uat, ct))
|
|
||||||
.map_err(|e| {
|
|
||||||
admin_error!(err = ?e, "Invalid identity");
|
|
||||||
e
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let pce = PasswordChangeEvent::from_idm_account_set_password(
|
|
||||||
ident, cleartext,
|
|
||||||
// &idms_prox_write.qs_write,
|
|
||||||
)
|
|
||||||
.map_err(|e| {
|
|
||||||
admin_error!(err = ?e, "Failed to begin idm_account_set_password");
|
|
||||||
e
|
|
||||||
})?;
|
|
||||||
|
|
||||||
idms_prox_write
|
|
||||||
.set_account_password(&pce)
|
|
||||||
.and_then(|_| idms_prox_write.commit())
|
|
||||||
});
|
|
||||||
res
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
#[instrument(
|
#[instrument(
|
||||||
level = "info",
|
level = "info",
|
||||||
name = "handle_service_account_into_person",
|
name = "handle_service_account_into_person",
|
||||||
|
@ -739,8 +772,7 @@ impl QueryServerWriteV1 {
|
||||||
let idms_prox_write = self.idms.proxy_write_async(ct).await;
|
let idms_prox_write = self.idms.proxy_write_async(ct).await;
|
||||||
let res = spanned!("actors::v1_write::handle<IdmServiceAccountIntoPerson>", {
|
let res = spanned!("actors::v1_write::handle<IdmServiceAccountIntoPerson>", {
|
||||||
let ident = idms_prox_write
|
let ident = idms_prox_write
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
.and_then(|uat| idms_prox_write.process_uat_to_identity(&uat, ct))
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!(err = ?e, "Invalid identity");
|
admin_error!(err = ?e, "Invalid identity");
|
||||||
e
|
e
|
||||||
|
@ -778,8 +810,7 @@ impl QueryServerWriteV1 {
|
||||||
"actors::v1_write::handle<InternalRegenerateRadiusMessage>",
|
"actors::v1_write::handle<InternalRegenerateRadiusMessage>",
|
||||||
{
|
{
|
||||||
let ident = idms_prox_write
|
let ident = idms_prox_write
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
.and_then(|uat| idms_prox_write.process_uat_to_identity(&uat, ct))
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!(err = ?e, "Invalid identity");
|
admin_error!(err = ?e, "Invalid identity");
|
||||||
e
|
e
|
||||||
|
@ -832,8 +863,7 @@ impl QueryServerWriteV1 {
|
||||||
let idms_prox_write = self.idms.proxy_write_async(ct).await;
|
let idms_prox_write = self.idms.proxy_write_async(ct).await;
|
||||||
spanned!("actors::v1_write::handle<PurgeAttributeMessage>", {
|
spanned!("actors::v1_write::handle<PurgeAttributeMessage>", {
|
||||||
let ident = idms_prox_write
|
let ident = idms_prox_write
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
.and_then(|uat| idms_prox_write.process_uat_to_identity(&uat, ct))
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!(err = ?e, "Invalid identity");
|
admin_error!(err = ?e, "Invalid identity");
|
||||||
e
|
e
|
||||||
|
@ -888,8 +918,7 @@ impl QueryServerWriteV1 {
|
||||||
spanned!("actors::v1_write::handle<RemoveAttributeValuesMessage>", {
|
spanned!("actors::v1_write::handle<RemoveAttributeValuesMessage>", {
|
||||||
let ct = duration_from_epoch_now();
|
let ct = duration_from_epoch_now();
|
||||||
let ident = idms_prox_write
|
let ident = idms_prox_write
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
.and_then(|uat| idms_prox_write.process_uat_to_identity(&uat, ct))
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!(err = ?e, "Invalid identity");
|
admin_error!(err = ?e, "Invalid identity");
|
||||||
e
|
e
|
||||||
|
@ -1107,8 +1136,7 @@ impl QueryServerWriteV1 {
|
||||||
let mut idms_prox_write = self.idms.proxy_write_async(ct).await;
|
let mut idms_prox_write = self.idms.proxy_write_async(ct).await;
|
||||||
let res = spanned!("actors::v1_write::handle<IdmAccountUnixSetCredMessage>", {
|
let res = spanned!("actors::v1_write::handle<IdmAccountUnixSetCredMessage>", {
|
||||||
let ident = idms_prox_write
|
let ident = idms_prox_write
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
.and_then(|uat| idms_prox_write.process_uat_to_identity(&uat, ct))
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!(err = ?e, "Invalid identity");
|
admin_error!(err = ?e, "Invalid identity");
|
||||||
e
|
e
|
||||||
|
@ -1163,8 +1191,7 @@ impl QueryServerWriteV1 {
|
||||||
let ct = duration_from_epoch_now();
|
let ct = duration_from_epoch_now();
|
||||||
|
|
||||||
let ident = idms_prox_write
|
let ident = idms_prox_write
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
.and_then(|uat| idms_prox_write.process_uat_to_identity(&uat, ct))
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!(err = ?e, "Invalid identity");
|
admin_error!(err = ?e, "Invalid identity");
|
||||||
e
|
e
|
||||||
|
@ -1229,8 +1256,7 @@ impl QueryServerWriteV1 {
|
||||||
let ct = duration_from_epoch_now();
|
let ct = duration_from_epoch_now();
|
||||||
|
|
||||||
let ident = idms_prox_write
|
let ident = idms_prox_write
|
||||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
.validate_and_parse_token_to_ident(uat.as_deref(), ct)
|
||||||
.and_then(|uat| idms_prox_write.process_uat_to_identity(&uat, ct))
|
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!(err = ?e, "Invalid identity");
|
admin_error!(err = ?e, "Invalid identity");
|
||||||
e
|
e
|
||||||
|
@ -1244,10 +1270,7 @@ impl QueryServerWriteV1 {
|
||||||
e
|
e
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let ml = ModifyList::new_remove(
|
let ml = ModifyList::new_remove("oauth2_rs_scope_map", PartialValue::Refer(group_uuid));
|
||||||
"oauth2_rs_scope_map",
|
|
||||||
PartialValue::new_oauthscopemap(group_uuid),
|
|
||||||
);
|
|
||||||
|
|
||||||
let mdf = match ModifyEvent::from_internal_parts(
|
let mdf = match ModifyEvent::from_internal_parts(
|
||||||
ident,
|
ident,
|
||||||
|
|
|
@ -397,7 +397,7 @@ fn from_vec_dbval1(attr_val: Vec<DbValueV1>) -> Result<DbValueSetV2, OperationEr
|
||||||
}
|
}
|
||||||
// Neither of these should exist yet.
|
// Neither of these should exist yet.
|
||||||
Some(DbValueV1::TrustedDeviceEnrollment { u: _ })
|
Some(DbValueV1::TrustedDeviceEnrollment { u: _ })
|
||||||
| Some(DbValueV1::AuthSession { u: _ })
|
| Some(DbValueV1::Session { u: _ })
|
||||||
| None => {
|
| None => {
|
||||||
// Shiiiiii
|
// Shiiiiii
|
||||||
debug_assert!(false);
|
debug_assert!(false);
|
||||||
|
|
|
@ -285,6 +285,16 @@ pub struct DbValueCredV1 {
|
||||||
pub data: DbCred,
|
pub data: DbCred,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub enum DbApiToken {
|
||||||
|
V1 {
|
||||||
|
#[serde(rename = "u")]
|
||||||
|
uuid: Uuid,
|
||||||
|
#[serde(rename = "s")]
|
||||||
|
secret: DbPasswordV1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub enum DbValuePasskeyV1 {
|
pub enum DbValuePasskeyV1 {
|
||||||
V4 { u: Uuid, t: String, k: PasskeyV4 },
|
V4 { u: Uuid, t: String, k: PasskeyV4 },
|
||||||
|
@ -341,6 +351,30 @@ pub struct DbValueOauthScopeMapV1 {
|
||||||
pub data: Vec<String>,
|
pub data: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub enum DbValueIdentityId {
|
||||||
|
#[serde(rename = "v1i")]
|
||||||
|
V1Internal,
|
||||||
|
#[serde(rename = "v1u")]
|
||||||
|
V1Uuid(Uuid),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub enum DbValueSession {
|
||||||
|
V1 {
|
||||||
|
#[serde(rename = "u")]
|
||||||
|
refer: Uuid,
|
||||||
|
#[serde(rename = "l")]
|
||||||
|
label: String,
|
||||||
|
#[serde(rename = "e")]
|
||||||
|
expiry: Option<String>,
|
||||||
|
#[serde(rename = "i")]
|
||||||
|
issued_at: String,
|
||||||
|
#[serde(rename = "b")]
|
||||||
|
issued_by: DbValueIdentityId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub enum DbValueV1 {
|
pub enum DbValueV1 {
|
||||||
#[serde(rename = "U8")]
|
#[serde(rename = "U8")]
|
||||||
|
@ -354,7 +388,7 @@ pub enum DbValueV1 {
|
||||||
#[serde(rename = "BO")]
|
#[serde(rename = "BO")]
|
||||||
Bool(bool),
|
Bool(bool),
|
||||||
#[serde(rename = "SY")]
|
#[serde(rename = "SY")]
|
||||||
SyntaxType(usize),
|
SyntaxType(u16),
|
||||||
#[serde(rename = "IN")]
|
#[serde(rename = "IN")]
|
||||||
IndexType(usize),
|
IndexType(usize),
|
||||||
#[serde(rename = "RF")]
|
#[serde(rename = "RF")]
|
||||||
|
@ -403,7 +437,7 @@ pub enum DbValueV1 {
|
||||||
#[serde(rename = "TE")]
|
#[serde(rename = "TE")]
|
||||||
TrustedDeviceEnrollment { u: Uuid },
|
TrustedDeviceEnrollment { u: Uuid },
|
||||||
#[serde(rename = "AS")]
|
#[serde(rename = "AS")]
|
||||||
AuthSession { u: Uuid },
|
Session { u: Uuid },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
@ -419,7 +453,7 @@ pub enum DbValueSetV2 {
|
||||||
#[serde(rename = "BO")]
|
#[serde(rename = "BO")]
|
||||||
Bool(Vec<bool>),
|
Bool(Vec<bool>),
|
||||||
#[serde(rename = "SY")]
|
#[serde(rename = "SY")]
|
||||||
SyntaxType(Vec<usize>),
|
SyntaxType(Vec<u16>),
|
||||||
#[serde(rename = "IN")]
|
#[serde(rename = "IN")]
|
||||||
IndexType(Vec<usize>),
|
IndexType(Vec<usize>),
|
||||||
#[serde(rename = "RF")]
|
#[serde(rename = "RF")]
|
||||||
|
@ -469,7 +503,11 @@ pub enum DbValueSetV2 {
|
||||||
#[serde(rename = "TE")]
|
#[serde(rename = "TE")]
|
||||||
TrustedDeviceEnrollment(Vec<Uuid>),
|
TrustedDeviceEnrollment(Vec<Uuid>),
|
||||||
#[serde(rename = "AS")]
|
#[serde(rename = "AS")]
|
||||||
AuthSession(Vec<Uuid>),
|
Session(Vec<DbValueSession>),
|
||||||
|
#[serde(rename = "JE")]
|
||||||
|
JwsKeyEs256(Vec<Vec<u8>>),
|
||||||
|
#[serde(rename = "JR")]
|
||||||
|
JwsKeyRs256(Vec<Vec<u8>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DbValueSetV2 {
|
impl DbValueSetV2 {
|
||||||
|
@ -505,7 +543,9 @@ impl DbValueSetV2 {
|
||||||
DbValueSetV2::Passkey(set) => set.len(),
|
DbValueSetV2::Passkey(set) => set.len(),
|
||||||
DbValueSetV2::DeviceKey(set) => set.len(),
|
DbValueSetV2::DeviceKey(set) => set.len(),
|
||||||
DbValueSetV2::TrustedDeviceEnrollment(set) => set.len(),
|
DbValueSetV2::TrustedDeviceEnrollment(set) => set.len(),
|
||||||
DbValueSetV2::AuthSession(set) => set.len(),
|
DbValueSetV2::Session(set) => set.len(),
|
||||||
|
DbValueSetV2::JwsKeyEs256(set) => set.len(),
|
||||||
|
DbValueSetV2::JwsKeyRs256(set) => set.len(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,17 +60,17 @@ pub struct IdlArcSqlite {
|
||||||
|
|
||||||
pub struct IdlArcSqliteReadTransaction<'a> {
|
pub struct IdlArcSqliteReadTransaction<'a> {
|
||||||
db: IdlSqliteReadTransaction,
|
db: IdlSqliteReadTransaction,
|
||||||
entry_cache: ARCacheReadTxn<'a, u64, Arc<EntrySealedCommitted>>,
|
entry_cache: ARCacheReadTxn<'a, u64, Arc<EntrySealedCommitted>, ()>,
|
||||||
idl_cache: ARCacheReadTxn<'a, IdlCacheKey, Box<IDLBitRange>>,
|
idl_cache: ARCacheReadTxn<'a, IdlCacheKey, Box<IDLBitRange>, ()>,
|
||||||
name_cache: ARCacheReadTxn<'a, NameCacheKey, NameCacheValue>,
|
name_cache: ARCacheReadTxn<'a, NameCacheKey, NameCacheValue, ()>,
|
||||||
allids: CowCellReadTxn<IDLBitRange>,
|
allids: CowCellReadTxn<IDLBitRange>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct IdlArcSqliteWriteTransaction<'a> {
|
pub struct IdlArcSqliteWriteTransaction<'a> {
|
||||||
db: IdlSqliteWriteTransaction,
|
db: IdlSqliteWriteTransaction,
|
||||||
entry_cache: ARCacheWriteTxn<'a, u64, Arc<EntrySealedCommitted>>,
|
entry_cache: ARCacheWriteTxn<'a, u64, Arc<EntrySealedCommitted>, ()>,
|
||||||
idl_cache: ARCacheWriteTxn<'a, IdlCacheKey, Box<IDLBitRange>>,
|
idl_cache: ARCacheWriteTxn<'a, IdlCacheKey, Box<IDLBitRange>, ()>,
|
||||||
name_cache: ARCacheWriteTxn<'a, NameCacheKey, NameCacheValue>,
|
name_cache: ARCacheWriteTxn<'a, NameCacheKey, NameCacheValue, ()>,
|
||||||
op_ts_max: CowCellWriteTxn<'a, Option<Duration>>,
|
op_ts_max: CowCellWriteTxn<'a, Option<Duration>>,
|
||||||
allids: CowCellWriteTxn<'a, IDLBitRange>,
|
allids: CowCellWriteTxn<'a, IDLBitRange>,
|
||||||
maxid: CowCellWriteTxn<'a, u64>,
|
maxid: CowCellWriteTxn<'a, u64>,
|
||||||
|
|
|
@ -434,7 +434,7 @@ pub const JSON_IDM_ACP_ACCOUNT_READ_PRIV_V1: &str = r#"{
|
||||||
"{\"and\": [{\"eq\": [\"class\",\"account\"]}, {\"andnot\": {\"or\": [{\"eq\": [\"memberof\",\"00000000-0000-0000-0000-000000001000\"]}, {\"eq\": [\"class\", \"tombstone\"]}, {\"eq\": [\"class\", \"recycled\"]}]}}]}"
|
"{\"and\": [{\"eq\": [\"class\",\"account\"]}, {\"andnot\": {\"or\": [{\"eq\": [\"memberof\",\"00000000-0000-0000-0000-000000001000\"]}, {\"eq\": [\"class\", \"tombstone\"]}, {\"eq\": [\"class\", \"recycled\"]}]}}]}"
|
||||||
],
|
],
|
||||||
"acp_search_attr": [
|
"acp_search_attr": [
|
||||||
"class", "name", "spn", "uuid", "displayname", "ssh_publickey", "primary_credential", "memberof", "mail", "gidnumber", "account_expire", "account_valid_from", "passkeys", "devicekeys"
|
"class", "name", "spn", "uuid", "displayname", "ssh_publickey", "primary_credential", "memberof", "mail", "gidnumber", "account_expire", "account_valid_from", "passkeys", "devicekeys", "api_token_session"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}"#;
|
}"#;
|
||||||
|
@ -456,10 +456,10 @@ pub const JSON_IDM_ACP_ACCOUNT_WRITE_PRIV_V1: &str = r#"{
|
||||||
"{\"and\": [{\"eq\": [\"class\",\"account\"]}, {\"andnot\": {\"or\": [{\"eq\": [\"memberof\",\"00000000-0000-0000-0000-000000001000\"]}, {\"eq\": [\"class\", \"tombstone\"]}, {\"eq\": [\"class\", \"recycled\"]}]}}]}"
|
"{\"and\": [{\"eq\": [\"class\",\"account\"]}, {\"andnot\": {\"or\": [{\"eq\": [\"memberof\",\"00000000-0000-0000-0000-000000001000\"]}, {\"eq\": [\"class\", \"tombstone\"]}, {\"eq\": [\"class\", \"recycled\"]}]}}]}"
|
||||||
],
|
],
|
||||||
"acp_modify_removedattr": [
|
"acp_modify_removedattr": [
|
||||||
"name", "displayname", "ssh_publickey", "primary_credential", "mail", "account_expire", "account_valid_from", "passkeys", "devicekeys"
|
"name", "displayname", "ssh_publickey", "primary_credential", "mail", "account_expire", "account_valid_from", "passkeys", "devicekeys", "api_token_session"
|
||||||
],
|
],
|
||||||
"acp_modify_presentattr": [
|
"acp_modify_presentattr": [
|
||||||
"name", "displayname", "ssh_publickey", "primary_credential", "mail", "account_expire", "account_valid_from", "passkeys", "devicekeys"
|
"name", "displayname", "ssh_publickey", "primary_credential", "mail", "account_expire", "account_valid_from", "passkeys", "devicekeys", "api_token_session"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}"#;
|
}"#;
|
||||||
|
@ -591,7 +591,7 @@ pub const JSON_IDM_ACP_HP_ACCOUNT_READ_PRIV_V1: &str = r#"{
|
||||||
"{\"and\": [{\"eq\": [\"class\",\"account\"]}, {\"eq\": [\"memberof\",\"00000000-0000-0000-0000-000000001000\"]}, {\"andnot\": {\"or\": [{\"eq\": [\"class\", \"tombstone\"]}, {\"eq\": [\"class\", \"recycled\"]}]}}]}"
|
"{\"and\": [{\"eq\": [\"class\",\"account\"]}, {\"eq\": [\"memberof\",\"00000000-0000-0000-0000-000000001000\"]}, {\"andnot\": {\"or\": [{\"eq\": [\"class\", \"tombstone\"]}, {\"eq\": [\"class\", \"recycled\"]}]}}]}"
|
||||||
],
|
],
|
||||||
"acp_search_attr": [
|
"acp_search_attr": [
|
||||||
"class", "name", "spn", "uuid", "displayname", "ssh_publickey", "primary_credential", "memberof", "account_expire", "account_valid_from", "passkeys", "devicekeys"
|
"class", "name", "spn", "uuid", "displayname", "ssh_publickey", "primary_credential", "memberof", "account_expire", "account_valid_from", "passkeys", "devicekeys", "api_token_session"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}"#;
|
}"#;
|
||||||
|
@ -613,10 +613,10 @@ pub const JSON_IDM_ACP_HP_ACCOUNT_WRITE_PRIV_V1: &str = r#"{
|
||||||
"{\"and\": [{\"eq\": [\"class\",\"account\"]}, {\"eq\": [\"memberof\",\"00000000-0000-0000-0000-000000001000\"]}, {\"andnot\": {\"or\": [{\"eq\": [\"class\", \"tombstone\"]}, {\"eq\": [\"class\", \"recycled\"]}]}}]}"
|
"{\"and\": [{\"eq\": [\"class\",\"account\"]}, {\"eq\": [\"memberof\",\"00000000-0000-0000-0000-000000001000\"]}, {\"andnot\": {\"or\": [{\"eq\": [\"class\", \"tombstone\"]}, {\"eq\": [\"class\", \"recycled\"]}]}}]}"
|
||||||
],
|
],
|
||||||
"acp_modify_removedattr": [
|
"acp_modify_removedattr": [
|
||||||
"name", "displayname", "ssh_publickey", "primary_credential", "account_expire", "account_valid_from", "passkeys", "devicekeys"
|
"name", "displayname", "ssh_publickey", "primary_credential", "account_expire", "account_valid_from", "passkeys", "devicekeys", "api_token_session"
|
||||||
],
|
],
|
||||||
"acp_modify_presentattr": [
|
"acp_modify_presentattr": [
|
||||||
"name", "displayname", "ssh_publickey", "primary_credential", "account_expire", "account_valid_from", "passkeys", "devicekeys"
|
"name", "displayname", "ssh_publickey", "primary_credential", "account_expire", "account_valid_from", "passkeys", "devicekeys", "api_token_session"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}"#;
|
}"#;
|
||||||
|
|
|
@ -472,7 +472,7 @@ pub const JSON_SYSTEM_INFO_V1: &str = r#"{
|
||||||
"class": ["object", "system_info", "system"],
|
"class": ["object", "system_info", "system"],
|
||||||
"uuid": ["00000000-0000-0000-0000-ffffff000001"],
|
"uuid": ["00000000-0000-0000-0000-ffffff000001"],
|
||||||
"description": ["System (local) info and metadata object."],
|
"description": ["System (local) info and metadata object."],
|
||||||
"version": ["7"]
|
"version": ["8"]
|
||||||
}
|
}
|
||||||
}"#;
|
}"#;
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ pub use crate::constants::system_config::*;
|
||||||
pub use crate::constants::uuids::*;
|
pub use crate::constants::uuids::*;
|
||||||
|
|
||||||
// Increment this as we add new schema types and values!!!
|
// Increment this as we add new schema types and values!!!
|
||||||
pub const SYSTEM_INDEX_VERSION: i64 = 25;
|
pub const SYSTEM_INDEX_VERSION: i64 = 26;
|
||||||
// On test builds, define to 60 seconds
|
// On test builds, define to 60 seconds
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub const PURGE_FREQUENCY: u64 = 60;
|
pub const PURGE_FREQUENCY: u64 = 60;
|
||||||
|
|
|
@ -839,6 +839,37 @@ pub const JSON_SCHEMA_ATTR_RS256_PRIVATE_KEY_DER: &str = r#"{
|
||||||
}
|
}
|
||||||
}"#;
|
}"#;
|
||||||
|
|
||||||
|
pub const JSON_SCHEMA_ATTR_JWS_ES256_PRIVATE_KEY: &str = r#"{
|
||||||
|
"attrs": {
|
||||||
|
"class": [
|
||||||
|
"object",
|
||||||
|
"system",
|
||||||
|
"attributetype"
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
"An es256 private key for jws"
|
||||||
|
],
|
||||||
|
"index": [
|
||||||
|
"EQUALITY"
|
||||||
|
],
|
||||||
|
"unique": [
|
||||||
|
"true"
|
||||||
|
],
|
||||||
|
"multivalue": [
|
||||||
|
"false"
|
||||||
|
],
|
||||||
|
"attributename": [
|
||||||
|
"jws_es256_private_key"
|
||||||
|
],
|
||||||
|
"syntax": [
|
||||||
|
"JWS_KEY_ES256"
|
||||||
|
],
|
||||||
|
"uuid": [
|
||||||
|
"00000000-0000-0000-0000-ffff00000110"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
pub const JSON_SCHEMA_ATTR_OAUTH2_ALLOW_INSECURE_CLIENT_DISABLE_PKCE: &str = r#"{
|
pub const JSON_SCHEMA_ATTR_OAUTH2_ALLOW_INSECURE_CLIENT_DISABLE_PKCE: &str = r#"{
|
||||||
"attrs": {
|
"attrs": {
|
||||||
"class": [
|
"class": [
|
||||||
|
@ -1047,6 +1078,37 @@ pub const JSON_SCHEMA_ATTR_OAUTH2_PREFER_SHORT_USERNAME: &str = r#"{
|
||||||
}
|
}
|
||||||
}"#;
|
}"#;
|
||||||
|
|
||||||
|
pub const JSON_SCHEMA_ATTR_API_TOKEN_SESSION: &str = r#"{
|
||||||
|
"attrs": {
|
||||||
|
"class": [
|
||||||
|
"object",
|
||||||
|
"system",
|
||||||
|
"attributetype"
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
"A session entry related to an issued api token"
|
||||||
|
],
|
||||||
|
"index": [
|
||||||
|
"EQUALITY"
|
||||||
|
],
|
||||||
|
"unique": [
|
||||||
|
"true"
|
||||||
|
],
|
||||||
|
"multivalue": [
|
||||||
|
"true"
|
||||||
|
],
|
||||||
|
"attributename": [
|
||||||
|
"api_token_session"
|
||||||
|
],
|
||||||
|
"syntax": [
|
||||||
|
"SESSION"
|
||||||
|
],
|
||||||
|
"uuid": [
|
||||||
|
"00000000-0000-0000-0000-ffff00000111"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
// === classes ===
|
// === classes ===
|
||||||
|
|
||||||
pub const JSON_SCHEMA_CLASS_PERSON: &str = r#"
|
pub const JSON_SCHEMA_CLASS_PERSON: &str = r#"
|
||||||
|
@ -1220,7 +1282,9 @@ pub const JSON_SCHEMA_CLASS_SERVICE_ACCOUNT: &str = r#"
|
||||||
],
|
],
|
||||||
"systemmay": [
|
"systemmay": [
|
||||||
"mail",
|
"mail",
|
||||||
"primary_credential"
|
"primary_credential",
|
||||||
|
"jws_es256_private_key",
|
||||||
|
"api_token_session"
|
||||||
],
|
],
|
||||||
"uuid": [
|
"uuid": [
|
||||||
"00000000-0000-0000-0000-ffff00000106"
|
"00000000-0000-0000-0000-ffff00000106"
|
||||||
|
|
|
@ -186,6 +186,9 @@ pub const _UUID_SCHEMA_CLASS_DYNGROUP: Uuid = uuid!("00000000-0000-0000-0000-fff
|
||||||
pub const _UUID_SCHEMA_ATTR_DYNGROUP_FILTER: Uuid = uuid!("00000000-0000-0000-0000-ffff00000108");
|
pub const _UUID_SCHEMA_ATTR_DYNGROUP_FILTER: Uuid = uuid!("00000000-0000-0000-0000-ffff00000108");
|
||||||
pub const _UUID_SCHEMA_ATTR_OAUTH2_PREFERR_SHORT_USERNAME: Uuid =
|
pub const _UUID_SCHEMA_ATTR_OAUTH2_PREFERR_SHORT_USERNAME: Uuid =
|
||||||
uuid!("00000000-0000-0000-0000-ffff00000109");
|
uuid!("00000000-0000-0000-0000-ffff00000109");
|
||||||
|
pub const _UUID_SCHEMA_ATTR_JWS_ES256_PRIVATE_KEY: Uuid =
|
||||||
|
uuid!("00000000-0000-0000-0000-ffff00000110");
|
||||||
|
pub const _UUID_SCHEMA_ATTR_API_TOKEN_SESSION: Uuid = uuid!("00000000-0000-0000-0000-ffff00000111");
|
||||||
|
|
||||||
// System and domain infos
|
// System and domain infos
|
||||||
// I'd like to strongly criticise william of the past for making poor choices about these allocations.
|
// I'd like to strongly criticise william of the past for making poor choices about these allocations.
|
||||||
|
|
|
@ -33,7 +33,7 @@ use crate::repl::cid::Cid;
|
||||||
use crate::repl::entry::EntryChangelog;
|
use crate::repl::entry::EntryChangelog;
|
||||||
use crate::schema::{SchemaAttribute, SchemaClass, SchemaTransaction};
|
use crate::schema::{SchemaAttribute, SchemaClass, SchemaTransaction};
|
||||||
use crate::value::{IndexType, SyntaxType};
|
use crate::value::{IndexType, SyntaxType};
|
||||||
use crate::value::{IntentTokenState, PartialValue, Value};
|
use crate::value::{IntentTokenState, PartialValue, Session, Value};
|
||||||
use crate::valueset::{self, ValueSet};
|
use crate::valueset::{self, ValueSet};
|
||||||
use kanidm_proto::v1::Entry as ProtoEntry;
|
use kanidm_proto::v1::Entry as ProtoEntry;
|
||||||
use kanidm_proto::v1::Filter as ProtoFilter;
|
use kanidm_proto::v1::Filter as ProtoFilter;
|
||||||
|
@ -44,6 +44,7 @@ use crate::be::dbentry::{DbEntry, DbEntryV2, DbEntryVers};
|
||||||
use crate::be::dbvalue::DbValueSetV2;
|
use crate::be::dbvalue::DbValueSetV2;
|
||||||
use crate::be::{IdxKey, IdxSlope};
|
use crate::be::{IdxKey, IdxSlope};
|
||||||
|
|
||||||
|
use compact_jwt::JwsSigner;
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use ldap3_proto::simple::{LdapPartialAttribute, LdapSearchResultEntry};
|
use ldap3_proto::simple::{LdapPartialAttribute, LdapSearchResultEntry};
|
||||||
use smartstring::alias::String as AttrString;
|
use smartstring::alias::String as AttrString;
|
||||||
|
@ -1865,6 +1866,14 @@ impl<VALID, STATE> Entry<VALID, STATE> {
|
||||||
self.attrs.get(attr).and_then(|vs| vs.as_intenttoken_map())
|
self.attrs.get(attr).and_then(|vs| vs.as_intenttoken_map())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn get_ava_as_session_map(
|
||||||
|
&self,
|
||||||
|
attr: &str,
|
||||||
|
) -> Option<&std::collections::BTreeMap<Uuid, Session>> {
|
||||||
|
self.attrs.get(attr).and_then(|vs| vs.as_session_map())
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
/// If possible, return an iterator over the set of values transformed into a `&str`.
|
/// If possible, return an iterator over the set of values transformed into a `&str`.
|
||||||
pub fn get_ava_iter_iname(&self, attr: &str) -> Option<impl Iterator<Item = &str>> {
|
pub fn get_ava_iter_iname(&self, attr: &str) -> Option<impl Iterator<Item = &str>> {
|
||||||
|
@ -2031,6 +2040,12 @@ impl<VALID, STATE> Entry<VALID, STATE> {
|
||||||
.and_then(|vs| vs.to_private_binary_single())
|
.and_then(|vs| vs.to_private_binary_single())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_ava_single_jws_key_es256(&self, attr: &str) -> Option<&JwsSigner> {
|
||||||
|
self.attrs
|
||||||
|
.get(attr)
|
||||||
|
.and_then(|vs| vs.to_jws_key_es256_single())
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
/// Return a single security principle name, if valid to transform this value.
|
/// Return a single security principle name, if valid to transform this value.
|
||||||
pub(crate) fn generate_spn(&self, domain_name: &str) -> Option<Value> {
|
pub(crate) fn generate_spn(&self, domain_name: &str) -> Option<Value> {
|
||||||
|
|
|
@ -28,7 +28,7 @@ use kanidm_proto::v1::ModifyList as ProtoModifyList;
|
||||||
use kanidm_proto::v1::OperationError;
|
use kanidm_proto::v1::OperationError;
|
||||||
use kanidm_proto::v1::{
|
use kanidm_proto::v1::{
|
||||||
AuthCredential, AuthMech, AuthRequest, AuthStep, CreateRequest, DeleteRequest, ModifyRequest,
|
AuthCredential, AuthMech, AuthRequest, AuthStep, CreateRequest, DeleteRequest, ModifyRequest,
|
||||||
SearchRequest, SearchResponse, UserAuthToken, WhoamiResponse,
|
SearchRequest, SearchResponse, WhoamiResponse,
|
||||||
};
|
};
|
||||||
|
|
||||||
use ldap3_proto::simple::LdapFilter;
|
use ldap3_proto::simple::LdapFilter;
|
||||||
|
@ -878,25 +878,21 @@ impl AuthResult {
|
||||||
|
|
||||||
pub struct WhoamiResult {
|
pub struct WhoamiResult {
|
||||||
youare: ProtoEntry,
|
youare: ProtoEntry,
|
||||||
uat: UserAuthToken,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WhoamiResult {
|
impl WhoamiResult {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
qs: &QueryServerReadTransaction,
|
qs: &QueryServerReadTransaction,
|
||||||
e: &Entry<EntryReduced, EntryCommitted>,
|
e: &Entry<EntryReduced, EntryCommitted>,
|
||||||
uat: UserAuthToken,
|
|
||||||
) -> Result<Self, OperationError> {
|
) -> Result<Self, OperationError> {
|
||||||
Ok(WhoamiResult {
|
Ok(WhoamiResult {
|
||||||
youare: e.to_pe(qs)?,
|
youare: e.to_pe(qs)?,
|
||||||
uat,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn response(self) -> WhoamiResponse {
|
pub fn response(self) -> WhoamiResponse {
|
||||||
WhoamiResponse {
|
WhoamiResponse {
|
||||||
youare: self.youare,
|
youare: self.youare,
|
||||||
uat: self.uat,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -285,7 +285,12 @@ impl Filter<FilterValid> {
|
||||||
ev: &Identity,
|
ev: &Identity,
|
||||||
idxmeta: Option<&IdxMeta>,
|
idxmeta: Option<&IdxMeta>,
|
||||||
mut rsv_cache: Option<
|
mut rsv_cache: Option<
|
||||||
&mut ARCacheReadTxn<'a, (IdentityId, Filter<FilterValid>), Filter<FilterValidResolved>>,
|
&mut ARCacheReadTxn<
|
||||||
|
'a,
|
||||||
|
(IdentityId, Filter<FilterValid>),
|
||||||
|
Filter<FilterValidResolved>,
|
||||||
|
(),
|
||||||
|
>,
|
||||||
>,
|
>,
|
||||||
) -> Result<Filter<FilterValidResolved>, OperationError> {
|
) -> Result<Filter<FilterValidResolved>, OperationError> {
|
||||||
// Given a filter, resolve Not and SelfUuid to real terms.
|
// Given a filter, resolve Not and SelfUuid to real terms.
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
//! identity may consume during operations to prevent denial-of-service.
|
//! identity may consume during operations to prevent denial-of-service.
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use kanidm_proto::v1::UserAuthToken;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
@ -20,6 +19,17 @@ pub struct Limits {
|
||||||
pub filter_max_elements: usize,
|
pub filter_max_elements: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Limits {
|
||||||
|
fn default() -> Self {
|
||||||
|
Limits {
|
||||||
|
unindexed_allow: false,
|
||||||
|
search_max_results: 128,
|
||||||
|
search_max_filter_test: 256,
|
||||||
|
filter_max_elements: 32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Limits {
|
impl Limits {
|
||||||
pub fn unlimited() -> Self {
|
pub fn unlimited() -> Self {
|
||||||
Limits {
|
Limits {
|
||||||
|
@ -29,16 +39,6 @@ impl Limits {
|
||||||
filter_max_elements: usize::MAX,
|
filter_max_elements: usize::MAX,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// From a userauthtoken
|
|
||||||
pub fn from_uat(uat: &UserAuthToken) -> Self {
|
|
||||||
Limits {
|
|
||||||
unindexed_allow: uat.lim_uidx,
|
|
||||||
search_max_results: uat.lim_rmax,
|
|
||||||
search_max_filter_test: uat.lim_pmax,
|
|
||||||
filter_max_elements: uat.lim_fmax,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
|
@ -3,6 +3,7 @@ use crate::prelude::*;
|
||||||
use crate::schema::SchemaTransaction;
|
use crate::schema::SchemaTransaction;
|
||||||
|
|
||||||
use kanidm_proto::v1::OperationError;
|
use kanidm_proto::v1::OperationError;
|
||||||
|
use kanidm_proto::v1::UiHint;
|
||||||
use kanidm_proto::v1::{AuthType, UserAuthToken};
|
use kanidm_proto::v1::{AuthType, UserAuthToken};
|
||||||
use kanidm_proto::v1::{BackupCodesView, CredentialStatus};
|
use kanidm_proto::v1::{BackupCodesView, CredentialStatus};
|
||||||
|
|
||||||
|
@ -26,6 +27,7 @@ use webauthn_rs::prelude::AuthenticationResult;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref PVCLASS_ACCOUNT: PartialValue = PartialValue::new_class("account");
|
static ref PVCLASS_ACCOUNT: PartialValue = PartialValue::new_class("account");
|
||||||
|
static ref PVCLASS_POSIXACCOUNT: PartialValue = PartialValue::new_class("posixaccount");
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! try_from_entry {
|
macro_rules! try_from_entry {
|
||||||
|
@ -93,7 +95,13 @@ macro_rules! try_from_entry {
|
||||||
let credential_update_intent_tokens = $value
|
let credential_update_intent_tokens = $value
|
||||||
.get_ava_as_intenttokens("credential_update_intent_token")
|
.get_ava_as_intenttokens("credential_update_intent_token")
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_else(|| BTreeMap::new());
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let mut ui_hints = BTreeSet::default();
|
||||||
|
|
||||||
|
if $value.attribute_equality("class", &PVCLASS_POSIXACCOUNT) {
|
||||||
|
ui_hints.insert(UiHint::PosixAccount);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Account {
|
Ok(Account {
|
||||||
uuid,
|
uuid,
|
||||||
|
@ -107,6 +115,7 @@ macro_rules! try_from_entry {
|
||||||
expire,
|
expire,
|
||||||
radius_secret,
|
radius_secret,
|
||||||
spn,
|
spn,
|
||||||
|
ui_hints,
|
||||||
mail_primary,
|
mail_primary,
|
||||||
mail,
|
mail,
|
||||||
credential_update_intent_tokens,
|
credential_update_intent_tokens,
|
||||||
|
@ -135,6 +144,7 @@ pub(crate) struct Account {
|
||||||
pub expire: Option<OffsetDateTime>,
|
pub expire: Option<OffsetDateTime>,
|
||||||
pub radius_secret: Option<String>,
|
pub radius_secret: Option<String>,
|
||||||
pub spn: String,
|
pub spn: String,
|
||||||
|
pub ui_hints: BTreeSet<UiHint>,
|
||||||
// TODO #256: When you add mail, you should update the check to zxcvbn
|
// TODO #256: When you add mail, you should update the check to zxcvbn
|
||||||
// to include these.
|
// to include these.
|
||||||
pub mail_primary: Option<String>,
|
pub mail_primary: Option<String>,
|
||||||
|
@ -206,13 +216,9 @@ impl Account {
|
||||||
displayname: self.displayname.clone(),
|
displayname: self.displayname.clone(),
|
||||||
spn: self.spn.clone(),
|
spn: self.spn.clone(),
|
||||||
mail_primary: self.mail_primary.clone(),
|
mail_primary: self.mail_primary.clone(),
|
||||||
|
ui_hints: self.ui_hints.clone(),
|
||||||
// application: None,
|
// application: None,
|
||||||
// groups: self.groups.iter().map(|g| g.to_proto()).collect(),
|
groups: self.groups.iter().map(|g| g.to_proto()).collect(),
|
||||||
// What's the best way to get access to these limits with regard to claims/other?
|
|
||||||
lim_uidx: false,
|
|
||||||
lim_rmax: 128,
|
|
||||||
lim_pmax: 256,
|
|
||||||
lim_fmax: 32,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -756,7 +756,7 @@ impl AuthSession {
|
||||||
.to_userauthtoken(session_id, *time, auth_type)
|
.to_userauthtoken(session_id, *time, auth_type)
|
||||||
.ok_or(OperationError::InvalidState)?;
|
.ok_or(OperationError::InvalidState)?;
|
||||||
|
|
||||||
let jwt = Jws { inner: uat };
|
let jwt = Jws::new(uat);
|
||||||
|
|
||||||
// Now encrypt and prepare the token for return to the client.
|
// Now encrypt and prepare the token for return to the client.
|
||||||
let token = jwt
|
let token = jwt
|
||||||
|
@ -1353,7 +1353,7 @@ mod tests {
|
||||||
.expect("Failed to setup passkey rego challenge");
|
.expect("Failed to setup passkey rego challenge");
|
||||||
|
|
||||||
let r = wa
|
let r = wa
|
||||||
.do_registration(webauthn.get_origin().clone(), chal)
|
.do_registration(webauthn.get_allowed_origins()[0].clone(), chal)
|
||||||
.expect("Failed to create soft passkey");
|
.expect("Failed to create soft passkey");
|
||||||
|
|
||||||
let wan_cred = webauthn
|
let wan_cred = webauthn
|
||||||
|
@ -1381,7 +1381,7 @@ mod tests {
|
||||||
.expect("Failed to setup passkey rego challenge");
|
.expect("Failed to setup passkey rego challenge");
|
||||||
|
|
||||||
let r = wa
|
let r = wa
|
||||||
.do_registration(webauthn.get_origin().clone(), chal)
|
.do_registration(webauthn.get_allowed_origins()[0].clone(), chal)
|
||||||
.expect("Failed to create soft securitykey");
|
.expect("Failed to create soft securitykey");
|
||||||
|
|
||||||
let wan_cred = webauthn
|
let wan_cred = webauthn
|
||||||
|
@ -1431,7 +1431,7 @@ mod tests {
|
||||||
let (mut session, chal) = start_webauthn_only_session!(&mut audit, account, &webauthn);
|
let (mut session, chal) = start_webauthn_only_session!(&mut audit, account, &webauthn);
|
||||||
|
|
||||||
let resp = wa
|
let resp = wa
|
||||||
.do_authentication(webauthn.get_origin().clone(), chal)
|
.do_authentication(webauthn.get_allowed_origins()[0].clone(), chal)
|
||||||
.expect("failed to use softtoken to authenticate");
|
.expect("failed to use softtoken to authenticate");
|
||||||
|
|
||||||
match session.validate_creds(
|
match session.validate_creds(
|
||||||
|
@ -1460,7 +1460,7 @@ mod tests {
|
||||||
|
|
||||||
let resp = wa
|
let resp = wa
|
||||||
// HERE -> we use inv_chal instead.
|
// HERE -> we use inv_chal instead.
|
||||||
.do_authentication(webauthn.get_origin().clone(), inv_chal)
|
.do_authentication(webauthn.get_allowed_origins()[0].clone(), inv_chal)
|
||||||
.expect("failed to use softtoken to authenticate");
|
.expect("failed to use softtoken to authenticate");
|
||||||
|
|
||||||
match session.validate_creds(
|
match session.validate_creds(
|
||||||
|
@ -1484,7 +1484,7 @@ mod tests {
|
||||||
.expect("Failed to setup webauthn rego challenge");
|
.expect("Failed to setup webauthn rego challenge");
|
||||||
|
|
||||||
let r = inv_wa
|
let r = inv_wa
|
||||||
.do_registration(webauthn.get_origin().clone(), chal)
|
.do_registration(webauthn.get_allowed_origins()[0].clone(), chal)
|
||||||
.expect("Failed to create soft token");
|
.expect("Failed to create soft token");
|
||||||
|
|
||||||
let inv_cred = webauthn
|
let inv_cred = webauthn
|
||||||
|
@ -1498,7 +1498,7 @@ mod tests {
|
||||||
|
|
||||||
// Create the response.
|
// Create the response.
|
||||||
let resp = inv_wa
|
let resp = inv_wa
|
||||||
.do_authentication(webauthn.get_origin().clone(), chal)
|
.do_authentication(webauthn.get_allowed_origins()[0].clone(), chal)
|
||||||
.expect("Failed to use softtoken for response.");
|
.expect("Failed to use softtoken for response.");
|
||||||
|
|
||||||
let (mut session, _chal) = start_webauthn_only_session!(&mut audit, account, &webauthn);
|
let (mut session, _chal) = start_webauthn_only_session!(&mut audit, account, &webauthn);
|
||||||
|
@ -1592,7 +1592,7 @@ mod tests {
|
||||||
|
|
||||||
let resp = wa
|
let resp = wa
|
||||||
// HERE -> we use inv_chal instead.
|
// HERE -> we use inv_chal instead.
|
||||||
.do_authentication(webauthn.get_origin().clone(), inv_chal)
|
.do_authentication(webauthn.get_allowed_origins()[0].clone(), inv_chal)
|
||||||
.expect("failed to use softtoken to authenticate");
|
.expect("failed to use softtoken to authenticate");
|
||||||
|
|
||||||
match session.validate_creds(
|
match session.validate_creds(
|
||||||
|
@ -1615,7 +1615,7 @@ mod tests {
|
||||||
let chal = chal.unwrap();
|
let chal = chal.unwrap();
|
||||||
|
|
||||||
let resp = wa
|
let resp = wa
|
||||||
.do_authentication(webauthn.get_origin().clone(), chal)
|
.do_authentication(webauthn.get_allowed_origins()[0].clone(), chal)
|
||||||
.expect("failed to use softtoken to authenticate");
|
.expect("failed to use softtoken to authenticate");
|
||||||
|
|
||||||
match session.validate_creds(
|
match session.validate_creds(
|
||||||
|
@ -1655,7 +1655,7 @@ mod tests {
|
||||||
let chal = chal.unwrap();
|
let chal = chal.unwrap();
|
||||||
|
|
||||||
let resp = wa
|
let resp = wa
|
||||||
.do_authentication(webauthn.get_origin().clone(), chal)
|
.do_authentication(webauthn.get_allowed_origins()[0].clone(), chal)
|
||||||
.expect("failed to use softtoken to authenticate");
|
.expect("failed to use softtoken to authenticate");
|
||||||
|
|
||||||
match session.validate_creds(
|
match session.validate_creds(
|
||||||
|
@ -1771,7 +1771,7 @@ mod tests {
|
||||||
|
|
||||||
let resp = wa
|
let resp = wa
|
||||||
// HERE -> we use inv_chal instead.
|
// HERE -> we use inv_chal instead.
|
||||||
.do_authentication(webauthn.get_origin().clone(), inv_chal)
|
.do_authentication(webauthn.get_allowed_origins()[0].clone(), inv_chal)
|
||||||
.expect("failed to use softtoken to authenticate");
|
.expect("failed to use softtoken to authenticate");
|
||||||
|
|
||||||
match session.validate_creds(
|
match session.validate_creds(
|
||||||
|
@ -1794,7 +1794,7 @@ mod tests {
|
||||||
let chal = chal.unwrap();
|
let chal = chal.unwrap();
|
||||||
|
|
||||||
let resp = wa
|
let resp = wa
|
||||||
.do_authentication(webauthn.get_origin().clone(), chal)
|
.do_authentication(webauthn.get_allowed_origins()[0].clone(), chal)
|
||||||
.expect("failed to use softtoken to authenticate");
|
.expect("failed to use softtoken to authenticate");
|
||||||
|
|
||||||
match session.validate_creds(
|
match session.validate_creds(
|
||||||
|
@ -1892,7 +1892,7 @@ mod tests {
|
||||||
let chal = chal.unwrap();
|
let chal = chal.unwrap();
|
||||||
|
|
||||||
let resp = wa
|
let resp = wa
|
||||||
.do_authentication(webauthn.get_origin().clone(), chal)
|
.do_authentication(webauthn.get_allowed_origins()[0].clone(), chal)
|
||||||
.expect("failed to use softtoken to authenticate");
|
.expect("failed to use softtoken to authenticate");
|
||||||
|
|
||||||
match session.validate_creds(
|
match session.validate_creds(
|
||||||
|
|
|
@ -886,7 +886,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
||||||
impl<'a> IdmServerCredUpdateTransaction<'a> {
|
impl<'a> IdmServerCredUpdateTransaction<'a> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn get_origin(&self) -> &Url {
|
pub fn get_origin(&self) -> &Url {
|
||||||
self.webauthn.get_origin()
|
&self.webauthn.get_allowed_origins()[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_current_session(
|
fn get_current_session(
|
||||||
|
@ -962,9 +962,8 @@ impl<'a> IdmServerCredUpdateTransaction<'a> {
|
||||||
PasswordQuality::TooShort(PW_MIN_LENGTH)
|
PasswordQuality::TooShort(PW_MIN_LENGTH)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// check account pwpolicy (for 3 or 4)? Do we need pw strength beyond this
|
// PW's should always be enforced as strong as possible.
|
||||||
// or should we be enforcing mfa instead
|
if entropy.score() < 4 {
|
||||||
if entropy.score() < 3 {
|
|
||||||
// The password is too week as per:
|
// The password is too week as per:
|
||||||
// https://docs.rs/zxcvbn/2.0.0/zxcvbn/struct.Entropy.html
|
// https://docs.rs/zxcvbn/2.0.0/zxcvbn/struct.Entropy.html
|
||||||
let feedback: zxcvbn::feedback::Feedback = entropy
|
let feedback: zxcvbn::feedback::Feedback = entropy
|
||||||
|
|
|
@ -274,3 +274,13 @@ impl LdapAuthEvent {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct LdapTokenAuthEvent {
|
||||||
|
pub token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LdapTokenAuthEvent {
|
||||||
|
pub fn from_parts(token: String) -> Result<Self, OperationError> {
|
||||||
|
Ok(LdapTokenAuthEvent { token })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -12,23 +12,29 @@ lazy_static! {
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Group {
|
pub struct Group {
|
||||||
name: String,
|
spn: String,
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
// We'll probably add policy and claims later to this
|
// We'll probably add policy and claims later to this
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! try_from_account_e {
|
macro_rules! try_from_account_e {
|
||||||
($value:expr, $qs:expr) => {{
|
($value:expr, $qs:expr) => {{
|
||||||
|
/*
|
||||||
let name = $value
|
let name = $value
|
||||||
.get_ava_single_iname("name")
|
.get_ava_single_iname("name")
|
||||||
.map(str::to_string)
|
.map(str::to_string)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
OperationError::InvalidAccountState("Missing attribute: name".to_string())
|
OperationError::InvalidAccountState("Missing attribute: name".to_string())
|
||||||
})?;
|
})?;
|
||||||
|
*/
|
||||||
|
|
||||||
|
let spn = $value.get_ava_single_proto_string("spn").ok_or(
|
||||||
|
OperationError::InvalidAccountState("Missing attribute: spn".to_string()),
|
||||||
|
)?;
|
||||||
|
|
||||||
let uuid = $value.get_uuid();
|
let uuid = $value.get_uuid();
|
||||||
|
|
||||||
let upg = Group { name, uuid };
|
let upg = Group { spn, uuid };
|
||||||
|
|
||||||
let mut groups: Vec<Group> = match $value.get_ava_as_refuuid("memberof") {
|
let mut groups: Vec<Group> = match $value.get_ava_as_refuuid("memberof") {
|
||||||
Some(riter) => {
|
Some(riter) => {
|
||||||
|
@ -48,6 +54,7 @@ macro_rules! try_from_account_e {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|e| Group::try_from_entry(e.as_ref()))
|
.map(|e| Group::try_from_entry(e.as_ref()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
groups.map_err(|e| {
|
groups.map_err(|e| {
|
||||||
admin_error!(?e, "failed to transform group entries to groups");
|
admin_error!(?e, "failed to transform group entries to groups");
|
||||||
e
|
e
|
||||||
|
@ -95,21 +102,29 @@ impl Group {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now extract our needed attributes
|
// Now extract our needed attributes
|
||||||
|
/*
|
||||||
let name = value
|
let name = value
|
||||||
.get_ava_single_iname("name")
|
.get_ava_single_iname("name")
|
||||||
.map(|s| s.to_string())
|
.map(|s| s.to_string())
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
OperationError::InvalidAccountState("Missing attribute: name".to_string())
|
OperationError::InvalidAccountState("Missing attribute: name".to_string())
|
||||||
})?;
|
})?;
|
||||||
|
*/
|
||||||
|
let spn =
|
||||||
|
value
|
||||||
|
.get_ava_single_proto_string("spn")
|
||||||
|
.ok_or(OperationError::InvalidAccountState(
|
||||||
|
"Missing attribute: spn".to_string(),
|
||||||
|
))?;
|
||||||
|
|
||||||
let uuid = value.get_uuid();
|
let uuid = value.get_uuid();
|
||||||
|
|
||||||
Ok(Group { name, uuid })
|
Ok(Group { spn, uuid })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_proto(&self) -> ProtoGroup {
|
pub fn to_proto(&self) -> ProtoGroup {
|
||||||
ProtoGroup {
|
ProtoGroup {
|
||||||
name: self.name.clone(),
|
spn: self.spn.clone(),
|
||||||
uuid: self.uuid.as_hyphenated().to_string(),
|
uuid: self.uuid.as_hyphenated().to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ pub(crate) mod group;
|
||||||
pub mod oauth2;
|
pub mod oauth2;
|
||||||
pub(crate) mod radius;
|
pub(crate) mod radius;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
|
pub(crate) mod serviceaccount;
|
||||||
pub(crate) mod unix;
|
pub(crate) mod unix;
|
||||||
|
|
||||||
use kanidm_proto::v1::{AuthAllowed, AuthMech};
|
use kanidm_proto::v1::{AuthAllowed, AuthMech};
|
||||||
|
|
|
@ -702,7 +702,7 @@ impl Oauth2ResourceServersReadTransaction {
|
||||||
|
|
||||||
// Validate that the session id matches our uat.
|
// Validate that the session id matches our uat.
|
||||||
if consent_req.session_id != uat.session_id {
|
if consent_req.session_id != uat.session_id {
|
||||||
security_info!("consent request sessien id does not match the session id of our UAT.");
|
security_info!("consent request session id does not match the session id of our UAT.");
|
||||||
return Err(OperationError::InvalidSessionState);
|
return Err(OperationError::InvalidSessionState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -988,7 +988,7 @@ impl Oauth2ResourceServersReadTransaction {
|
||||||
trace!(?oidc);
|
trace!(?oidc);
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
oidc.sign_with_kid(&o2rs.jws_signer, &client_id)
|
oidc.sign(&o2rs.jws_signer)
|
||||||
.map(|jwt_signed| jwt_signed.to_string())
|
.map(|jwt_signed| jwt_signed.to_string())
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!(err = ?e, "Unable to encode uat data");
|
admin_error!(err = ?e, "Unable to encode uat data");
|
||||||
|
@ -1310,7 +1310,7 @@ impl Oauth2ResourceServersReadTransaction {
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
o2rs.jws_signer
|
o2rs.jws_signer
|
||||||
.public_key_as_jwk(Some(&o2rs.name))
|
.public_key_as_jwk()
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!("Unable to retrieve public key for {} - {:?}", o2rs.name, e);
|
admin_error!("Unable to retrieve public key for {} - {:?}", o2rs.name, e);
|
||||||
OperationError::InvalidState
|
OperationError::InvalidState
|
||||||
|
@ -2207,7 +2207,7 @@ mod tests {
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
};
|
};
|
||||||
assert!(use_.unwrap() == JwkUse::Sig);
|
assert!(use_.unwrap() == JwkUse::Sig);
|
||||||
assert!(kid.unwrap() == "test_resource_server")
|
assert!(kid.is_some())
|
||||||
}
|
}
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
};
|
};
|
||||||
|
@ -2486,7 +2486,7 @@ mod tests {
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
};
|
};
|
||||||
assert!(use_.unwrap() == JwkUse::Sig);
|
assert!(use_.unwrap() == JwkUse::Sig);
|
||||||
assert!(kid.unwrap() == "test_resource_server")
|
assert!(kid.is_some());
|
||||||
}
|
}
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
};
|
};
|
||||||
|
@ -2742,7 +2742,6 @@ mod tests {
|
||||||
)))
|
)))
|
||||||
};
|
};
|
||||||
|
|
||||||
trace!("ATTACHHERE");
|
|
||||||
assert!(idms_prox_write.qs_write.delete(&de).is_ok());
|
assert!(idms_prox_write.qs_write.delete(&de).is_ok());
|
||||||
// Assert the consent maps are gone.
|
// Assert the consent maps are gone.
|
||||||
let ident = idms_prox_write
|
let ident = idms_prox_write
|
||||||
|
|
|
@ -8,9 +8,9 @@ use crate::idm::credupdatesession::CredentialUpdateSessionMutex;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use crate::idm::event::PasswordChangeEvent;
|
use crate::idm::event::PasswordChangeEvent;
|
||||||
use crate::idm::event::{
|
use crate::idm::event::{
|
||||||
CredentialStatusEvent, GeneratePasswordEvent, LdapAuthEvent, RadiusAuthTokenEvent,
|
CredentialStatusEvent, GeneratePasswordEvent, LdapAuthEvent, LdapTokenAuthEvent,
|
||||||
RegenerateRadiusSecretEvent, UnixGroupTokenEvent, UnixPasswordChangeEvent, UnixUserAuthEvent,
|
RadiusAuthTokenEvent, RegenerateRadiusSecretEvent, UnixGroupTokenEvent,
|
||||||
UnixUserTokenEvent,
|
UnixPasswordChangeEvent, UnixUserAuthEvent, UnixUserTokenEvent,
|
||||||
};
|
};
|
||||||
use crate::idm::oauth2::{
|
use crate::idm::oauth2::{
|
||||||
AccessTokenIntrospectRequest, AccessTokenIntrospectResponse, AccessTokenRequest,
|
AccessTokenIntrospectRequest, AccessTokenIntrospectResponse, AccessTokenRequest,
|
||||||
|
@ -19,9 +19,10 @@ use crate::idm::oauth2::{
|
||||||
Oauth2ResourceServersWriteTransaction, OidcDiscoveryResponse, OidcToken,
|
Oauth2ResourceServersWriteTransaction, OidcDiscoveryResponse, OidcToken,
|
||||||
};
|
};
|
||||||
use crate::idm::radius::RadiusAccount;
|
use crate::idm::radius::RadiusAccount;
|
||||||
|
use crate::idm::serviceaccount::ServiceAccount;
|
||||||
use crate::idm::unix::{UnixGroup, UnixUserAccount};
|
use crate::idm::unix::{UnixGroup, UnixUserAccount};
|
||||||
use crate::idm::AuthState;
|
use crate::idm::AuthState;
|
||||||
use crate::ldap::LdapBoundToken;
|
use crate::ldap::{LdapBoundToken, LdapSession};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::utils::{password_from_random, readable_password_from_random, uuid_from_duration, Sid};
|
use crate::utils::{password_from_random, readable_password_from_random, uuid_from_duration, Sid};
|
||||||
|
|
||||||
|
@ -33,7 +34,7 @@ use crate::idm::delayed::{
|
||||||
|
|
||||||
use hashbrown::HashSet;
|
use hashbrown::HashSet;
|
||||||
use kanidm_proto::v1::{
|
use kanidm_proto::v1::{
|
||||||
AuthType, BackupCodesView, CredentialStatus, PasswordFeedback, RadiusAuthToken, UnixGroupToken,
|
ApiToken, BackupCodesView, CredentialStatus, PasswordFeedback, RadiusAuthToken, UnixGroupToken,
|
||||||
UnixUserToken, UserAuthToken,
|
UnixUserToken, UserAuthToken,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -400,6 +401,11 @@ impl IdmServerDelayed {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) enum Token {
|
||||||
|
UserAuthToken(UserAuthToken),
|
||||||
|
ApiToken(ApiToken, Arc<EntrySealedCommitted>),
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) trait IdmServerTransaction<'a> {
|
pub(crate) trait IdmServerTransaction<'a> {
|
||||||
type QsTransactionType: QueryServerTransaction<'a>;
|
type QsTransactionType: QueryServerTransaction<'a>;
|
||||||
|
|
||||||
|
@ -407,6 +413,125 @@ pub(crate) trait IdmServerTransaction<'a> {
|
||||||
|
|
||||||
fn get_uat_validator_txn(&self) -> &JwsValidator;
|
fn get_uat_validator_txn(&self) -> &JwsValidator;
|
||||||
|
|
||||||
|
/// This is the preferred method to transform and securely verify a token into
|
||||||
|
/// an identity that can be used for operations and access enforcement. This
|
||||||
|
/// function *is* aware of the various classes of tokens that may exist, and can
|
||||||
|
/// appropriately check them.
|
||||||
|
///
|
||||||
|
/// The primary method of verification selection is the use of the KID parameter
|
||||||
|
/// that we internally sign with. We can use this to select the appropriate token type
|
||||||
|
/// and validation method.
|
||||||
|
fn validate_and_parse_token_to_ident(
|
||||||
|
&self,
|
||||||
|
token: Option<&str>,
|
||||||
|
ct: Duration,
|
||||||
|
) -> Result<Identity, OperationError> {
|
||||||
|
match self.validate_and_parse_token_to_token(token, ct)? {
|
||||||
|
Token::UserAuthToken(uat) => self.process_uat_to_identity(&uat, ct),
|
||||||
|
Token::ApiToken(apit, entry) => self.process_apit_to_identity(&apit, entry, ct),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_and_parse_token_to_token(
|
||||||
|
&self,
|
||||||
|
token: Option<&str>,
|
||||||
|
ct: Duration,
|
||||||
|
) -> Result<Token, OperationError> {
|
||||||
|
let jwsu = token
|
||||||
|
.ok_or_else(|| {
|
||||||
|
security_info!("No token provided");
|
||||||
|
OperationError::NotAuthenticated
|
||||||
|
})
|
||||||
|
.and_then(|s| {
|
||||||
|
JwsUnverified::from_str(s).map_err(|e| {
|
||||||
|
security_info!(?e, "Unable to decode token");
|
||||||
|
OperationError::NotAuthenticated
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Frow the unverified token we can now get the kid, and use that to locate the correct
|
||||||
|
// key to id the token.
|
||||||
|
let jws_validator = self.get_uat_validator_txn();
|
||||||
|
let kid = jwsu.get_jwk_kid().ok_or_else(|| {
|
||||||
|
security_info!("Token does not contain a valid kid");
|
||||||
|
OperationError::NotAuthenticated
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let jwsv_kid = jws_validator.get_jwk_kid().ok_or_else(|| {
|
||||||
|
security_info!("JWS validator does not contain a valid kid");
|
||||||
|
OperationError::NotAuthenticated
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if kid == jwsv_kid {
|
||||||
|
// It's signed by the primary jws, so it's probably a UserAuthToken.
|
||||||
|
let uat = jwsu
|
||||||
|
.validate(jws_validator)
|
||||||
|
.map_err(|e| {
|
||||||
|
security_info!(?e, "Unable to verify token");
|
||||||
|
OperationError::NotAuthenticated
|
||||||
|
})
|
||||||
|
.map(|t: Jws<UserAuthToken>| t.into_inner())?;
|
||||||
|
|
||||||
|
if time::OffsetDateTime::unix_epoch() + ct >= uat.expiry {
|
||||||
|
security_info!("Session expired");
|
||||||
|
Err(OperationError::SessionExpired)
|
||||||
|
} else {
|
||||||
|
Ok(Token::UserAuthToken(uat))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// It's a per-user key, get their validator.
|
||||||
|
let entry = self
|
||||||
|
.get_qs_txn()
|
||||||
|
.internal_search(filter!(f_eq(
|
||||||
|
"jws_es256_private_key",
|
||||||
|
PartialValue::new_iutf8(&kid)
|
||||||
|
)))
|
||||||
|
.and_then(|mut vs| match vs.pop() {
|
||||||
|
Some(entry) if vs.is_empty() => Ok(entry),
|
||||||
|
_ => {
|
||||||
|
admin_error!(
|
||||||
|
?kid,
|
||||||
|
"entries was empty, or matched multiple results for kid"
|
||||||
|
);
|
||||||
|
Err(OperationError::NotAuthenticated)
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let user_signer = entry
|
||||||
|
.get_ava_single_jws_key_es256("jws_es256_private_key")
|
||||||
|
.ok_or_else(|| {
|
||||||
|
admin_error!(
|
||||||
|
?kid,
|
||||||
|
"A kid was present on entry {} but it does not contain a signing key",
|
||||||
|
entry.get_uuid()
|
||||||
|
);
|
||||||
|
OperationError::NotAuthenticated
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let user_validator = user_signer.get_validator().map_err(|e| {
|
||||||
|
security_info!(?e, "Unable to access token verifier");
|
||||||
|
OperationError::NotAuthenticated
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let apit = jwsu
|
||||||
|
.validate(&user_validator)
|
||||||
|
.map_err(|e| {
|
||||||
|
security_info!(?e, "Unable to verify token");
|
||||||
|
OperationError::NotAuthenticated
|
||||||
|
})
|
||||||
|
.map(|t: Jws<ApiToken>| t.into_inner())?;
|
||||||
|
|
||||||
|
if let Some(expiry) = apit.expiry {
|
||||||
|
if time::OffsetDateTime::unix_epoch() + ct >= expiry {
|
||||||
|
security_info!("Session expired");
|
||||||
|
return Err(OperationError::SessionExpired);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Token::ApiToken(apit, entry))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn validate_and_parse_uat(
|
fn validate_and_parse_uat(
|
||||||
&self,
|
&self,
|
||||||
token: Option<&str>,
|
token: Option<&str>,
|
||||||
|
@ -429,7 +554,7 @@ pub(crate) trait IdmServerTransaction<'a> {
|
||||||
security_info!(?e, "Unable to verify token");
|
security_info!(?e, "Unable to verify token");
|
||||||
OperationError::NotAuthenticated
|
OperationError::NotAuthenticated
|
||||||
})
|
})
|
||||||
.map(|t: Jws<UserAuthToken>| t.inner)
|
.map(|t: Jws<UserAuthToken>| t.into_inner())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if time::OffsetDateTime::unix_epoch() + ct >= uat.expiry {
|
if time::OffsetDateTime::unix_epoch() + ct >= uat.expiry {
|
||||||
|
@ -461,6 +586,18 @@ pub(crate) trait IdmServerTransaction<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// For any event/operation to proceed, we need to attach an identity to the
|
||||||
|
/// event for security and access processing. When that event is externally
|
||||||
|
/// triggered via one of our various api layers, we process some type of
|
||||||
|
/// account token into this identity. In the current server this is the
|
||||||
|
/// UserAuthToken. For a UserAuthToken to be provided it MUST have been
|
||||||
|
/// cryptographically verified meaning it is now a *trusted* source of
|
||||||
|
/// data that we previously issued.
|
||||||
|
///
|
||||||
|
/// This is the function that is responsible for converting that UAT into
|
||||||
|
/// something we can pin access controls and other limits and references to.
|
||||||
|
/// This is why it is the location where validity windows are checked and other
|
||||||
|
/// relevant session information is injected.
|
||||||
fn process_uat_to_identity(
|
fn process_uat_to_identity(
|
||||||
&self,
|
&self,
|
||||||
uat: &UserAuthToken,
|
uat: &UserAuthToken,
|
||||||
|
@ -519,12 +656,87 @@ pub(crate) trait IdmServerTransaction<'a> {
|
||||||
|
|
||||||
trace!(claims = ?entry.get_ava_set("claim"), "Applied claims");
|
trace!(claims = ?entry.get_ava_set("claim"), "Applied claims");
|
||||||
|
|
||||||
let limits = Limits::from_uat(uat);
|
let limits = Limits::default();
|
||||||
Ok(Identity {
|
Ok(Identity {
|
||||||
origin: IdentType::User(IdentUser { entry }),
|
origin: IdentType::User(IdentUser { entry }),
|
||||||
limits,
|
limits,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn process_apit_to_identity(
|
||||||
|
&self,
|
||||||
|
apit: &ApiToken,
|
||||||
|
entry: Arc<EntrySealedCommitted>,
|
||||||
|
ct: Duration,
|
||||||
|
) -> Result<Identity, OperationError> {
|
||||||
|
let valid = ServiceAccount::check_api_token_valid(ct, apit, &entry);
|
||||||
|
|
||||||
|
if !valid {
|
||||||
|
// Check_api token logs this.
|
||||||
|
return Err(OperationError::SessionExpired);
|
||||||
|
}
|
||||||
|
|
||||||
|
let limits = Limits::default();
|
||||||
|
Ok(Identity {
|
||||||
|
origin: IdentType::User(IdentUser { entry }),
|
||||||
|
limits,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_ldap_session(
|
||||||
|
&self,
|
||||||
|
session: &LdapSession,
|
||||||
|
ct: Duration,
|
||||||
|
) -> Result<Identity, OperationError> {
|
||||||
|
match session {
|
||||||
|
LdapSession::UnixBind(uuid) => {
|
||||||
|
let anon_entry = self
|
||||||
|
.get_qs_txn()
|
||||||
|
.internal_search_uuid(&UUID_ANONYMOUS)
|
||||||
|
.map_err(|e| {
|
||||||
|
admin_error!("Failed to validate ldap session -> {:?}", e);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let entry = if uuid == &UUID_ANONYMOUS {
|
||||||
|
anon_entry.clone()
|
||||||
|
} else {
|
||||||
|
self.get_qs_txn().internal_search_uuid(&uuid).map_err(|e| {
|
||||||
|
admin_error!("Failed to start auth ldap -> {:?}", e);
|
||||||
|
e
|
||||||
|
})?
|
||||||
|
};
|
||||||
|
|
||||||
|
if Account::check_within_valid_time(
|
||||||
|
ct,
|
||||||
|
entry.get_ava_single_datetime("account_valid_from").as_ref(),
|
||||||
|
entry.get_ava_single_datetime("account_expire").as_ref(),
|
||||||
|
) {
|
||||||
|
// Good to go
|
||||||
|
let limits = Limits::default();
|
||||||
|
Ok(Identity {
|
||||||
|
origin: IdentType::User(IdentUser { entry: anon_entry }),
|
||||||
|
limits,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Nope, expired
|
||||||
|
Err(OperationError::SessionExpired)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LdapSession::UserAuthToken(uat) => self.process_uat_to_identity(&uat, ct),
|
||||||
|
LdapSession::ApiToken(apit) => {
|
||||||
|
let entry = self
|
||||||
|
.get_qs_txn()
|
||||||
|
.internal_search_uuid(&apit.account_id)
|
||||||
|
.map_err(|e| {
|
||||||
|
admin_error!("Failed to validate ldap session -> {:?}", e);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
|
self.process_apit_to_identity(&apit, entry, ct)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> IdmServerTransaction<'a> for IdmServerAuthTransaction<'a> {
|
impl<'a> IdmServerTransaction<'a> for IdmServerAuthTransaction<'a> {
|
||||||
|
@ -921,6 +1133,34 @@ impl<'a> IdmServerAuthTransaction<'a> {
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn token_auth_ldap(
|
||||||
|
&mut self,
|
||||||
|
lae: &LdapTokenAuthEvent,
|
||||||
|
ct: Duration,
|
||||||
|
) -> Result<Option<LdapBoundToken>, OperationError> {
|
||||||
|
match self.validate_and_parse_token_to_token(Some(&lae.token), ct)? {
|
||||||
|
Token::UserAuthToken(uat) => {
|
||||||
|
let spn = uat.spn.clone();
|
||||||
|
Ok(Some(LdapBoundToken {
|
||||||
|
session_id: uat.session_id,
|
||||||
|
spn,
|
||||||
|
effective_session: LdapSession::UserAuthToken(uat),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
Token::ApiToken(apit, entry) => {
|
||||||
|
let spn = entry.get_ava_single_proto_string("spn").ok_or(
|
||||||
|
OperationError::InvalidAccountState("Missing attribute: spn".to_string()),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Some(LdapBoundToken {
|
||||||
|
session_id: apit.token_id,
|
||||||
|
spn,
|
||||||
|
effective_session: LdapSession::ApiToken(apit),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn auth_ldap(
|
pub async fn auth_ldap(
|
||||||
&mut self,
|
&mut self,
|
||||||
lae: &LdapAuthEvent,
|
lae: &LdapAuthEvent,
|
||||||
|
@ -953,15 +1193,9 @@ impl<'a> IdmServerAuthTransaction<'a> {
|
||||||
|
|
||||||
// Account must be anon, so we can gen the uat.
|
// Account must be anon, so we can gen the uat.
|
||||||
Ok(Some(LdapBoundToken {
|
Ok(Some(LdapBoundToken {
|
||||||
uuid: UUID_ANONYMOUS,
|
session_id,
|
||||||
effective_uat: account
|
|
||||||
.to_userauthtoken(session_id, ct, AuthType::Anonymous)
|
|
||||||
.ok_or(OperationError::InvalidState)
|
|
||||||
.map_err(|e| {
|
|
||||||
admin_error!("Unable to generate effective_uat -> {:?}", e);
|
|
||||||
e
|
|
||||||
})?,
|
|
||||||
spn: account.spn,
|
spn: account.spn,
|
||||||
|
effective_session: LdapSession::UnixBind(UUID_ANONYMOUS),
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
let account =
|
let account =
|
||||||
|
@ -1015,20 +1249,6 @@ impl<'a> IdmServerAuthTransaction<'a> {
|
||||||
.verify_unix_credential(lae.cleartext.as_str(), &self.async_tx, ct)?
|
.verify_unix_credential(lae.cleartext.as_str(), &self.async_tx, ct)?
|
||||||
.is_some()
|
.is_some()
|
||||||
{
|
{
|
||||||
// Get the anon uat
|
|
||||||
let anon_entry =
|
|
||||||
self.qs_read
|
|
||||||
.internal_search_uuid(&UUID_ANONYMOUS)
|
|
||||||
.map_err(|e| {
|
|
||||||
admin_error!(
|
|
||||||
"Failed to find effective uat for auth ldap -> {:?}",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
e
|
|
||||||
})?;
|
|
||||||
let anon_account =
|
|
||||||
Account::try_from_entry_ro(anon_entry.as_ref(), &mut self.qs_read)?;
|
|
||||||
|
|
||||||
let session_id = Uuid::new_v4();
|
let session_id = Uuid::new_v4();
|
||||||
security_info!(
|
security_info!(
|
||||||
"Starting session {} for {} {}",
|
"Starting session {} for {} {}",
|
||||||
|
@ -1039,14 +1259,8 @@ impl<'a> IdmServerAuthTransaction<'a> {
|
||||||
|
|
||||||
Ok(Some(LdapBoundToken {
|
Ok(Some(LdapBoundToken {
|
||||||
spn: account.spn,
|
spn: account.spn,
|
||||||
uuid: account.uuid,
|
session_id,
|
||||||
effective_uat: anon_account
|
effective_session: LdapSession::UnixBind(account.uuid),
|
||||||
.to_userauthtoken(session_id, ct, AuthType::UnixPassword)
|
|
||||||
.ok_or(OperationError::InvalidState)
|
|
||||||
.map_err(|e| {
|
|
||||||
admin_error!("Unable to generate effective_uat -> {:?}", e);
|
|
||||||
e
|
|
||||||
})?,
|
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
// PW failure, update softlock.
|
// PW failure, update softlock.
|
||||||
|
@ -1262,7 +1476,7 @@ impl<'a> IdmServerTransaction<'a> for IdmServerProxyWriteTransaction<'a> {
|
||||||
|
|
||||||
impl<'a> IdmServerProxyWriteTransaction<'a> {
|
impl<'a> IdmServerProxyWriteTransaction<'a> {
|
||||||
pub fn get_origin(&self) -> &Url {
|
pub fn get_origin(&self) -> &Url {
|
||||||
self.webauthn.get_origin()
|
self.webauthn.get_allowed_origins().get(0).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_password_quality(
|
fn check_password_quality(
|
||||||
|
@ -1288,9 +1502,8 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
||||||
OperationError::PasswordQuality(vec![PasswordFeedback::TooShort(PW_MIN_LENGTH)])
|
OperationError::PasswordQuality(vec![PasswordFeedback::TooShort(PW_MIN_LENGTH)])
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// check account pwpolicy (for 3 or 4)? Do we need pw strength beyond this
|
// Unix PW's are a single factor, so we enforce good pws
|
||||||
// or should we be enforcing mfa instead
|
if entropy.score() < 4 {
|
||||||
if entropy.score() < 3 {
|
|
||||||
// The password is too week as per:
|
// The password is too week as per:
|
||||||
// https://docs.rs/zxcvbn/2.0.0/zxcvbn/struct.Entropy.html
|
// https://docs.rs/zxcvbn/2.0.0/zxcvbn/struct.Entropy.html
|
||||||
let feedback: zxcvbn::feedback::Feedback = entropy
|
let feedback: zxcvbn::feedback::Feedback = entropy
|
||||||
|
@ -1784,7 +1997,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
||||||
let modlist = ModifyList::new_list(vec![
|
let modlist = ModifyList::new_list(vec![
|
||||||
Modify::Removed(
|
Modify::Removed(
|
||||||
AttrString::from("oauth2_consent_scope_map"),
|
AttrString::from("oauth2_consent_scope_map"),
|
||||||
PartialValue::OauthScopeMap(o2cg.oauth2_rs_uuid),
|
PartialValue::Refer(o2cg.oauth2_rs_uuid),
|
||||||
),
|
),
|
||||||
Modify::Present(
|
Modify::Present(
|
||||||
AttrString::from("oauth2_consent_scope_map"),
|
AttrString::from("oauth2_consent_scope_map"),
|
||||||
|
@ -3248,11 +3461,12 @@ mod tests {
|
||||||
|
|
||||||
// Check it's valid.
|
// Check it's valid.
|
||||||
idms_prox_read
|
idms_prox_read
|
||||||
.validate_and_parse_uat(Some(token.as_str()), ct)
|
.validate_and_parse_token_to_ident(Some(token.as_str()), ct)
|
||||||
.expect("Failed to validate");
|
.expect("Failed to validate");
|
||||||
|
|
||||||
// In X time it should be INVALID
|
// In X time it should be INVALID
|
||||||
match idms_prox_read.validate_and_parse_uat(Some(token.as_str()), expiry) {
|
match idms_prox_read.validate_and_parse_token_to_ident(Some(token.as_str()), expiry)
|
||||||
|
{
|
||||||
Err(OperationError::SessionExpired) => {}
|
Err(OperationError::SessionExpired) => {}
|
||||||
_ => assert!(false),
|
_ => assert!(false),
|
||||||
}
|
}
|
||||||
|
@ -3376,7 +3590,7 @@ mod tests {
|
||||||
|
|
||||||
// Check it's valid.
|
// Check it's valid.
|
||||||
idms_prox_read
|
idms_prox_read
|
||||||
.validate_and_parse_uat(Some(token.as_str()), ct)
|
.validate_and_parse_token_to_ident(Some(token.as_str()), ct)
|
||||||
.expect("Failed to validate");
|
.expect("Failed to validate");
|
||||||
|
|
||||||
drop(idms_prox_read);
|
drop(idms_prox_read);
|
||||||
|
@ -3404,11 +3618,11 @@ mod tests {
|
||||||
|
|
||||||
let idms_prox_read = idms.proxy_read();
|
let idms_prox_read = idms.proxy_read();
|
||||||
assert!(idms_prox_read
|
assert!(idms_prox_read
|
||||||
.validate_and_parse_uat(Some(token.as_str()), ct)
|
.validate_and_parse_token_to_ident(Some(token.as_str()), ct)
|
||||||
.is_err());
|
.is_err());
|
||||||
// A new token will work due to the matching key.
|
// A new token will work due to the matching key.
|
||||||
idms_prox_read
|
idms_prox_read
|
||||||
.validate_and_parse_uat(Some(new_token.as_str()), ct)
|
.validate_and_parse_token_to_ident(Some(new_token.as_str()), ct)
|
||||||
.expect("Failed to validate");
|
.expect("Failed to validate");
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
455
kanidmd/idm/src/idm/serviceaccount.rs
Normal file
455
kanidmd/idm/src/idm/serviceaccount.rs
Normal file
|
@ -0,0 +1,455 @@
|
||||||
|
use crate::event::SearchEvent;
|
||||||
|
use crate::idm::account::Account;
|
||||||
|
use crate::idm::server::{IdmServerProxyReadTransaction, IdmServerProxyWriteTransaction};
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::value::Session;
|
||||||
|
|
||||||
|
use compact_jwt::{Jws, JwsSigner};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::time::Duration;
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
|
use kanidm_proto::v1::ApiToken;
|
||||||
|
|
||||||
|
// Need to add KID to es256 der for lookups ✅
|
||||||
|
|
||||||
|
// Need to generate the es256 on the account on modifies ✅
|
||||||
|
|
||||||
|
// Add migration to generate the es256 on startup at least once. ✅
|
||||||
|
|
||||||
|
// Create new valueset type to store sessions w_ labels ✅
|
||||||
|
|
||||||
|
// Able to lookup from KID to get service account
|
||||||
|
|
||||||
|
// Able to take token -> ident
|
||||||
|
// -- check still valid
|
||||||
|
|
||||||
|
// revoke
|
||||||
|
|
||||||
|
const GRACE_WINDOW: Duration = Duration::from_secs(600);
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref PVCLASS_SERVICE_ACCOUNT: PartialValue = PartialValue::new_class("service_account");
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! try_from_entry {
|
||||||
|
($value:expr) => {{
|
||||||
|
// Check the classes
|
||||||
|
if !$value.attribute_equality("class", &PVCLASS_SERVICE_ACCOUNT) {
|
||||||
|
return Err(OperationError::InvalidAccountState(
|
||||||
|
"Missing class: service account".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let spn = $value.get_ava_single_proto_string("spn").ok_or(
|
||||||
|
OperationError::InvalidAccountState("Missing attribute: spn".to_string()),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let jws_key = $value
|
||||||
|
.get_ava_single_jws_key_es256("jws_es256_private_key")
|
||||||
|
.cloned()
|
||||||
|
.ok_or(OperationError::InvalidAccountState(
|
||||||
|
"Missing attribute: jws_es256_private_key".to_string(),
|
||||||
|
))?;
|
||||||
|
|
||||||
|
let api_tokens = $value
|
||||||
|
.get_ava_as_session_map("api_token_session")
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let valid_from = $value.get_ava_single_datetime("account_valid_from");
|
||||||
|
|
||||||
|
let expire = $value.get_ava_single_datetime("account_expire");
|
||||||
|
|
||||||
|
let uuid = $value.get_uuid().clone();
|
||||||
|
|
||||||
|
Ok(ServiceAccount {
|
||||||
|
spn,
|
||||||
|
uuid,
|
||||||
|
valid_from,
|
||||||
|
expire,
|
||||||
|
api_tokens,
|
||||||
|
jws_key,
|
||||||
|
})
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ServiceAccount {
|
||||||
|
pub spn: String,
|
||||||
|
pub uuid: Uuid,
|
||||||
|
|
||||||
|
pub valid_from: Option<OffsetDateTime>,
|
||||||
|
pub expire: Option<OffsetDateTime>,
|
||||||
|
|
||||||
|
pub api_tokens: BTreeMap<Uuid, Session>,
|
||||||
|
|
||||||
|
pub jws_key: JwsSigner,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServiceAccount {
|
||||||
|
pub(crate) fn try_from_entry_rw(
|
||||||
|
value: &Entry<EntrySealed, EntryCommitted>,
|
||||||
|
// qs: &mut QueryServerWriteTransaction,
|
||||||
|
) -> Result<Self, OperationError> {
|
||||||
|
spanned!("idm::serviceaccount::try_from_entry_rw", {
|
||||||
|
// let groups = Group::try_from_account_entry_rw(value, qs)?;
|
||||||
|
try_from_entry!(value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn check_api_token_valid(
|
||||||
|
ct: Duration,
|
||||||
|
apit: &ApiToken,
|
||||||
|
entry: &Entry<EntrySealed, EntryCommitted>,
|
||||||
|
) -> bool {
|
||||||
|
let within_valid_window = Account::check_within_valid_time(
|
||||||
|
ct,
|
||||||
|
entry.get_ava_single_datetime("account_valid_from").as_ref(),
|
||||||
|
entry.get_ava_single_datetime("account_expire").as_ref(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if !within_valid_window {
|
||||||
|
security_info!("Account has expired or is not yet valid, not allowing to proceed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the sessions.
|
||||||
|
let session_present = entry
|
||||||
|
.get_ava_as_session_map("api_token_session")
|
||||||
|
.map(|session_map| session_map.get(&apit.token_id).is_some())
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
if session_present {
|
||||||
|
security_info!("A valid session value exists for this token");
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
let grace = apit.issued_at + GRACE_WINDOW;
|
||||||
|
let current = time::OffsetDateTime::unix_epoch() + ct;
|
||||||
|
trace!(%grace, %current);
|
||||||
|
if current >= grace {
|
||||||
|
security_info!(
|
||||||
|
"The token grace window has passed, and no session exists. Assuming invalid."
|
||||||
|
);
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
security_info!("The token grace window is in effect. Assuming valid.");
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ListApiTokenEvent {
|
||||||
|
// Who initiated this?
|
||||||
|
pub ident: Identity,
|
||||||
|
// Who is it targetting?
|
||||||
|
pub target: Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GenerateApiTokenEvent {
|
||||||
|
// Who initiated this?
|
||||||
|
pub ident: Identity,
|
||||||
|
// Who is it targetting?
|
||||||
|
pub target: Uuid,
|
||||||
|
// The label
|
||||||
|
pub label: String,
|
||||||
|
// When should it expire?
|
||||||
|
pub expiry: Option<time::OffsetDateTime>,
|
||||||
|
// Limits?
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GenerateApiTokenEvent {
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn new_internal(target: Uuid, label: &str, expiry: Option<Duration>) -> Self {
|
||||||
|
GenerateApiTokenEvent {
|
||||||
|
ident: Identity::from_internal(),
|
||||||
|
target,
|
||||||
|
label: label.to_string(),
|
||||||
|
expiry: expiry.map(|ct| time::OffsetDateTime::unix_epoch() + ct),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DestroyApiTokenEvent {
|
||||||
|
// Who initiated this?
|
||||||
|
pub ident: Identity,
|
||||||
|
// Who is it targetting?
|
||||||
|
pub target: Uuid,
|
||||||
|
// Which token id.
|
||||||
|
pub token_id: Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DestroyApiTokenEvent {
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn new_internal(target: Uuid, token_id: Uuid) -> Self {
|
||||||
|
DestroyApiTokenEvent {
|
||||||
|
ident: Identity::from_internal(),
|
||||||
|
target,
|
||||||
|
token_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IdmServerProxyWriteTransaction<'a> {
|
||||||
|
pub fn service_account_generate_api_token(
|
||||||
|
&self,
|
||||||
|
gte: &GenerateApiTokenEvent,
|
||||||
|
ct: Duration,
|
||||||
|
) -> Result<String, OperationError> {
|
||||||
|
let service_account = self
|
||||||
|
.qs_write
|
||||||
|
.internal_search_uuid(>e.target)
|
||||||
|
.and_then(|account_entry| ServiceAccount::try_from_entry_rw(&account_entry))
|
||||||
|
.map_err(|e| {
|
||||||
|
admin_error!(?e, "Failed to search service account");
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let session_id = Uuid::new_v4();
|
||||||
|
let issued_at = time::OffsetDateTime::unix_epoch() + ct;
|
||||||
|
|
||||||
|
// Normalise to UTC incase it was provided as something else.
|
||||||
|
let expiry = gte
|
||||||
|
.expiry
|
||||||
|
.clone()
|
||||||
|
.map(|odt| odt.to_offset(time::UtcOffset::UTC));
|
||||||
|
|
||||||
|
// create a new session
|
||||||
|
let session = Value::Session(
|
||||||
|
session_id,
|
||||||
|
Session {
|
||||||
|
label: gte.label.clone(),
|
||||||
|
expiry,
|
||||||
|
// Need the other inner bits?
|
||||||
|
// for the gracewindow.
|
||||||
|
issued_at,
|
||||||
|
// Who actually created this?
|
||||||
|
issued_by: gte.ident.get_event_origin_id(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// create the session token (not yet signed)
|
||||||
|
let token = Jws::new(ApiToken {
|
||||||
|
account_id: service_account.uuid,
|
||||||
|
token_id: session_id,
|
||||||
|
label: gte.label.clone(),
|
||||||
|
expiry: gte.expiry.clone(),
|
||||||
|
issued_at,
|
||||||
|
});
|
||||||
|
|
||||||
|
// modify the account to put the session onto it.
|
||||||
|
let modlist = ModifyList::new_list(vec![Modify::Present(
|
||||||
|
AttrString::from("api_token_session"),
|
||||||
|
session,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
self.qs_write
|
||||||
|
.impersonate_modify(
|
||||||
|
// Filter as executed
|
||||||
|
&filter!(f_eq("uuid", PartialValue::new_uuid(gte.target))),
|
||||||
|
// Filter as intended (acp)
|
||||||
|
&filter_all!(f_eq("uuid", PartialValue::new_uuid(gte.target))),
|
||||||
|
&modlist,
|
||||||
|
// Provide the event to impersonate
|
||||||
|
>e.ident,
|
||||||
|
)
|
||||||
|
.and_then(|_| {
|
||||||
|
// The modify succeeded and was allowed, now sign the token for return.
|
||||||
|
token
|
||||||
|
.sign_embed_public_jwk(&service_account.jws_key)
|
||||||
|
.map(|jws_signed| jws_signed.to_string())
|
||||||
|
.map_err(|e| {
|
||||||
|
admin_error!(err = ?e, "Unable to sign api token");
|
||||||
|
OperationError::CryptographyError
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.map_err(|e| {
|
||||||
|
admin_error!("Failed to generate api token {:?}", e);
|
||||||
|
e
|
||||||
|
})
|
||||||
|
// Done!
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn service_account_destroy_api_token(
|
||||||
|
&self,
|
||||||
|
dte: &DestroyApiTokenEvent,
|
||||||
|
) -> Result<(), OperationError> {
|
||||||
|
// Delete the attribute with uuid.
|
||||||
|
let modlist = ModifyList::new_list(vec![Modify::Removed(
|
||||||
|
AttrString::from("api_token_session"),
|
||||||
|
PartialValue::Refer(dte.token_id),
|
||||||
|
)]);
|
||||||
|
|
||||||
|
self.qs_write
|
||||||
|
.impersonate_modify(
|
||||||
|
// Filter as executed
|
||||||
|
&filter!(f_and!([
|
||||||
|
f_eq("uuid", PartialValue::Uuid(dte.target)),
|
||||||
|
f_eq("api_token_session", PartialValue::Refer(dte.token_id))
|
||||||
|
])),
|
||||||
|
// Filter as intended (acp)
|
||||||
|
&filter_all!(f_and!([
|
||||||
|
f_eq("uuid", PartialValue::Uuid(dte.target)),
|
||||||
|
f_eq("api_token_session", PartialValue::Refer(dte.token_id))
|
||||||
|
])),
|
||||||
|
&modlist,
|
||||||
|
// Provide the event to impersonate
|
||||||
|
&dte.ident,
|
||||||
|
)
|
||||||
|
.map_err(|e| {
|
||||||
|
admin_error!("Failed to destroy api token {:?}", e);
|
||||||
|
e
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IdmServerProxyReadTransaction<'a> {
|
||||||
|
pub fn service_account_list_api_token(
|
||||||
|
&self,
|
||||||
|
lte: &ListApiTokenEvent,
|
||||||
|
) -> Result<Vec<ApiToken>, OperationError> {
|
||||||
|
// Make an event from the request
|
||||||
|
let srch = match SearchEvent::from_target_uuid_request(
|
||||||
|
lte.ident.clone(),
|
||||||
|
lte.target,
|
||||||
|
&self.qs_read,
|
||||||
|
) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => {
|
||||||
|
admin_error!("Failed to begin ssh key read: {:?}", e);
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match self.qs_read.search_ext(&srch) {
|
||||||
|
Ok(mut entries) => {
|
||||||
|
let r = entries
|
||||||
|
.pop()
|
||||||
|
// get the first entry
|
||||||
|
.and_then(|e| {
|
||||||
|
let account_id = e.get_uuid();
|
||||||
|
// From the entry, turn it into the value
|
||||||
|
e.get_ava_as_session_map("api_token_session").map(|smap| {
|
||||||
|
smap.iter()
|
||||||
|
.map(|(u, s)| ApiToken {
|
||||||
|
account_id,
|
||||||
|
token_id: *u,
|
||||||
|
label: s.label.clone(),
|
||||||
|
expiry: s.expiry.clone(),
|
||||||
|
issued_at: s.issued_at.clone(),
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
// No matching entry? Return none.
|
||||||
|
Vec::new()
|
||||||
|
});
|
||||||
|
Ok(r)
|
||||||
|
}
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{DestroyApiTokenEvent, GenerateApiTokenEvent, GRACE_WINDOW};
|
||||||
|
use crate::idm::server::IdmServerTransaction;
|
||||||
|
// use crate::prelude::*;
|
||||||
|
|
||||||
|
use crate::event::CreateEvent;
|
||||||
|
use compact_jwt::{Jws, JwsUnverified};
|
||||||
|
use kanidm_proto::v1::ApiToken;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
const TEST_CURRENT_TIME: u64 = 6000;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_idm_service_account_api_token() {
|
||||||
|
run_idm_test!(|_qs: &QueryServer,
|
||||||
|
idms: &IdmServer,
|
||||||
|
_idms_delayed: &mut IdmServerDelayed| {
|
||||||
|
let ct = Duration::from_secs(TEST_CURRENT_TIME);
|
||||||
|
let past_grc = Duration::from_secs(TEST_CURRENT_TIME + 1) + GRACE_WINDOW;
|
||||||
|
let exp = Duration::from_secs(TEST_CURRENT_TIME + 6000);
|
||||||
|
let post_exp = Duration::from_secs(TEST_CURRENT_TIME + 6010);
|
||||||
|
let idms_prox_write = idms.proxy_write(ct);
|
||||||
|
|
||||||
|
let testaccount_uuid = Uuid::new_v4();
|
||||||
|
|
||||||
|
let e1 = entry_init!(
|
||||||
|
("class", Value::new_class("object")),
|
||||||
|
("class", Value::new_class("account")),
|
||||||
|
("class", Value::new_class("service_account")),
|
||||||
|
("name", Value::new_iname("test_account_only")),
|
||||||
|
("uuid", Value::new_uuid(testaccount_uuid)),
|
||||||
|
("description", Value::new_utf8s("testaccount")),
|
||||||
|
("displayname", Value::new_utf8s("testaccount"))
|
||||||
|
);
|
||||||
|
|
||||||
|
let ce = CreateEvent::new_internal(vec![e1]);
|
||||||
|
let cr = idms_prox_write.qs_write.create(&ce);
|
||||||
|
assert!(cr.is_ok());
|
||||||
|
|
||||||
|
let gte = GenerateApiTokenEvent::new_internal(testaccount_uuid, "TestToken", Some(exp));
|
||||||
|
|
||||||
|
let api_token = idms_prox_write
|
||||||
|
.service_account_generate_api_token(>e, ct)
|
||||||
|
.expect("failed to generate new api token");
|
||||||
|
|
||||||
|
trace!(?api_token);
|
||||||
|
|
||||||
|
// Deserialise it.
|
||||||
|
let apitoken_unverified =
|
||||||
|
JwsUnverified::from_str(&api_token).expect("Failed to parse apitoken");
|
||||||
|
let apitoken_inner: Jws<ApiToken> = apitoken_unverified
|
||||||
|
.validate_embeded()
|
||||||
|
.expect("Embedded jwk not found");
|
||||||
|
let apitoken_inner = apitoken_inner.into_inner();
|
||||||
|
|
||||||
|
let ident = idms_prox_write
|
||||||
|
.validate_and_parse_token_to_ident(Some(&api_token), ct)
|
||||||
|
.expect("Unable to verify api token.");
|
||||||
|
|
||||||
|
assert!(ident.get_uuid() == Some(testaccount_uuid));
|
||||||
|
|
||||||
|
// Woohoo! Okay lets test the other edge cases.
|
||||||
|
|
||||||
|
// Check the expiry
|
||||||
|
assert!(
|
||||||
|
idms_prox_write
|
||||||
|
.validate_and_parse_token_to_ident(Some(&api_token), post_exp)
|
||||||
|
.expect_err("Should not succeed")
|
||||||
|
== OperationError::SessionExpired
|
||||||
|
);
|
||||||
|
|
||||||
|
// Delete session
|
||||||
|
let dte = DestroyApiTokenEvent::new_internal(
|
||||||
|
apitoken_inner.account_id,
|
||||||
|
apitoken_inner.token_id,
|
||||||
|
);
|
||||||
|
assert!(idms_prox_write
|
||||||
|
.service_account_destroy_api_token(&dte)
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
|
// Within gracewindow?
|
||||||
|
// This is okay, because we are within the gracewindow.
|
||||||
|
let ident = idms_prox_write
|
||||||
|
.validate_and_parse_token_to_ident(Some(&api_token), ct)
|
||||||
|
.expect("Unable to verify api token.");
|
||||||
|
assert!(ident.get_uuid() == Some(testaccount_uuid));
|
||||||
|
|
||||||
|
// Past gracewindow?
|
||||||
|
assert!(
|
||||||
|
idms_prox_write
|
||||||
|
.validate_and_parse_token_to_ident(Some(&api_token), past_grc)
|
||||||
|
.expect_err("Should not succeed")
|
||||||
|
== OperationError::SessionExpired
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(idms_prox_write.commit().is_ok());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,11 +2,11 @@
|
||||||
//! are sent to for processing.
|
//! are sent to for processing.
|
||||||
|
|
||||||
use crate::event::SearchEvent;
|
use crate::event::SearchEvent;
|
||||||
use crate::idm::event::LdapAuthEvent;
|
use crate::idm::event::{LdapAuthEvent, LdapTokenAuthEvent};
|
||||||
use crate::idm::server::{IdmServer, IdmServerTransaction};
|
use crate::idm::server::{IdmServer, IdmServerTransaction};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use async_std::task;
|
use async_std::task;
|
||||||
use kanidm_proto::v1::{OperationError, UserAuthToken};
|
use kanidm_proto::v1::{ApiToken, OperationError, UserAuthToken};
|
||||||
use ldap3_proto::simple::*;
|
use ldap3_proto::simple::*;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
@ -26,12 +26,27 @@ pub enum LdapResponseState {
|
||||||
BindMultiPartResponse(LdapBoundToken, Vec<LdapMsg>),
|
BindMultiPartResponse(LdapBoundToken, Vec<LdapMsg>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum LdapSession {
|
||||||
|
// Maps through and provides anon read, but allows us to check the validity
|
||||||
|
// of the account still.
|
||||||
|
UnixBind(Uuid),
|
||||||
|
UserAuthToken(UserAuthToken),
|
||||||
|
ApiToken(ApiToken),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct LdapBoundToken {
|
pub struct LdapBoundToken {
|
||||||
|
// Used to help ID the user doing the action, makes logging nicer.
|
||||||
pub spn: String,
|
pub spn: String,
|
||||||
pub uuid: Uuid,
|
pub session_id: Uuid,
|
||||||
// For now, always anonymous
|
// This is the effective session permission. This is generated from either:
|
||||||
pub effective_uat: UserAuthToken,
|
// * A valid anonymous bind
|
||||||
|
// * A valid unix pw bind
|
||||||
|
// * A valid ApiToken
|
||||||
|
// In a way, this is a stepping stone to an "ident" but allows us to check
|
||||||
|
// the session is still "valid" depending on it's origin.
|
||||||
|
pub effective_session: LdapSession,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LdapServer {
|
pub struct LdapServer {
|
||||||
|
@ -269,12 +284,12 @@ impl LdapServer {
|
||||||
|
|
||||||
admin_info!(filter = ?lfilter, "LDAP Search Filter");
|
admin_info!(filter = ?lfilter, "LDAP Search Filter");
|
||||||
|
|
||||||
// Build the event, with the permissions from effective_uuid
|
// Build the event, with the permissions from effective_session
|
||||||
// (should always be anonymous at the moment)
|
//
|
||||||
// ! Remember, searchEvent wraps to ignore hidden for us.
|
// ! Remember, searchEvent wraps to ignore hidden for us.
|
||||||
let se = spanned!("ldap::do_search<core><prepare_se>", {
|
let se = spanned!("ldap::do_search<core><prepare_se>", {
|
||||||
let ident = idm_read
|
let ident = idm_read
|
||||||
.process_uat_to_identity(&uat.effective_uat, ct)
|
.validate_ldap_session(&uat.effective_session, ct)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!("Invalid identity: {:?}", e);
|
admin_error!("Invalid identity: {:?}", e);
|
||||||
e
|
e
|
||||||
|
@ -346,9 +361,18 @@ impl LdapServer {
|
||||||
security_info!("✅ LDAP Bind success anonymous");
|
security_info!("✅ LDAP Bind success anonymous");
|
||||||
UUID_ANONYMOUS
|
UUID_ANONYMOUS
|
||||||
} else {
|
} else {
|
||||||
security_info!("❌ LDAP Bind failure anonymous");
|
// This is the path to access api-token logins.
|
||||||
// Yeah-nahhhhh
|
let lae = LdapTokenAuthEvent::from_parts(pw.to_string())?;
|
||||||
return Ok(None);
|
return idm_auth.token_auth_ldap(&lae, ct).await.and_then(|r| {
|
||||||
|
idm_auth.commit().map(|_| {
|
||||||
|
if r.is_some() {
|
||||||
|
security_info!(%dn, "✅ LDAP Bind success");
|
||||||
|
} else {
|
||||||
|
security_info!(%dn, "❌ LDAP Bind failure");
|
||||||
|
};
|
||||||
|
r
|
||||||
|
})
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let rdn = match self
|
let rdn = match self
|
||||||
|
@ -528,11 +552,16 @@ mod tests {
|
||||||
// use crate::prelude::*;
|
// use crate::prelude::*;
|
||||||
use crate::event::{CreateEvent, ModifyEvent};
|
use crate::event::{CreateEvent, ModifyEvent};
|
||||||
use crate::idm::event::UnixPasswordChangeEvent;
|
use crate::idm::event::UnixPasswordChangeEvent;
|
||||||
use crate::ldap::LdapServer;
|
use crate::idm::serviceaccount::GenerateApiTokenEvent;
|
||||||
|
use crate::ldap::{LdapServer, LdapSession};
|
||||||
use async_std::task;
|
use async_std::task;
|
||||||
use hashbrown::HashSet;
|
use hashbrown::HashSet;
|
||||||
|
use kanidm_proto::v1::ApiToken;
|
||||||
use ldap3_proto::proto::{LdapFilter, LdapOp, LdapSearchScope};
|
use ldap3_proto::proto::{LdapFilter, LdapOp, LdapSearchScope};
|
||||||
use ldap3_proto::simple::*;
|
use ldap3_proto::simple::*;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use compact_jwt::{Jws, JwsUnverified};
|
||||||
|
|
||||||
const TEST_PASSWORD: &'static str = "ntaoeuntnaoeuhraohuercahu😍";
|
const TEST_PASSWORD: &'static str = "ntaoeuntnaoeuhraohuercahu😍";
|
||||||
|
|
||||||
|
@ -566,25 +595,26 @@ mod tests {
|
||||||
let anon_t = task::block_on(ldaps.do_bind(idms, "", ""))
|
let anon_t = task::block_on(ldaps.do_bind(idms, "", ""))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(anon_t.uuid == UUID_ANONYMOUS);
|
assert!(anon_t.effective_session == LdapSession::UnixBind(UUID_ANONYMOUS));
|
||||||
assert!(task::block_on(ldaps.do_bind(idms, "", "test"))
|
assert!(
|
||||||
.unwrap()
|
task::block_on(ldaps.do_bind(idms, "", "test")).unwrap_err()
|
||||||
.is_none());
|
== OperationError::NotAuthenticated
|
||||||
|
);
|
||||||
|
|
||||||
// Now test the admin and various DN's
|
// Now test the admin and various DN's
|
||||||
let admin_t = task::block_on(ldaps.do_bind(idms, "admin", TEST_PASSWORD))
|
let admin_t = task::block_on(ldaps.do_bind(idms, "admin", TEST_PASSWORD))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(admin_t.uuid == *UUID_ADMIN);
|
assert!(admin_t.effective_session == LdapSession::UnixBind(*UUID_ADMIN));
|
||||||
let admin_t =
|
let admin_t =
|
||||||
task::block_on(ldaps.do_bind(idms, "admin@example.com", TEST_PASSWORD))
|
task::block_on(ldaps.do_bind(idms, "admin@example.com", TEST_PASSWORD))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(admin_t.uuid == *UUID_ADMIN);
|
assert!(admin_t.effective_session == LdapSession::UnixBind(*UUID_ADMIN));
|
||||||
let admin_t = task::block_on(ldaps.do_bind(idms, STR_UUID_ADMIN, TEST_PASSWORD))
|
let admin_t = task::block_on(ldaps.do_bind(idms, STR_UUID_ADMIN, TEST_PASSWORD))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(admin_t.uuid == *UUID_ADMIN);
|
assert!(admin_t.effective_session == LdapSession::UnixBind(*UUID_ADMIN));
|
||||||
let admin_t = task::block_on(ldaps.do_bind(
|
let admin_t = task::block_on(ldaps.do_bind(
|
||||||
idms,
|
idms,
|
||||||
"name=admin,dc=example,dc=com",
|
"name=admin,dc=example,dc=com",
|
||||||
|
@ -592,7 +622,7 @@ mod tests {
|
||||||
))
|
))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(admin_t.uuid == *UUID_ADMIN);
|
assert!(admin_t.effective_session == LdapSession::UnixBind(*UUID_ADMIN));
|
||||||
let admin_t = task::block_on(ldaps.do_bind(
|
let admin_t = task::block_on(ldaps.do_bind(
|
||||||
idms,
|
idms,
|
||||||
"spn=admin@example.com,dc=example,dc=com",
|
"spn=admin@example.com,dc=example,dc=com",
|
||||||
|
@ -600,7 +630,7 @@ mod tests {
|
||||||
))
|
))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(admin_t.uuid == *UUID_ADMIN);
|
assert!(admin_t.effective_session == LdapSession::UnixBind(*UUID_ADMIN));
|
||||||
let admin_t = task::block_on(ldaps.do_bind(
|
let admin_t = task::block_on(ldaps.do_bind(
|
||||||
idms,
|
idms,
|
||||||
format!("uuid={},dc=example,dc=com", STR_UUID_ADMIN).as_str(),
|
format!("uuid={},dc=example,dc=com", STR_UUID_ADMIN).as_str(),
|
||||||
|
@ -608,17 +638,17 @@ mod tests {
|
||||||
))
|
))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(admin_t.uuid == *UUID_ADMIN);
|
assert!(admin_t.effective_session == LdapSession::UnixBind(*UUID_ADMIN));
|
||||||
|
|
||||||
let admin_t = task::block_on(ldaps.do_bind(idms, "name=admin", TEST_PASSWORD))
|
let admin_t = task::block_on(ldaps.do_bind(idms, "name=admin", TEST_PASSWORD))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(admin_t.uuid == *UUID_ADMIN);
|
assert!(admin_t.effective_session == LdapSession::UnixBind(*UUID_ADMIN));
|
||||||
let admin_t =
|
let admin_t =
|
||||||
task::block_on(ldaps.do_bind(idms, "spn=admin@example.com", TEST_PASSWORD))
|
task::block_on(ldaps.do_bind(idms, "spn=admin@example.com", TEST_PASSWORD))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(admin_t.uuid == *UUID_ADMIN);
|
assert!(admin_t.effective_session == LdapSession::UnixBind(*UUID_ADMIN));
|
||||||
let admin_t = task::block_on(ldaps.do_bind(
|
let admin_t = task::block_on(ldaps.do_bind(
|
||||||
idms,
|
idms,
|
||||||
format!("uuid={}", STR_UUID_ADMIN).as_str(),
|
format!("uuid={}", STR_UUID_ADMIN).as_str(),
|
||||||
|
@ -626,13 +656,13 @@ mod tests {
|
||||||
))
|
))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(admin_t.uuid == *UUID_ADMIN);
|
assert!(admin_t.effective_session == LdapSession::UnixBind(*UUID_ADMIN));
|
||||||
|
|
||||||
let admin_t =
|
let admin_t =
|
||||||
task::block_on(ldaps.do_bind(idms, "admin,dc=example,dc=com", TEST_PASSWORD))
|
task::block_on(ldaps.do_bind(idms, "admin,dc=example,dc=com", TEST_PASSWORD))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(admin_t.uuid == *UUID_ADMIN);
|
assert!(admin_t.effective_session == LdapSession::UnixBind(*UUID_ADMIN));
|
||||||
let admin_t = task::block_on(ldaps.do_bind(
|
let admin_t = task::block_on(ldaps.do_bind(
|
||||||
idms,
|
idms,
|
||||||
"admin@example.com,dc=example,dc=com",
|
"admin@example.com,dc=example,dc=com",
|
||||||
|
@ -640,7 +670,7 @@ mod tests {
|
||||||
))
|
))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(admin_t.uuid == *UUID_ADMIN);
|
assert!(admin_t.effective_session == LdapSession::UnixBind(*UUID_ADMIN));
|
||||||
let admin_t = task::block_on(ldaps.do_bind(
|
let admin_t = task::block_on(ldaps.do_bind(
|
||||||
idms,
|
idms,
|
||||||
format!("{},dc=example,dc=com", STR_UUID_ADMIN).as_str(),
|
format!("{},dc=example,dc=com", STR_UUID_ADMIN).as_str(),
|
||||||
|
@ -648,7 +678,7 @@ mod tests {
|
||||||
))
|
))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(admin_t.uuid == *UUID_ADMIN);
|
assert!(admin_t.effective_session == LdapSession::UnixBind(*UUID_ADMIN));
|
||||||
|
|
||||||
// Bad password, check last to prevent softlocking of the admin account.
|
// Bad password, check last to prevent softlocking of the admin account.
|
||||||
assert!(task::block_on(ldaps.do_bind(idms, "admin", "test"))
|
assert!(task::block_on(ldaps.do_bind(idms, "admin", "test"))
|
||||||
|
@ -745,7 +775,7 @@ mod tests {
|
||||||
let anon_t = task::block_on(ldaps.do_bind(idms, "", ""))
|
let anon_t = task::block_on(ldaps.do_bind(idms, "", ""))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(anon_t.uuid == UUID_ANONYMOUS);
|
assert!(anon_t.effective_session == LdapSession::UnixBind(UUID_ANONYMOUS));
|
||||||
|
|
||||||
// Check that when we request *, we get default list.
|
// Check that when we request *, we get default list.
|
||||||
let sr = SearchRequest {
|
let sr = SearchRequest {
|
||||||
|
@ -853,28 +883,140 @@ mod tests {
|
||||||
fn test_ldap_token_privilege_granting() {
|
fn test_ldap_token_privilege_granting() {
|
||||||
run_idm_test!(
|
run_idm_test!(
|
||||||
|_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed| {
|
|_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed| {
|
||||||
|
// Setup the ldap server
|
||||||
|
let ldaps = LdapServer::new(idms).expect("failed to start ldap");
|
||||||
|
|
||||||
|
// Prebuild the search req we'll be using this test.
|
||||||
|
let sr = SearchRequest {
|
||||||
|
msgid: 1,
|
||||||
|
base: "dc=example,dc=com".to_string(),
|
||||||
|
scope: LdapSearchScope::Subtree,
|
||||||
|
filter: LdapFilter::Equality("name".to_string(), "testperson1".to_string()),
|
||||||
|
attrs: vec!["name".to_string(), "mail".to_string()],
|
||||||
|
};
|
||||||
|
|
||||||
|
let sa_uuid = uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
|
||||||
|
|
||||||
// Configure the user account that will have the tokens issued.
|
// Configure the user account that will have the tokens issued.
|
||||||
// Should be a SERVICE account.
|
// Should be a SERVICE account.
|
||||||
|
let apitoken = {
|
||||||
|
// Create a service account,
|
||||||
|
|
||||||
// Should I finally do the class rules shit?
|
let e1 = entry_init!(
|
||||||
|
("class", Value::new_class("object")),
|
||||||
|
("class", Value::new_class("service_account")),
|
||||||
|
("class", Value::new_class("account")),
|
||||||
|
("uuid", Value::new_uuid(sa_uuid)),
|
||||||
|
("name", Value::new_iname("service_permission_test")),
|
||||||
|
("displayname", Value::new_utf8s("service_permission_test"))
|
||||||
|
);
|
||||||
|
|
||||||
// Issue a token, make it purpose = ldap.
|
// Setup a person with an email
|
||||||
|
let e2 = entry_init!(
|
||||||
|
("class", Value::new_class("object")),
|
||||||
|
("class", Value::new_class("person")),
|
||||||
|
("class", Value::new_class("account")),
|
||||||
|
("class", Value::new_class("posixaccount")),
|
||||||
|
("name", Value::new_iname("testperson1")),
|
||||||
|
(
|
||||||
|
"mail",
|
||||||
|
Value::EmailAddress("testperson1@example.com".to_string(), true)
|
||||||
|
),
|
||||||
|
("description", Value::new_utf8s("testperson1")),
|
||||||
|
("displayname", Value::new_utf8s("testperson1")),
|
||||||
|
("gidnumber", Value::new_uint32(12345678)),
|
||||||
|
("loginshell", Value::new_iutf8("/bin/zsh"))
|
||||||
|
);
|
||||||
|
|
||||||
// assert the uat fails on non-ldap events.
|
// Setup an access control for the service account to view mail attrs.
|
||||||
|
|
||||||
// Setup a person with
|
let ct = duration_from_epoch_now();
|
||||||
// Setup an access control for the service account to view mail attrs.
|
|
||||||
|
|
||||||
// Setup the ldap server
|
let server_txn = idms.proxy_write(ct);
|
||||||
let _ldaps = LdapServer::new(idms).expect("failed to start ldap");
|
let ce = CreateEvent::new_internal(vec![e1, e2]);
|
||||||
|
assert!(server_txn.qs_write.create(&ce).is_ok());
|
||||||
|
|
||||||
// Bind with account pw, search and show attr isn't accessible.
|
// idm_people_read_priv
|
||||||
|
let me = unsafe {
|
||||||
|
ModifyEvent::new_internal_invalid(
|
||||||
|
filter!(f_eq(
|
||||||
|
"name",
|
||||||
|
PartialValue::new_iname("idm_people_read_priv")
|
||||||
|
)),
|
||||||
|
ModifyList::new_list(vec![Modify::Present(
|
||||||
|
AttrString::from("member"),
|
||||||
|
Value::new_refer(sa_uuid),
|
||||||
|
)]),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
assert!(server_txn.qs_write.modify(&me).is_ok());
|
||||||
|
|
||||||
// Bind using the token instead of the PW.
|
// Issue a token
|
||||||
|
// make it purpose = ldap <- currently purpose isn't supported,
|
||||||
|
// it's an idea for future.
|
||||||
|
let gte = GenerateApiTokenEvent::new_internal(sa_uuid, "TestToken", None);
|
||||||
|
|
||||||
// Search and retrieve an attribute that's now accessible.
|
let apitoken = server_txn
|
||||||
|
.service_account_generate_api_token(>e, ct)
|
||||||
|
.expect("Failed to create new apitoken");
|
||||||
|
|
||||||
assert!(true);
|
assert!(server_txn.commit().is_ok());
|
||||||
|
|
||||||
|
apitoken
|
||||||
|
};
|
||||||
|
|
||||||
|
// assert the token fails on non-ldap events token-xchg <- currently
|
||||||
|
// we don't have purpose so this isn't tested.
|
||||||
|
|
||||||
|
// Bind with anonymous, search and show mail attr isn't accessible.
|
||||||
|
let anon_lbt = task::block_on(ldaps.do_bind(idms, "", ""))
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
assert!(anon_lbt.effective_session == LdapSession::UnixBind(UUID_ANONYMOUS));
|
||||||
|
|
||||||
|
let r1 = task::block_on(ldaps.do_search(idms, &sr, &anon_lbt)).unwrap();
|
||||||
|
assert!(r1.len() == 2);
|
||||||
|
match &r1[0].op {
|
||||||
|
LdapOp::SearchResultEntry(lsre) => {
|
||||||
|
assert_entry_contains!(
|
||||||
|
lsre,
|
||||||
|
"spn=testperson1@example.com,dc=example,dc=com",
|
||||||
|
("name", "testperson1")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => assert!(false),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Inspect the token to get its uuid out.
|
||||||
|
let apitoken_unverified =
|
||||||
|
JwsUnverified::from_str(&apitoken).expect("Failed to parse apitoken");
|
||||||
|
|
||||||
|
let apitoken_inner: Jws<ApiToken> = apitoken_unverified
|
||||||
|
.validate_embeded()
|
||||||
|
.expect("Embedded jwk not found");
|
||||||
|
|
||||||
|
let apitoken_inner = apitoken_inner.into_inner();
|
||||||
|
|
||||||
|
// Bind using the token
|
||||||
|
let sa_lbt = task::block_on(ldaps.do_bind(idms, "", &apitoken))
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
assert!(sa_lbt.effective_session == LdapSession::ApiToken(apitoken_inner.clone()));
|
||||||
|
|
||||||
|
// Search and retrieve mail that's now accessible.
|
||||||
|
let r1 = task::block_on(ldaps.do_search(idms, &sr, &sa_lbt)).unwrap();
|
||||||
|
assert!(r1.len() == 2);
|
||||||
|
match &r1[0].op {
|
||||||
|
LdapOp::SearchResultEntry(lsre) => {
|
||||||
|
assert_entry_contains!(
|
||||||
|
lsre,
|
||||||
|
"spn=testperson1@example.com,dc=example,dc=com",
|
||||||
|
("name", "testperson1"),
|
||||||
|
("mail", "testperson1@example.com")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => assert!(false),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
//! The Kanidmd server library. This implements all of the internal components of the server
|
//! The Kanidmd server library. This implements all of the internal components of the server
|
||||||
//! which is used to process authentication, store identities and enforce access controls.
|
//! which is used to process authentication, store identities and enforce access controls.
|
||||||
|
|
||||||
//#![deny(warnings)]
|
#![deny(warnings)]
|
||||||
|
|
||||||
#![recursion_limit = "512"]
|
#![recursion_limit = "512"]
|
||||||
#![warn(unused_extern_crates)]
|
#![warn(unused_extern_crates)]
|
||||||
#![deny(clippy::todo)]
|
#![deny(clippy::todo)]
|
||||||
|
|
|
@ -68,6 +68,11 @@ impl Plugin for GidNumber {
|
||||||
"plugin_gidnumber"
|
"plugin_gidnumber"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(
|
||||||
|
level = "debug",
|
||||||
|
name = "gidnumber_pre_create_transform",
|
||||||
|
skip(_qs, cand, _ce)
|
||||||
|
)]
|
||||||
fn pre_create_transform(
|
fn pre_create_transform(
|
||||||
_qs: &QueryServerWriteTransaction,
|
_qs: &QueryServerWriteTransaction,
|
||||||
cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
|
cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
|
||||||
|
@ -80,6 +85,7 @@ impl Plugin for GidNumber {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "debug", name = "gidnumber_pre_modify", skip(_qs, cand, _me))]
|
||||||
fn pre_modify(
|
fn pre_modify(
|
||||||
_qs: &QueryServerWriteTransaction,
|
_qs: &QueryServerWriteTransaction,
|
||||||
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
|
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
|
||||||
|
|
|
@ -7,11 +7,12 @@ use compact_jwt::JwsSigner;
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref CLASS_OAUTH2_BASIC: PartialValue =
|
static ref CLASS_OAUTH2_BASIC: PartialValue =
|
||||||
PartialValue::new_class("oauth2_resource_server_basic");
|
PartialValue::new_class("oauth2_resource_server_basic");
|
||||||
|
static ref CLASS_SERVICE_ACCOUNT: PartialValue = PartialValue::new_class("service_account");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Oauth2Secrets {}
|
pub struct JwsKeygen {}
|
||||||
|
|
||||||
macro_rules! oauth2_transform {
|
macro_rules! keygen_transform {
|
||||||
(
|
(
|
||||||
$e:expr
|
$e:expr
|
||||||
) => {{
|
) => {{
|
||||||
|
@ -52,18 +53,32 @@ macro_rules! oauth2_transform {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if $e.attribute_equality("class", &CLASS_SERVICE_ACCOUNT) {
|
||||||
|
if !$e.attribute_pres("jws_es256_private_key") {
|
||||||
|
security_info!("regenerating jws es256 private key");
|
||||||
|
let jwssigner = JwsSigner::generate_es256()
|
||||||
|
.map_err(|e| {
|
||||||
|
admin_error!(err = ?e, "Unable to generate ES256 JwsSigner private key");
|
||||||
|
OperationError::CryptographyError
|
||||||
|
})?;
|
||||||
|
let v = Value::JwsKeyEs256(jwssigner);
|
||||||
|
$e.add_ava("jws_es256_private_key", v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Plugin for Oauth2Secrets {
|
impl Plugin for JwsKeygen {
|
||||||
fn id() -> &'static str {
|
fn id() -> &'static str {
|
||||||
"plugin_oauth2_secrets"
|
"plugin_jws_keygen"
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(
|
#[instrument(
|
||||||
level = "debug",
|
level = "debug",
|
||||||
name = "oauth2_pre_create_transform",
|
name = "jwskeygen_pre_create_transform",
|
||||||
skip(_qs, cand, _ce)
|
skip(_qs, cand, _ce)
|
||||||
)]
|
)]
|
||||||
fn pre_create_transform(
|
fn pre_create_transform(
|
||||||
|
@ -71,16 +86,16 @@ impl Plugin for Oauth2Secrets {
|
||||||
cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
|
cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
|
||||||
_ce: &CreateEvent,
|
_ce: &CreateEvent,
|
||||||
) -> Result<(), OperationError> {
|
) -> Result<(), OperationError> {
|
||||||
cand.iter_mut().try_for_each(|e| oauth2_transform!(e))
|
cand.iter_mut().try_for_each(|e| keygen_transform!(e))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "debug", name = "oauth2_pre_modify", skip(_qs, cand, _me))]
|
#[instrument(level = "debug", name = "jwskeygen_pre_modify", skip(_qs, cand, _me))]
|
||||||
fn pre_modify(
|
fn pre_modify(
|
||||||
_qs: &QueryServerWriteTransaction,
|
_qs: &QueryServerWriteTransaction,
|
||||||
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
|
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
|
||||||
_me: &ModifyEvent,
|
_me: &ModifyEvent,
|
||||||
) -> Result<(), OperationError> {
|
) -> Result<(), OperationError> {
|
||||||
cand.iter_mut().try_for_each(|e| oauth2_transform!(e))
|
cand.iter_mut().try_for_each(|e| keygen_transform!(e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,8 @@ mod domain;
|
||||||
pub(crate) mod dyngroup;
|
pub(crate) mod dyngroup;
|
||||||
mod failure;
|
mod failure;
|
||||||
mod gidnumber;
|
mod gidnumber;
|
||||||
|
mod jwskeygen;
|
||||||
mod memberof;
|
mod memberof;
|
||||||
mod oauth2;
|
|
||||||
mod password_import;
|
mod password_import;
|
||||||
mod protected;
|
mod protected;
|
||||||
mod recycle;
|
mod recycle;
|
||||||
|
@ -126,7 +126,7 @@ impl Plugins {
|
||||||
spanned!("plugins::run_pre_create_transform", {
|
spanned!("plugins::run_pre_create_transform", {
|
||||||
base::Base::pre_create_transform(qs, cand, ce)
|
base::Base::pre_create_transform(qs, cand, ce)
|
||||||
.and_then(|_| password_import::PasswordImport::pre_create_transform(qs, cand, ce))
|
.and_then(|_| password_import::PasswordImport::pre_create_transform(qs, cand, ce))
|
||||||
.and_then(|_| oauth2::Oauth2Secrets::pre_create_transform(qs, cand, ce))
|
.and_then(|_| jwskeygen::JwsKeygen::pre_create_transform(qs, cand, ce))
|
||||||
.and_then(|_| gidnumber::GidNumber::pre_create_transform(qs, cand, ce))
|
.and_then(|_| gidnumber::GidNumber::pre_create_transform(qs, cand, ce))
|
||||||
.and_then(|_| domain::Domain::pre_create_transform(qs, cand, ce))
|
.and_then(|_| domain::Domain::pre_create_transform(qs, cand, ce))
|
||||||
.and_then(|_| spn::Spn::pre_create_transform(qs, cand, ce))
|
.and_then(|_| spn::Spn::pre_create_transform(qs, cand, ce))
|
||||||
|
@ -165,7 +165,7 @@ impl Plugins {
|
||||||
protected::Protected::pre_modify(qs, cand, me)
|
protected::Protected::pre_modify(qs, cand, me)
|
||||||
.and_then(|_| base::Base::pre_modify(qs, cand, me))
|
.and_then(|_| base::Base::pre_modify(qs, cand, me))
|
||||||
.and_then(|_| password_import::PasswordImport::pre_modify(qs, cand, me))
|
.and_then(|_| password_import::PasswordImport::pre_modify(qs, cand, me))
|
||||||
.and_then(|_| oauth2::Oauth2Secrets::pre_modify(qs, cand, me))
|
.and_then(|_| jwskeygen::JwsKeygen::pre_modify(qs, cand, me))
|
||||||
.and_then(|_| gidnumber::GidNumber::pre_modify(qs, cand, me))
|
.and_then(|_| gidnumber::GidNumber::pre_modify(qs, cand, me))
|
||||||
.and_then(|_| domain::Domain::pre_modify(qs, cand, me))
|
.and_then(|_| domain::Domain::pre_modify(qs, cand, me))
|
||||||
.and_then(|_| spn::Spn::pre_modify(qs, cand, me))
|
.and_then(|_| spn::Spn::pre_modify(qs, cand, me))
|
||||||
|
|
|
@ -172,31 +172,36 @@ impl SchemaAttribute {
|
||||||
// on that may only use a single tagged attribute for example.
|
// on that may only use a single tagged attribute for example.
|
||||||
pub fn validate_partialvalue(&self, a: &str, v: &PartialValue) -> Result<(), SchemaError> {
|
pub fn validate_partialvalue(&self, a: &str, v: &PartialValue) -> Result<(), SchemaError> {
|
||||||
let r = match self.syntax {
|
let r = match self.syntax {
|
||||||
SyntaxType::Boolean => v.is_bool(),
|
SyntaxType::Boolean => matches!(v, PartialValue::Bool(_)),
|
||||||
SyntaxType::SYNTAX_ID => v.is_syntax(),
|
SyntaxType::SyntaxId => matches!(v, PartialValue::Syntax(_)),
|
||||||
SyntaxType::INDEX_ID => v.is_index(),
|
SyntaxType::IndexId => matches!(v, PartialValue::Index(_)),
|
||||||
SyntaxType::Uuid => v.is_uuid(),
|
SyntaxType::Uuid => matches!(v, PartialValue::Uuid(_)),
|
||||||
SyntaxType::REFERENCE_UUID => v.is_refer(),
|
SyntaxType::ReferenceUuid => matches!(v, PartialValue::Refer(_)),
|
||||||
SyntaxType::Utf8StringInsensitive => v.is_iutf8(),
|
SyntaxType::Utf8StringInsensitive => matches!(v, PartialValue::Iutf8(_)),
|
||||||
SyntaxType::Utf8StringIname => v.is_iname(),
|
SyntaxType::Utf8StringIname => matches!(v, PartialValue::Iname(_)),
|
||||||
SyntaxType::UTF8STRING => v.is_utf8(),
|
SyntaxType::Utf8String => matches!(v, PartialValue::Utf8(_)),
|
||||||
SyntaxType::JSON_FILTER => v.is_json_filter(),
|
SyntaxType::JsonFilter => matches!(v, PartialValue::JsonFilt(_)),
|
||||||
SyntaxType::Credential => v.is_credential(),
|
SyntaxType::Credential => matches!(v, PartialValue::Cred(_)),
|
||||||
SyntaxType::SecretUtf8String => v.is_secret_string(),
|
SyntaxType::SecretUtf8String => matches!(v, PartialValue::SecretValue),
|
||||||
SyntaxType::SshKey => v.is_sshkey(),
|
SyntaxType::SshKey => matches!(v, PartialValue::SshKey(_)),
|
||||||
SyntaxType::SecurityPrincipalName => v.is_spn(),
|
SyntaxType::SecurityPrincipalName => matches!(v, PartialValue::Spn(_, _)),
|
||||||
SyntaxType::UINT32 => v.is_uint32(),
|
SyntaxType::Uint32 => matches!(v, PartialValue::Uint32(_)),
|
||||||
SyntaxType::Cid => v.is_cid(),
|
SyntaxType::Cid => matches!(v, PartialValue::Cid(_)),
|
||||||
SyntaxType::NsUniqueId => v.is_nsuniqueid(),
|
SyntaxType::NsUniqueId => matches!(v, PartialValue::Nsuniqueid(_)),
|
||||||
SyntaxType::DateTime => v.is_datetime(),
|
SyntaxType::DateTime => matches!(v, PartialValue::DateTime(_)),
|
||||||
SyntaxType::EmailAddress => v.is_email_address(),
|
SyntaxType::EmailAddress => matches!(v, PartialValue::EmailAddress(_)),
|
||||||
SyntaxType::Url => v.is_url(),
|
SyntaxType::Url => matches!(v, PartialValue::Url(_)),
|
||||||
SyntaxType::OauthScope => v.is_oauthscope(),
|
SyntaxType::OauthScope => matches!(v, PartialValue::OauthScope(_)),
|
||||||
SyntaxType::OauthScopeMap => v.is_oauthscopemap() || v.is_refer(),
|
SyntaxType::OauthScopeMap => matches!(v, PartialValue::Refer(_)),
|
||||||
SyntaxType::PrivateBinary => v.is_privatebinary(),
|
SyntaxType::PrivateBinary => matches!(v, PartialValue::PrivateBinary),
|
||||||
SyntaxType::IntentToken => matches!(v, PartialValue::IntentToken(_)),
|
SyntaxType::IntentToken => matches!(v, PartialValue::IntentToken(_)),
|
||||||
SyntaxType::Passkey => matches!(v, PartialValue::Passkey(_)),
|
SyntaxType::Passkey => matches!(v, PartialValue::Passkey(_)),
|
||||||
SyntaxType::DeviceKey => matches!(v, PartialValue::DeviceKey(_)),
|
SyntaxType::DeviceKey => matches!(v, PartialValue::DeviceKey(_)),
|
||||||
|
// Allow refer types.
|
||||||
|
SyntaxType::Session => matches!(v, PartialValue::Refer(_)),
|
||||||
|
// These are just insensitive string lookups on the hex-ified kid.
|
||||||
|
SyntaxType::JwsKeyEs256 => matches!(v, PartialValue::Iutf8(_)),
|
||||||
|
SyntaxType::JwsKeyRs256 => matches!(v, PartialValue::Iutf8(_)),
|
||||||
};
|
};
|
||||||
if r {
|
if r {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -214,31 +219,34 @@ impl SchemaAttribute {
|
||||||
pub fn validate_value(&self, a: &str, v: &Value) -> Result<(), SchemaError> {
|
pub fn validate_value(&self, a: &str, v: &Value) -> Result<(), SchemaError> {
|
||||||
let r = v.validate()
|
let r = v.validate()
|
||||||
&& match self.syntax {
|
&& match self.syntax {
|
||||||
SyntaxType::Boolean => v.is_bool(),
|
SyntaxType::Boolean => matches!(v, Value::Bool(_)),
|
||||||
SyntaxType::SYNTAX_ID => v.is_syntax(),
|
SyntaxType::SyntaxId => matches!(v, Value::Syntax(_)),
|
||||||
SyntaxType::INDEX_ID => v.is_index(),
|
SyntaxType::IndexId => matches!(v, Value::Index(_)),
|
||||||
SyntaxType::Uuid => v.is_uuid(),
|
SyntaxType::Uuid => matches!(v, Value::Uuid(_)),
|
||||||
SyntaxType::REFERENCE_UUID => v.is_refer(),
|
SyntaxType::ReferenceUuid => matches!(v, Value::Refer(_)),
|
||||||
SyntaxType::Utf8StringInsensitive => v.is_iutf8(),
|
SyntaxType::Utf8StringInsensitive => matches!(v, Value::Iutf8(_)),
|
||||||
SyntaxType::Utf8StringIname => v.is_iname(),
|
SyntaxType::Utf8StringIname => matches!(v, Value::Iname(_)),
|
||||||
SyntaxType::UTF8STRING => v.is_utf8(),
|
SyntaxType::Utf8String => matches!(v, Value::Utf8(_)),
|
||||||
SyntaxType::JSON_FILTER => v.is_json_filter(),
|
SyntaxType::JsonFilter => matches!(v, Value::JsonFilt(_)),
|
||||||
SyntaxType::Credential => v.is_credential(),
|
SyntaxType::Credential => matches!(v, Value::Cred(_, _)),
|
||||||
SyntaxType::SecretUtf8String => v.is_secret_string(),
|
SyntaxType::SecretUtf8String => matches!(v, Value::SecretValue(_)),
|
||||||
SyntaxType::SshKey => v.is_sshkey(),
|
SyntaxType::SshKey => matches!(v, Value::SshKey(_, _)),
|
||||||
SyntaxType::SecurityPrincipalName => v.is_spn(),
|
SyntaxType::SecurityPrincipalName => matches!(v, Value::Spn(_, _)),
|
||||||
SyntaxType::UINT32 => v.is_uint32(),
|
SyntaxType::Uint32 => matches!(v, Value::Uint32(_)),
|
||||||
SyntaxType::Cid => v.is_cid(),
|
SyntaxType::Cid => matches!(v, Value::Cid(_)),
|
||||||
SyntaxType::NsUniqueId => v.is_nsuniqueid(),
|
SyntaxType::NsUniqueId => matches!(v, Value::Nsuniqueid(_)),
|
||||||
SyntaxType::DateTime => v.is_datetime(),
|
SyntaxType::DateTime => matches!(v, Value::DateTime(_)),
|
||||||
SyntaxType::EmailAddress => v.is_email_address(),
|
SyntaxType::EmailAddress => matches!(v, Value::EmailAddress(_, _)),
|
||||||
SyntaxType::Url => v.is_url(),
|
SyntaxType::Url => matches!(v, Value::Url(_)),
|
||||||
SyntaxType::OauthScope => v.is_oauthscope(),
|
SyntaxType::OauthScope => matches!(v, Value::OauthScope(_)),
|
||||||
SyntaxType::OauthScopeMap => v.is_oauthscopemap() || v.is_refer(),
|
SyntaxType::OauthScopeMap => matches!(v, Value::OauthScopeMap(_, _)),
|
||||||
SyntaxType::PrivateBinary => v.is_privatebinary(),
|
SyntaxType::PrivateBinary => matches!(v, Value::PrivateBinary(_)),
|
||||||
SyntaxType::IntentToken => matches!(v, Value::IntentToken(_, _)),
|
SyntaxType::IntentToken => matches!(v, Value::IntentToken(_, _)),
|
||||||
SyntaxType::Passkey => matches!(v, Value::Passkey(_, _, _)),
|
SyntaxType::Passkey => matches!(v, Value::Passkey(_, _, _)),
|
||||||
SyntaxType::DeviceKey => matches!(v, Value::DeviceKey(_, _, _)),
|
SyntaxType::DeviceKey => matches!(v, Value::DeviceKey(_, _, _)),
|
||||||
|
SyntaxType::Session => matches!(v, Value::Session(_, _)),
|
||||||
|
SyntaxType::JwsKeyEs256 => matches!(v, Value::JwsKeyEs256(_)),
|
||||||
|
SyntaxType::JwsKeyRs256 => matches!(v, Value::JwsKeyRs256(_)),
|
||||||
};
|
};
|
||||||
if r {
|
if r {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -580,7 +588,10 @@ impl<'a> SchemaWriteTransaction<'a> {
|
||||||
// No, they'll over-write each other ... but we do need name uniqueness.
|
// No, they'll over-write each other ... but we do need name uniqueness.
|
||||||
attributetypes.into_iter().for_each(|a| {
|
attributetypes.into_iter().for_each(|a| {
|
||||||
// Update the unique and ref caches.
|
// Update the unique and ref caches.
|
||||||
if a.syntax == SyntaxType::REFERENCE_UUID || a.syntax == SyntaxType::OauthScopeMap {
|
if a.syntax == SyntaxType::ReferenceUuid || a.syntax == SyntaxType::OauthScopeMap
|
||||||
|
// May not need to be a ref type since it doesn't have external links/impact?
|
||||||
|
// || a.syntax == SyntaxType::Session
|
||||||
|
{
|
||||||
self.ref_cache.insert(a.name.clone(), a.clone());
|
self.ref_cache.insert(a.name.clone(), a.clone());
|
||||||
}
|
}
|
||||||
if a.unique {
|
if a.unique {
|
||||||
|
@ -745,7 +756,7 @@ impl<'a> SchemaWriteTransaction<'a> {
|
||||||
unique: false,
|
unique: false,
|
||||||
phantom: false,
|
phantom: false,
|
||||||
index: vec![],
|
index: vec![],
|
||||||
syntax: SyntaxType::UTF8STRING,
|
syntax: SyntaxType::Utf8String,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
self.attributes.insert(AttrString::from("multivalue"), SchemaAttribute {
|
self.attributes.insert(AttrString::from("multivalue"), SchemaAttribute {
|
||||||
|
@ -790,7 +801,7 @@ impl<'a> SchemaWriteTransaction<'a> {
|
||||||
unique: false,
|
unique: false,
|
||||||
phantom: false,
|
phantom: false,
|
||||||
index: vec![],
|
index: vec![],
|
||||||
syntax: SyntaxType::INDEX_ID,
|
syntax: SyntaxType::IndexId,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
self.attributes.insert(
|
self.attributes.insert(
|
||||||
|
@ -805,7 +816,7 @@ impl<'a> SchemaWriteTransaction<'a> {
|
||||||
unique: false,
|
unique: false,
|
||||||
phantom: false,
|
phantom: false,
|
||||||
index: vec![IndexType::Equality],
|
index: vec![IndexType::Equality],
|
||||||
syntax: SyntaxType::SYNTAX_ID,
|
syntax: SyntaxType::SyntaxId,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
self.attributes.insert(
|
self.attributes.insert(
|
||||||
|
@ -957,7 +968,7 @@ impl<'a> SchemaWriteTransaction<'a> {
|
||||||
unique: false,
|
unique: false,
|
||||||
phantom: false,
|
phantom: false,
|
||||||
index: vec![IndexType::Equality, IndexType::SubString],
|
index: vec![IndexType::Equality, IndexType::SubString],
|
||||||
syntax: SyntaxType::JSON_FILTER,
|
syntax: SyntaxType::JsonFilter,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
self.attributes.insert(
|
self.attributes.insert(
|
||||||
|
@ -972,7 +983,7 @@ impl<'a> SchemaWriteTransaction<'a> {
|
||||||
unique: false,
|
unique: false,
|
||||||
phantom: false,
|
phantom: false,
|
||||||
index: vec![IndexType::Equality, IndexType::SubString],
|
index: vec![IndexType::Equality, IndexType::SubString],
|
||||||
syntax: SyntaxType::JSON_FILTER,
|
syntax: SyntaxType::JsonFilter,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
self.attributes.insert(
|
self.attributes.insert(
|
||||||
|
@ -1069,7 +1080,7 @@ impl<'a> SchemaWriteTransaction<'a> {
|
||||||
unique: false,
|
unique: false,
|
||||||
phantom: false,
|
phantom: false,
|
||||||
index: vec![IndexType::Equality],
|
index: vec![IndexType::Equality],
|
||||||
syntax: SyntaxType::REFERENCE_UUID,
|
syntax: SyntaxType::ReferenceUuid,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
self.attributes.insert(
|
self.attributes.insert(
|
||||||
|
@ -1082,7 +1093,7 @@ impl<'a> SchemaWriteTransaction<'a> {
|
||||||
unique: false,
|
unique: false,
|
||||||
phantom: false,
|
phantom: false,
|
||||||
index: vec![IndexType::Equality],
|
index: vec![IndexType::Equality],
|
||||||
syntax: SyntaxType::REFERENCE_UUID,
|
syntax: SyntaxType::ReferenceUuid,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
self.attributes.insert(
|
self.attributes.insert(
|
||||||
|
@ -1095,7 +1106,7 @@ impl<'a> SchemaWriteTransaction<'a> {
|
||||||
unique: false,
|
unique: false,
|
||||||
phantom: false,
|
phantom: false,
|
||||||
index: vec![IndexType::Equality],
|
index: vec![IndexType::Equality],
|
||||||
syntax: SyntaxType::REFERENCE_UUID,
|
syntax: SyntaxType::ReferenceUuid,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
// Migration related
|
// Migration related
|
||||||
|
@ -1111,7 +1122,7 @@ impl<'a> SchemaWriteTransaction<'a> {
|
||||||
unique: false,
|
unique: false,
|
||||||
phantom: false,
|
phantom: false,
|
||||||
index: vec![],
|
index: vec![],
|
||||||
syntax: SyntaxType::UINT32,
|
syntax: SyntaxType::Uint32,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
// Domain for sysinfo
|
// Domain for sysinfo
|
||||||
|
@ -1169,7 +1180,7 @@ impl<'a> SchemaWriteTransaction<'a> {
|
||||||
unique: false,
|
unique: false,
|
||||||
phantom: true,
|
phantom: true,
|
||||||
index: vec![],
|
index: vec![],
|
||||||
syntax: SyntaxType::UTF8STRING,
|
syntax: SyntaxType::Utf8String,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1301,7 +1312,7 @@ impl<'a> SchemaWriteTransaction<'a> {
|
||||||
unique: false,
|
unique: false,
|
||||||
phantom: true,
|
phantom: true,
|
||||||
index: vec![],
|
index: vec![],
|
||||||
syntax: SyntaxType::UINT32,
|
syntax: SyntaxType::Uint32,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
// end LDAP masking phantoms
|
// end LDAP masking phantoms
|
||||||
|
@ -1906,7 +1917,7 @@ mod tests {
|
||||||
unique: false,
|
unique: false,
|
||||||
phantom: false,
|
phantom: false,
|
||||||
index: vec![IndexType::Equality],
|
index: vec![IndexType::Equality],
|
||||||
syntax: SyntaxType::UTF8STRING,
|
syntax: SyntaxType::Utf8String,
|
||||||
};
|
};
|
||||||
|
|
||||||
let rvs = vs_utf8!["test1".to_string(), "test2".to_string()] as _;
|
let rvs = vs_utf8!["test1".to_string(), "test2".to_string()] as _;
|
||||||
|
@ -1955,7 +1966,7 @@ mod tests {
|
||||||
unique: false,
|
unique: false,
|
||||||
phantom: false,
|
phantom: false,
|
||||||
index: vec![IndexType::Equality],
|
index: vec![IndexType::Equality],
|
||||||
syntax: SyntaxType::SYNTAX_ID,
|
syntax: SyntaxType::SyntaxId,
|
||||||
};
|
};
|
||||||
|
|
||||||
let rvs = vs_syntax![SyntaxType::try_from("UTF8STRING").unwrap()] as _;
|
let rvs = vs_syntax![SyntaxType::try_from("UTF8STRING").unwrap()] as _;
|
||||||
|
@ -1978,7 +1989,7 @@ mod tests {
|
||||||
unique: false,
|
unique: false,
|
||||||
phantom: false,
|
phantom: false,
|
||||||
index: vec![IndexType::Equality],
|
index: vec![IndexType::Equality],
|
||||||
syntax: SyntaxType::INDEX_ID,
|
syntax: SyntaxType::IndexId,
|
||||||
};
|
};
|
||||||
//
|
//
|
||||||
let rvs = vs_index![IndexType::try_from("EQUALITY").unwrap()] as _;
|
let rvs = vs_index![IndexType::try_from("EQUALITY").unwrap()] as _;
|
||||||
|
|
|
@ -53,6 +53,7 @@ lazy_static! {
|
||||||
static ref PVCLASS_OAUTH2_RS: PartialValue = PartialValue::new_class("oauth2_resource_server");
|
static ref PVCLASS_OAUTH2_RS: PartialValue = PartialValue::new_class("oauth2_resource_server");
|
||||||
static ref PVCLASS_ACCOUNT: PartialValue = PartialValue::new_class("account");
|
static ref PVCLASS_ACCOUNT: PartialValue = PartialValue::new_class("account");
|
||||||
static ref PVCLASS_PERSON: PartialValue = PartialValue::new_class("person");
|
static ref PVCLASS_PERSON: PartialValue = PartialValue::new_class("person");
|
||||||
|
static ref PVCLASS_SERVICE_ACCOUNT: PartialValue = PartialValue::new_class("service_account");
|
||||||
static ref PVUUID_DOMAIN_INFO: PartialValue = PartialValue::new_uuid(*UUID_DOMAIN_INFO);
|
static ref PVUUID_DOMAIN_INFO: PartialValue = PartialValue::new_uuid(*UUID_DOMAIN_INFO);
|
||||||
static ref PVACP_ENABLE_FALSE: PartialValue = PartialValue::new_bool(false);
|
static ref PVACP_ENABLE_FALSE: PartialValue = PartialValue::new_bool(false);
|
||||||
}
|
}
|
||||||
|
@ -94,8 +95,9 @@ pub struct QueryServerReadTransaction<'a> {
|
||||||
schema: SchemaReadTransaction,
|
schema: SchemaReadTransaction,
|
||||||
accesscontrols: AccessControlsReadTransaction<'a>,
|
accesscontrols: AccessControlsReadTransaction<'a>,
|
||||||
_db_ticket: SemaphorePermit<'a>,
|
_db_ticket: SemaphorePermit<'a>,
|
||||||
resolve_filter_cache:
|
resolve_filter_cache: Cell<
|
||||||
Cell<ARCacheReadTxn<'a, (IdentityId, Filter<FilterValid>), Filter<FilterValidResolved>>>,
|
ARCacheReadTxn<'a, (IdentityId, Filter<FilterValid>), Filter<FilterValidResolved>, ()>,
|
||||||
|
>,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl<'a> Sync for QueryServerReadTransaction<'a> {}
|
unsafe impl<'a> Sync for QueryServerReadTransaction<'a> {}
|
||||||
|
@ -121,8 +123,9 @@ pub struct QueryServerWriteTransaction<'a> {
|
||||||
changed_uuid: Cell<HashSet<Uuid>>,
|
changed_uuid: Cell<HashSet<Uuid>>,
|
||||||
_db_ticket: SemaphorePermit<'a>,
|
_db_ticket: SemaphorePermit<'a>,
|
||||||
_write_ticket: SemaphorePermit<'a>,
|
_write_ticket: SemaphorePermit<'a>,
|
||||||
resolve_filter_cache:
|
resolve_filter_cache: Cell<
|
||||||
Cell<ARCacheReadTxn<'a, (IdentityId, Filter<FilterValid>), Filter<FilterValidResolved>>>,
|
ARCacheReadTxn<'a, (IdentityId, Filter<FilterValid>), Filter<FilterValidResolved>, ()>,
|
||||||
|
>,
|
||||||
dyngroup_cache: Cell<CowCellWriteTxn<'a, DynGroupCache>>,
|
dyngroup_cache: Cell<CowCellWriteTxn<'a, DynGroupCache>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,7 +166,7 @@ pub trait QueryServerTransaction<'a> {
|
||||||
#[allow(clippy::mut_from_ref)]
|
#[allow(clippy::mut_from_ref)]
|
||||||
fn get_resolve_filter_cache(
|
fn get_resolve_filter_cache(
|
||||||
&self,
|
&self,
|
||||||
) -> &mut ARCacheReadTxn<'a, (IdentityId, Filter<FilterValid>), Filter<FilterValidResolved>>;
|
) -> &mut ARCacheReadTxn<'a, (IdentityId, Filter<FilterValid>), Filter<FilterValidResolved>, ()>;
|
||||||
|
|
||||||
/// Conduct a search and apply access controls to yield a set of entries that
|
/// Conduct a search and apply access controls to yield a set of entries that
|
||||||
/// have been reduced to the set of user visible avas. Note that if you provide
|
/// have been reduced to the set of user visible avas. Note that if you provide
|
||||||
|
@ -475,14 +478,14 @@ pub trait QueryServerTransaction<'a> {
|
||||||
match schema.get_attributes().get(attr) {
|
match schema.get_attributes().get(attr) {
|
||||||
Some(schema_a) => {
|
Some(schema_a) => {
|
||||||
match schema_a.syntax {
|
match schema_a.syntax {
|
||||||
SyntaxType::UTF8STRING => Ok(Value::new_utf8(value.to_string())),
|
SyntaxType::Utf8String => Ok(Value::new_utf8(value.to_string())),
|
||||||
SyntaxType::Utf8StringInsensitive => Ok(Value::new_iutf8(value)),
|
SyntaxType::Utf8StringInsensitive => Ok(Value::new_iutf8(value)),
|
||||||
SyntaxType::Utf8StringIname => Ok(Value::new_iname(value)),
|
SyntaxType::Utf8StringIname => Ok(Value::new_iname(value)),
|
||||||
SyntaxType::Boolean => Value::new_bools(value)
|
SyntaxType::Boolean => Value::new_bools(value)
|
||||||
.ok_or_else(|| OperationError::InvalidAttribute("Invalid boolean syntax".to_string())),
|
.ok_or_else(|| OperationError::InvalidAttribute("Invalid boolean syntax".to_string())),
|
||||||
SyntaxType::SYNTAX_ID => Value::new_syntaxs(value)
|
SyntaxType::SyntaxId => Value::new_syntaxs(value)
|
||||||
.ok_or_else(|| OperationError::InvalidAttribute("Invalid Syntax syntax".to_string())),
|
.ok_or_else(|| OperationError::InvalidAttribute("Invalid Syntax syntax".to_string())),
|
||||||
SyntaxType::INDEX_ID => Value::new_indexs(value)
|
SyntaxType::IndexId => Value::new_indexs(value)
|
||||||
.ok_or_else(|| OperationError::InvalidAttribute("Invalid Index syntax".to_string())),
|
.ok_or_else(|| OperationError::InvalidAttribute("Invalid Index syntax".to_string())),
|
||||||
SyntaxType::Uuid => {
|
SyntaxType::Uuid => {
|
||||||
// It's a uuid - we do NOT check for existance, because that
|
// It's a uuid - we do NOT check for existance, because that
|
||||||
|
@ -503,7 +506,7 @@ pub trait QueryServerTransaction<'a> {
|
||||||
// I think this is unreachable due to how the .or_else works.
|
// I think this is unreachable due to how the .or_else works.
|
||||||
.ok_or_else(|| OperationError::InvalidAttribute("Invalid UUID syntax".to_string()))
|
.ok_or_else(|| OperationError::InvalidAttribute("Invalid UUID syntax".to_string()))
|
||||||
}
|
}
|
||||||
SyntaxType::REFERENCE_UUID => {
|
SyntaxType::ReferenceUuid => {
|
||||||
// See comments above.
|
// See comments above.
|
||||||
Value::new_refer_s(value)
|
Value::new_refer_s(value)
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
|
@ -515,13 +518,13 @@ pub trait QueryServerTransaction<'a> {
|
||||||
// I think this is unreachable due to how the .or_else works.
|
// I think this is unreachable due to how the .or_else works.
|
||||||
.ok_or_else(|| OperationError::InvalidAttribute("Invalid Reference syntax".to_string()))
|
.ok_or_else(|| OperationError::InvalidAttribute("Invalid Reference syntax".to_string()))
|
||||||
}
|
}
|
||||||
SyntaxType::JSON_FILTER => Value::new_json_filter_s(value)
|
SyntaxType::JsonFilter => Value::new_json_filter_s(value)
|
||||||
.ok_or_else(|| OperationError::InvalidAttribute("Invalid Filter syntax".to_string())),
|
.ok_or_else(|| OperationError::InvalidAttribute("Invalid Filter syntax".to_string())),
|
||||||
SyntaxType::Credential => Err(OperationError::InvalidAttribute("Credentials can not be supplied through modification - please use the IDM api".to_string())),
|
SyntaxType::Credential => Err(OperationError::InvalidAttribute("Credentials can not be supplied through modification - please use the IDM api".to_string())),
|
||||||
SyntaxType::SecretUtf8String => Err(OperationError::InvalidAttribute("Radius secrets can not be supplied through modification - please use the IDM api".to_string())),
|
SyntaxType::SecretUtf8String => Err(OperationError::InvalidAttribute("Radius secrets can not be supplied through modification - please use the IDM api".to_string())),
|
||||||
SyntaxType::SshKey => Err(OperationError::InvalidAttribute("SSH public keys can not be supplied through modification - please use the IDM api".to_string())),
|
SyntaxType::SshKey => Err(OperationError::InvalidAttribute("SSH public keys can not be supplied through modification - please use the IDM api".to_string())),
|
||||||
SyntaxType::SecurityPrincipalName => Err(OperationError::InvalidAttribute("SPNs are generated and not able to be set.".to_string())),
|
SyntaxType::SecurityPrincipalName => Err(OperationError::InvalidAttribute("SPNs are generated and not able to be set.".to_string())),
|
||||||
SyntaxType::UINT32 => Value::new_uint32_str(value)
|
SyntaxType::Uint32 => Value::new_uint32_str(value)
|
||||||
.ok_or_else(|| OperationError::InvalidAttribute("Invalid uint32 syntax".to_string())),
|
.ok_or_else(|| OperationError::InvalidAttribute("Invalid uint32 syntax".to_string())),
|
||||||
SyntaxType::Cid => Err(OperationError::InvalidAttribute("CIDs are generated and not able to be set.".to_string())),
|
SyntaxType::Cid => Err(OperationError::InvalidAttribute("CIDs are generated and not able to be set.".to_string())),
|
||||||
SyntaxType::NsUniqueId => Value::new_nsuniqueid_s(value)
|
SyntaxType::NsUniqueId => Value::new_nsuniqueid_s(value)
|
||||||
|
@ -539,6 +542,9 @@ pub trait QueryServerTransaction<'a> {
|
||||||
SyntaxType::IntentToken => Err(OperationError::InvalidAttribute("Intent Token Values can not be supplied through modification".to_string())),
|
SyntaxType::IntentToken => Err(OperationError::InvalidAttribute("Intent Token Values can not be supplied through modification".to_string())),
|
||||||
SyntaxType::Passkey => Err(OperationError::InvalidAttribute("Passkey Values can not be supplied through modification".to_string())),
|
SyntaxType::Passkey => Err(OperationError::InvalidAttribute("Passkey Values can not be supplied through modification".to_string())),
|
||||||
SyntaxType::DeviceKey => Err(OperationError::InvalidAttribute("DeviceKey Values can not be supplied through modification".to_string())),
|
SyntaxType::DeviceKey => Err(OperationError::InvalidAttribute("DeviceKey Values can not be supplied through modification".to_string())),
|
||||||
|
SyntaxType::Session => Err(OperationError::InvalidAttribute("Session Values can not be supplied through modification".to_string())),
|
||||||
|
SyntaxType::JwsKeyEs256 => Err(OperationError::InvalidAttribute("JwsKeyEs256 Values can not be supplied through modification".to_string())),
|
||||||
|
SyntaxType::JwsKeyRs256 => Err(OperationError::InvalidAttribute("JwsKeyRs256 Values can not be supplied through modification".to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
@ -556,16 +562,18 @@ pub trait QueryServerTransaction<'a> {
|
||||||
match schema.get_attributes().get(attr) {
|
match schema.get_attributes().get(attr) {
|
||||||
Some(schema_a) => {
|
Some(schema_a) => {
|
||||||
match schema_a.syntax {
|
match schema_a.syntax {
|
||||||
SyntaxType::UTF8STRING => Ok(PartialValue::new_utf8(value.to_string())),
|
SyntaxType::Utf8String => Ok(PartialValue::new_utf8(value.to_string())),
|
||||||
SyntaxType::Utf8StringInsensitive => Ok(PartialValue::new_iutf8(value)),
|
SyntaxType::Utf8StringInsensitive
|
||||||
|
| SyntaxType::JwsKeyEs256
|
||||||
|
| SyntaxType::JwsKeyRs256 => Ok(PartialValue::new_iutf8(value)),
|
||||||
SyntaxType::Utf8StringIname => Ok(PartialValue::new_iname(value)),
|
SyntaxType::Utf8StringIname => Ok(PartialValue::new_iname(value)),
|
||||||
SyntaxType::Boolean => PartialValue::new_bools(value).ok_or_else(|| {
|
SyntaxType::Boolean => PartialValue::new_bools(value).ok_or_else(|| {
|
||||||
OperationError::InvalidAttribute("Invalid boolean syntax".to_string())
|
OperationError::InvalidAttribute("Invalid boolean syntax".to_string())
|
||||||
}),
|
}),
|
||||||
SyntaxType::SYNTAX_ID => PartialValue::new_syntaxs(value).ok_or_else(|| {
|
SyntaxType::SyntaxId => PartialValue::new_syntaxs(value).ok_or_else(|| {
|
||||||
OperationError::InvalidAttribute("Invalid Syntax syntax".to_string())
|
OperationError::InvalidAttribute("Invalid Syntax syntax".to_string())
|
||||||
}),
|
}),
|
||||||
SyntaxType::INDEX_ID => PartialValue::new_indexs(value).ok_or_else(|| {
|
SyntaxType::IndexId => PartialValue::new_indexs(value).ok_or_else(|| {
|
||||||
OperationError::InvalidAttribute("Invalid Index syntax".to_string())
|
OperationError::InvalidAttribute("Invalid Index syntax".to_string())
|
||||||
}),
|
}),
|
||||||
SyntaxType::Uuid => {
|
SyntaxType::Uuid => {
|
||||||
|
@ -595,7 +603,10 @@ pub trait QueryServerTransaction<'a> {
|
||||||
// PartialValue::new_uuid(un)
|
// PartialValue::new_uuid(un)
|
||||||
// }))
|
// }))
|
||||||
}
|
}
|
||||||
SyntaxType::REFERENCE_UUID => {
|
// ⚠️ Any types here need to also be added to update_attributes in
|
||||||
|
// schema.rs for reference type / cache awareness during referential
|
||||||
|
// integrity processing. Exceptions are self-contained value types!
|
||||||
|
SyntaxType::ReferenceUuid | SyntaxType::OauthScopeMap | SyntaxType::Session => {
|
||||||
// See comments above.
|
// See comments above.
|
||||||
PartialValue::new_refer_s(value)
|
PartialValue::new_refer_s(value)
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
|
@ -610,23 +621,7 @@ pub trait QueryServerTransaction<'a> {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
SyntaxType::OauthScopeMap => {
|
SyntaxType::JsonFilter => {
|
||||||
// See comments above.
|
|
||||||
PartialValue::new_oauthscopemap_s(value)
|
|
||||||
.or_else(|| {
|
|
||||||
let un = self.name_to_uuid(value).unwrap_or(UUID_DOES_NOT_EXIST);
|
|
||||||
Some(PartialValue::new_oauthscopemap(un))
|
|
||||||
})
|
|
||||||
// I think this is unreachable due to how the .or_else works.
|
|
||||||
// See above case for how to avoid having unreachable code
|
|
||||||
.ok_or_else(|| {
|
|
||||||
OperationError::InvalidAttribute(
|
|
||||||
"Invalid Reference syntax".to_string(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
SyntaxType::JSON_FILTER => {
|
|
||||||
PartialValue::new_json_filter_s(value).ok_or_else(|| {
|
PartialValue::new_json_filter_s(value).ok_or_else(|| {
|
||||||
OperationError::InvalidAttribute("Invalid Filter syntax".to_string())
|
OperationError::InvalidAttribute("Invalid Filter syntax".to_string())
|
||||||
})
|
})
|
||||||
|
@ -639,7 +634,7 @@ pub trait QueryServerTransaction<'a> {
|
||||||
OperationError::InvalidAttribute("Invalid spn syntax".to_string())
|
OperationError::InvalidAttribute("Invalid spn syntax".to_string())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
SyntaxType::UINT32 => PartialValue::new_uint32_str(value).ok_or_else(|| {
|
SyntaxType::Uint32 => PartialValue::new_uint32_str(value).ok_or_else(|| {
|
||||||
OperationError::InvalidAttribute("Invalid uint32 syntax".to_string())
|
OperationError::InvalidAttribute("Invalid uint32 syntax".to_string())
|
||||||
}),
|
}),
|
||||||
SyntaxType::Cid => PartialValue::new_cid_s(value).ok_or_else(|| {
|
SyntaxType::Cid => PartialValue::new_cid_s(value).ok_or_else(|| {
|
||||||
|
@ -832,7 +827,7 @@ impl<'a> QueryServerTransaction<'a> for QueryServerReadTransaction<'a> {
|
||||||
|
|
||||||
fn get_resolve_filter_cache(
|
fn get_resolve_filter_cache(
|
||||||
&self,
|
&self,
|
||||||
) -> &mut ARCacheReadTxn<'a, (IdentityId, Filter<FilterValid>), Filter<FilterValidResolved>>
|
) -> &mut ARCacheReadTxn<'a, (IdentityId, Filter<FilterValid>), Filter<FilterValidResolved>, ()>
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
let mptr = self.resolve_filter_cache.as_ptr();
|
let mptr = self.resolve_filter_cache.as_ptr();
|
||||||
|
@ -841,6 +836,7 @@ impl<'a> QueryServerTransaction<'a> for QueryServerReadTransaction<'a> {
|
||||||
'a,
|
'a,
|
||||||
(IdentityId, Filter<FilterValid>),
|
(IdentityId, Filter<FilterValid>),
|
||||||
Filter<FilterValidResolved>,
|
Filter<FilterValidResolved>,
|
||||||
|
(),
|
||||||
>
|
>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -942,7 +938,7 @@ impl<'a> QueryServerTransaction<'a> for QueryServerWriteTransaction<'a> {
|
||||||
|
|
||||||
fn get_resolve_filter_cache(
|
fn get_resolve_filter_cache(
|
||||||
&self,
|
&self,
|
||||||
) -> &mut ARCacheReadTxn<'a, (IdentityId, Filter<FilterValid>), Filter<FilterValidResolved>>
|
) -> &mut ARCacheReadTxn<'a, (IdentityId, Filter<FilterValid>), Filter<FilterValidResolved>, ()>
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
let mptr = self.resolve_filter_cache.as_ptr();
|
let mptr = self.resolve_filter_cache.as_ptr();
|
||||||
|
@ -951,6 +947,7 @@ impl<'a> QueryServerTransaction<'a> for QueryServerWriteTransaction<'a> {
|
||||||
'a,
|
'a,
|
||||||
(IdentityId, Filter<FilterValid>),
|
(IdentityId, Filter<FilterValid>),
|
||||||
Filter<FilterValidResolved>,
|
Filter<FilterValidResolved>,
|
||||||
|
(),
|
||||||
>
|
>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1200,6 +1197,10 @@ impl QueryServer {
|
||||||
migrate_txn.migrate_6_to_7()?;
|
migrate_txn.migrate_6_to_7()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if system_info_version < 8 {
|
||||||
|
migrate_txn.migrate_7_to_8()?;
|
||||||
|
}
|
||||||
|
|
||||||
migrate_txn.commit()?;
|
migrate_txn.commit()?;
|
||||||
// Migrations complete. Init idm will now set the version as needed.
|
// Migrations complete. Init idm will now set the version as needed.
|
||||||
|
|
||||||
|
@ -2319,7 +2320,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
||||||
/// Modify accounts that are not persons, to be service accounts so that the extension
|
/// Modify accounts that are not persons, to be service accounts so that the extension
|
||||||
/// rules remain valid.
|
/// rules remain valid.
|
||||||
pub fn migrate_6_to_7(&self) -> Result<(), OperationError> {
|
pub fn migrate_6_to_7(&self) -> Result<(), OperationError> {
|
||||||
spanned!("server::migrate_5_to_6", {
|
spanned!("server::migrate_6_to_7", {
|
||||||
admin_warn!("starting 6 to 7 migration.");
|
admin_warn!("starting 6 to 7 migration.");
|
||||||
let filter = filter!(f_and!([
|
let filter = filter!(f_and!([
|
||||||
f_eq("class", (*PVCLASS_ACCOUNT).clone()),
|
f_eq("class", (*PVCLASS_ACCOUNT).clone()),
|
||||||
|
@ -2331,6 +2332,19 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Migrate 7 to 8
|
||||||
|
///
|
||||||
|
/// Touch all service accounts to trigger a regen of their es256 jws keys for api tokens
|
||||||
|
pub fn migrate_7_to_8(&self) -> Result<(), OperationError> {
|
||||||
|
spanned!("server::migrate_7_to_8", {
|
||||||
|
admin_warn!("starting 7 to 8 migration.");
|
||||||
|
let filter = filter!(f_eq("class", (*PVCLASS_SERVICE_ACCOUNT).clone()));
|
||||||
|
let modlist = ModifyList::new_append("class", Value::new_class("service_account"));
|
||||||
|
self.internal_modify(&filter, &modlist)
|
||||||
|
// Complete
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// These are where searches and other actions are actually implemented. This
|
// These are where searches and other actions are actually implemented. This
|
||||||
// is the "internal" version, where we define the event as being internal
|
// is the "internal" version, where we define the event as being internal
|
||||||
// only, allowing certain plugin by passes etc.
|
// only, allowing certain plugin by passes etc.
|
||||||
|
@ -2633,6 +2647,8 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
||||||
JSON_SCHEMA_ATTR_PASSKEYS,
|
JSON_SCHEMA_ATTR_PASSKEYS,
|
||||||
JSON_SCHEMA_ATTR_DEVICEKEYS,
|
JSON_SCHEMA_ATTR_DEVICEKEYS,
|
||||||
JSON_SCHEMA_ATTR_DYNGROUP_FILTER,
|
JSON_SCHEMA_ATTR_DYNGROUP_FILTER,
|
||||||
|
JSON_SCHEMA_ATTR_JWS_ES256_PRIVATE_KEY,
|
||||||
|
JSON_SCHEMA_ATTR_API_TOKEN_SESSION,
|
||||||
JSON_SCHEMA_CLASS_PERSON,
|
JSON_SCHEMA_CLASS_PERSON,
|
||||||
JSON_SCHEMA_CLASS_ORGPERSON,
|
JSON_SCHEMA_CLASS_ORGPERSON,
|
||||||
JSON_SCHEMA_CLASS_GROUP,
|
JSON_SCHEMA_CLASS_GROUP,
|
||||||
|
|
|
@ -5,9 +5,11 @@
|
||||||
|
|
||||||
use crate::be::dbentry::DbIdentSpn;
|
use crate::be::dbentry::DbIdentSpn;
|
||||||
use crate::credential::Credential;
|
use crate::credential::Credential;
|
||||||
|
use crate::identity::IdentityId;
|
||||||
use crate::repl::cid::Cid;
|
use crate::repl::cid::Cid;
|
||||||
use kanidm_proto::v1::Filter as ProtoFilter;
|
use kanidm_proto::v1::Filter as ProtoFilter;
|
||||||
|
|
||||||
|
use compact_jwt::JwsSigner;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
@ -149,32 +151,36 @@ impl fmt::Display for IndexType {
|
||||||
|
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
#[derive(Hash, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
|
#[derive(Hash, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
|
||||||
|
#[repr(u16)]
|
||||||
pub enum SyntaxType {
|
pub enum SyntaxType {
|
||||||
UTF8STRING,
|
Utf8String = 0,
|
||||||
Utf8StringInsensitive,
|
Utf8StringInsensitive = 1,
|
||||||
Utf8StringIname,
|
Uuid = 2,
|
||||||
Uuid,
|
Boolean = 3,
|
||||||
Boolean,
|
SyntaxId = 4,
|
||||||
SYNTAX_ID,
|
IndexId = 5,
|
||||||
INDEX_ID,
|
ReferenceUuid = 6,
|
||||||
REFERENCE_UUID,
|
JsonFilter = 7,
|
||||||
JSON_FILTER,
|
Credential = 8,
|
||||||
Credential,
|
SecretUtf8String = 9,
|
||||||
SecretUtf8String,
|
SshKey = 10,
|
||||||
SshKey,
|
SecurityPrincipalName = 11,
|
||||||
SecurityPrincipalName,
|
Uint32 = 12,
|
||||||
UINT32,
|
Cid = 13,
|
||||||
Cid,
|
Utf8StringIname = 14,
|
||||||
NsUniqueId,
|
NsUniqueId = 15,
|
||||||
DateTime,
|
DateTime = 16,
|
||||||
EmailAddress,
|
EmailAddress = 17,
|
||||||
Url,
|
Url = 18,
|
||||||
OauthScope,
|
OauthScope = 19,
|
||||||
OauthScopeMap,
|
OauthScopeMap = 20,
|
||||||
PrivateBinary,
|
PrivateBinary = 21,
|
||||||
IntentToken,
|
IntentToken = 22,
|
||||||
Passkey,
|
Passkey = 23,
|
||||||
DeviceKey,
|
DeviceKey = 24,
|
||||||
|
Session = 25,
|
||||||
|
JwsKeyEs256 = 26,
|
||||||
|
JwsKeyRs256 = 27,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&str> for SyntaxType {
|
impl TryFrom<&str> for SyntaxType {
|
||||||
|
@ -183,21 +189,21 @@ impl TryFrom<&str> for SyntaxType {
|
||||||
fn try_from(value: &str) -> Result<SyntaxType, Self::Error> {
|
fn try_from(value: &str) -> Result<SyntaxType, Self::Error> {
|
||||||
let n_value = value.to_uppercase();
|
let n_value = value.to_uppercase();
|
||||||
match n_value.as_str() {
|
match n_value.as_str() {
|
||||||
"UTF8STRING" => Ok(SyntaxType::UTF8STRING),
|
"UTF8STRING" => Ok(SyntaxType::Utf8String),
|
||||||
"UTF8STRING_INSENSITIVE" => Ok(SyntaxType::Utf8StringInsensitive),
|
"UTF8STRING_INSENSITIVE" => Ok(SyntaxType::Utf8StringInsensitive),
|
||||||
"UTF8STRING_INAME" => Ok(SyntaxType::Utf8StringIname),
|
"UTF8STRING_INAME" => Ok(SyntaxType::Utf8StringIname),
|
||||||
"UUID" => Ok(SyntaxType::Uuid),
|
"UUID" => Ok(SyntaxType::Uuid),
|
||||||
"BOOLEAN" => Ok(SyntaxType::Boolean),
|
"BOOLEAN" => Ok(SyntaxType::Boolean),
|
||||||
"SYNTAX_ID" => Ok(SyntaxType::SYNTAX_ID),
|
"SYNTAX_ID" => Ok(SyntaxType::SyntaxId),
|
||||||
"INDEX_ID" => Ok(SyntaxType::INDEX_ID),
|
"INDEX_ID" => Ok(SyntaxType::IndexId),
|
||||||
"REFERENCE_UUID" => Ok(SyntaxType::REFERENCE_UUID),
|
"REFERENCE_UUID" => Ok(SyntaxType::ReferenceUuid),
|
||||||
"JSON_FILTER" => Ok(SyntaxType::JSON_FILTER),
|
"JSON_FILTER" => Ok(SyntaxType::JsonFilter),
|
||||||
"CREDENTIAL" => Ok(SyntaxType::Credential),
|
"CREDENTIAL" => Ok(SyntaxType::Credential),
|
||||||
// Compatability for older syntax name.
|
// Compatability for older syntax name.
|
||||||
"RADIUS_UTF8STRING" | "SECRET_UTF8STRING" => Ok(SyntaxType::SecretUtf8String),
|
"RADIUS_UTF8STRING" | "SECRET_UTF8STRING" => Ok(SyntaxType::SecretUtf8String),
|
||||||
"SSHKEY" => Ok(SyntaxType::SshKey),
|
"SSHKEY" => Ok(SyntaxType::SshKey),
|
||||||
"SECURITY_PRINCIPAL_NAME" => Ok(SyntaxType::SecurityPrincipalName),
|
"SECURITY_PRINCIPAL_NAME" => Ok(SyntaxType::SecurityPrincipalName),
|
||||||
"UINT32" => Ok(SyntaxType::UINT32),
|
"UINT32" => Ok(SyntaxType::Uint32),
|
||||||
"CID" => Ok(SyntaxType::Cid),
|
"CID" => Ok(SyntaxType::Cid),
|
||||||
"NSUNIQUEID" => Ok(SyntaxType::NsUniqueId),
|
"NSUNIQUEID" => Ok(SyntaxType::NsUniqueId),
|
||||||
"DATETIME" => Ok(SyntaxType::DateTime),
|
"DATETIME" => Ok(SyntaxType::DateTime),
|
||||||
|
@ -209,29 +215,32 @@ impl TryFrom<&str> for SyntaxType {
|
||||||
"INTENT_TOKEN" => Ok(SyntaxType::IntentToken),
|
"INTENT_TOKEN" => Ok(SyntaxType::IntentToken),
|
||||||
"PASSKEY" => Ok(SyntaxType::Passkey),
|
"PASSKEY" => Ok(SyntaxType::Passkey),
|
||||||
"DEVICEKEY" => Ok(SyntaxType::DeviceKey),
|
"DEVICEKEY" => Ok(SyntaxType::DeviceKey),
|
||||||
|
"SESSION" => Ok(SyntaxType::Session),
|
||||||
|
"JWS_KEY_ES256" => Ok(SyntaxType::JwsKeyEs256),
|
||||||
|
"JWS_KEY_RS256" => Ok(SyntaxType::JwsKeyRs256),
|
||||||
_ => Err(()),
|
_ => Err(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<usize> for SyntaxType {
|
impl TryFrom<u16> for SyntaxType {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn try_from(value: usize) -> Result<Self, Self::Error> {
|
fn try_from(value: u16) -> Result<Self, Self::Error> {
|
||||||
match value {
|
match value {
|
||||||
0 => Ok(SyntaxType::UTF8STRING),
|
0 => Ok(SyntaxType::Utf8String),
|
||||||
1 => Ok(SyntaxType::Utf8StringInsensitive),
|
1 => Ok(SyntaxType::Utf8StringInsensitive),
|
||||||
2 => Ok(SyntaxType::Uuid),
|
2 => Ok(SyntaxType::Uuid),
|
||||||
3 => Ok(SyntaxType::Boolean),
|
3 => Ok(SyntaxType::Boolean),
|
||||||
4 => Ok(SyntaxType::SYNTAX_ID),
|
4 => Ok(SyntaxType::SyntaxId),
|
||||||
5 => Ok(SyntaxType::INDEX_ID),
|
5 => Ok(SyntaxType::IndexId),
|
||||||
6 => Ok(SyntaxType::REFERENCE_UUID),
|
6 => Ok(SyntaxType::ReferenceUuid),
|
||||||
7 => Ok(SyntaxType::JSON_FILTER),
|
7 => Ok(SyntaxType::JsonFilter),
|
||||||
8 => Ok(SyntaxType::Credential),
|
8 => Ok(SyntaxType::Credential),
|
||||||
9 => Ok(SyntaxType::SecretUtf8String),
|
9 => Ok(SyntaxType::SecretUtf8String),
|
||||||
10 => Ok(SyntaxType::SshKey),
|
10 => Ok(SyntaxType::SshKey),
|
||||||
11 => Ok(SyntaxType::SecurityPrincipalName),
|
11 => Ok(SyntaxType::SecurityPrincipalName),
|
||||||
12 => Ok(SyntaxType::UINT32),
|
12 => Ok(SyntaxType::Uint32),
|
||||||
13 => Ok(SyntaxType::Cid),
|
13 => Ok(SyntaxType::Cid),
|
||||||
14 => Ok(SyntaxType::Utf8StringIname),
|
14 => Ok(SyntaxType::Utf8StringIname),
|
||||||
15 => Ok(SyntaxType::NsUniqueId),
|
15 => Ok(SyntaxType::NsUniqueId),
|
||||||
|
@ -244,60 +253,31 @@ impl TryFrom<usize> for SyntaxType {
|
||||||
22 => Ok(SyntaxType::IntentToken),
|
22 => Ok(SyntaxType::IntentToken),
|
||||||
23 => Ok(SyntaxType::Passkey),
|
23 => Ok(SyntaxType::Passkey),
|
||||||
24 => Ok(SyntaxType::DeviceKey),
|
24 => Ok(SyntaxType::DeviceKey),
|
||||||
|
25 => Ok(SyntaxType::Session),
|
||||||
|
26 => Ok(SyntaxType::JwsKeyEs256),
|
||||||
|
27 => Ok(SyntaxType::JwsKeyRs256),
|
||||||
_ => Err(()),
|
_ => Err(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SyntaxType {
|
|
||||||
pub fn to_usize(&self) -> usize {
|
|
||||||
match self {
|
|
||||||
SyntaxType::UTF8STRING => 0,
|
|
||||||
SyntaxType::Utf8StringInsensitive => 1,
|
|
||||||
SyntaxType::Uuid => 2,
|
|
||||||
SyntaxType::Boolean => 3,
|
|
||||||
SyntaxType::SYNTAX_ID => 4,
|
|
||||||
SyntaxType::INDEX_ID => 5,
|
|
||||||
SyntaxType::REFERENCE_UUID => 6,
|
|
||||||
SyntaxType::JSON_FILTER => 7,
|
|
||||||
SyntaxType::Credential => 8,
|
|
||||||
SyntaxType::SecretUtf8String => 9,
|
|
||||||
SyntaxType::SshKey => 10,
|
|
||||||
SyntaxType::SecurityPrincipalName => 11,
|
|
||||||
SyntaxType::UINT32 => 12,
|
|
||||||
SyntaxType::Cid => 13,
|
|
||||||
SyntaxType::Utf8StringIname => 14,
|
|
||||||
SyntaxType::NsUniqueId => 15,
|
|
||||||
SyntaxType::DateTime => 16,
|
|
||||||
SyntaxType::EmailAddress => 17,
|
|
||||||
SyntaxType::Url => 18,
|
|
||||||
SyntaxType::OauthScope => 19,
|
|
||||||
SyntaxType::OauthScopeMap => 20,
|
|
||||||
SyntaxType::PrivateBinary => 21,
|
|
||||||
SyntaxType::IntentToken => 22,
|
|
||||||
SyntaxType::Passkey => 23,
|
|
||||||
SyntaxType::DeviceKey => 24,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for SyntaxType {
|
impl fmt::Display for SyntaxType {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
f.write_str(match self {
|
f.write_str(match self {
|
||||||
SyntaxType::UTF8STRING => "UTF8STRING",
|
SyntaxType::Utf8String => "UTF8STRING",
|
||||||
SyntaxType::Utf8StringInsensitive => "UTF8STRING_INSENSITIVE",
|
SyntaxType::Utf8StringInsensitive => "UTF8STRING_INSENSITIVE",
|
||||||
SyntaxType::Utf8StringIname => "UTF8STRING_INAME",
|
SyntaxType::Utf8StringIname => "UTF8STRING_INAME",
|
||||||
SyntaxType::Uuid => "UUID",
|
SyntaxType::Uuid => "UUID",
|
||||||
SyntaxType::Boolean => "BOOLEAN",
|
SyntaxType::Boolean => "BOOLEAN",
|
||||||
SyntaxType::SYNTAX_ID => "SYNTAX_ID",
|
SyntaxType::SyntaxId => "SYNTAX_ID",
|
||||||
SyntaxType::INDEX_ID => "INDEX_ID",
|
SyntaxType::IndexId => "INDEX_ID",
|
||||||
SyntaxType::REFERENCE_UUID => "REFERENCE_UUID",
|
SyntaxType::ReferenceUuid => "REFERENCE_UUID",
|
||||||
SyntaxType::JSON_FILTER => "JSON_FILTER",
|
SyntaxType::JsonFilter => "JSON_FILTER",
|
||||||
SyntaxType::Credential => "CREDENTIAL",
|
SyntaxType::Credential => "CREDENTIAL",
|
||||||
SyntaxType::SecretUtf8String => "SECRET_UTF8STRING",
|
SyntaxType::SecretUtf8String => "SECRET_UTF8STRING",
|
||||||
SyntaxType::SshKey => "SSHKEY",
|
SyntaxType::SshKey => "SSHKEY",
|
||||||
SyntaxType::SecurityPrincipalName => "SECURITY_PRINCIPAL_NAME",
|
SyntaxType::SecurityPrincipalName => "SECURITY_PRINCIPAL_NAME",
|
||||||
SyntaxType::UINT32 => "UINT32",
|
SyntaxType::Uint32 => "UINT32",
|
||||||
SyntaxType::Cid => "CID",
|
SyntaxType::Cid => "CID",
|
||||||
SyntaxType::NsUniqueId => "NSUNIQUEID",
|
SyntaxType::NsUniqueId => "NSUNIQUEID",
|
||||||
SyntaxType::DateTime => "DATETIME",
|
SyntaxType::DateTime => "DATETIME",
|
||||||
|
@ -309,6 +289,9 @@ impl fmt::Display for SyntaxType {
|
||||||
SyntaxType::IntentToken => "INTENT_TOKEN",
|
SyntaxType::IntentToken => "INTENT_TOKEN",
|
||||||
SyntaxType::Passkey => "PASSKEY",
|
SyntaxType::Passkey => "PASSKEY",
|
||||||
SyntaxType::DeviceKey => "DEVICEKEY",
|
SyntaxType::DeviceKey => "DEVICEKEY",
|
||||||
|
SyntaxType::Session => "SESSION",
|
||||||
|
SyntaxType::JwsKeyEs256 => "JWS_KEY_ES256",
|
||||||
|
SyntaxType::JwsKeyRs256 => "JWS_KEY_RS256",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -347,7 +330,7 @@ pub enum PartialValue {
|
||||||
// Can add other selectors later.
|
// Can add other selectors later.
|
||||||
Url(Url),
|
Url(Url),
|
||||||
OauthScope(String),
|
OauthScope(String),
|
||||||
OauthScopeMap(Uuid),
|
// OauthScopeMap(Uuid),
|
||||||
PrivateBinary,
|
PrivateBinary,
|
||||||
PublicBinary(String),
|
PublicBinary(String),
|
||||||
// Enumeration(String),
|
// Enumeration(String),
|
||||||
|
@ -359,7 +342,7 @@ pub enum PartialValue {
|
||||||
DeviceKey(Uuid),
|
DeviceKey(Uuid),
|
||||||
|
|
||||||
TrustedDeviceEnrollment(Uuid),
|
TrustedDeviceEnrollment(Uuid),
|
||||||
AuthSession(Uuid),
|
Session(Uuid),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<SyntaxType> for PartialValue {
|
impl From<SyntaxType> for PartialValue {
|
||||||
|
@ -645,6 +628,7 @@ impl PartialValue {
|
||||||
matches!(self, PartialValue::OauthScope(_))
|
matches!(self, PartialValue::OauthScope(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
pub fn new_oauthscopemap(u: Uuid) -> Self {
|
pub fn new_oauthscopemap(u: Uuid) -> Self {
|
||||||
PartialValue::OauthScopeMap(u)
|
PartialValue::OauthScopeMap(u)
|
||||||
}
|
}
|
||||||
|
@ -659,6 +643,7 @@ impl PartialValue {
|
||||||
pub fn is_oauthscopemap(&self) -> bool {
|
pub fn is_oauthscopemap(&self) -> bool {
|
||||||
matches!(self, PartialValue::OauthScopeMap(_))
|
matches!(self, PartialValue::OauthScopeMap(_))
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
pub fn is_privatebinary(&self) -> bool {
|
pub fn is_privatebinary(&self) -> bool {
|
||||||
matches!(self, PartialValue::PrivateBinary)
|
matches!(self, PartialValue::PrivateBinary)
|
||||||
|
@ -735,12 +720,11 @@ impl PartialValue {
|
||||||
}
|
}
|
||||||
PartialValue::Url(u) => u.to_string(),
|
PartialValue::Url(u) => u.to_string(),
|
||||||
PartialValue::OauthScope(u) => u.to_string(),
|
PartialValue::OauthScope(u) => u.to_string(),
|
||||||
PartialValue::OauthScopeMap(u) => u.as_hyphenated().to_string(),
|
|
||||||
PartialValue::Address(a) => a.to_string(),
|
PartialValue::Address(a) => a.to_string(),
|
||||||
PartialValue::PhoneNumber(a) => a.to_string(),
|
PartialValue::PhoneNumber(a) => a.to_string(),
|
||||||
PartialValue::IntentToken(u) => u.clone(),
|
PartialValue::IntentToken(u) => u.clone(),
|
||||||
PartialValue::TrustedDeviceEnrollment(u) => u.as_hyphenated().to_string(),
|
PartialValue::TrustedDeviceEnrollment(u) => u.as_hyphenated().to_string(),
|
||||||
PartialValue::AuthSession(u) => u.as_hyphenated().to_string(),
|
PartialValue::Session(u) => u.as_hyphenated().to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -749,6 +733,14 @@ impl PartialValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct Session {
|
||||||
|
pub label: String,
|
||||||
|
pub expiry: Option<OffsetDateTime>,
|
||||||
|
pub issued_at: OffsetDateTime,
|
||||||
|
pub issued_by: IdentityId,
|
||||||
|
}
|
||||||
|
|
||||||
/// A value is a complete unit of data for an attribute. It is made up of a PartialValue, which is
|
/// A value is a complete unit of data for an attribute. It is made up of a PartialValue, which is
|
||||||
/// used for selection, filtering, searching, matching etc. It also contains supplemental data
|
/// used for selection, filtering, searching, matching etc. It also contains supplemental data
|
||||||
/// which may be stored inside of the Value, such as credential secrets, blobs etc.
|
/// which may be stored inside of the Value, such as credential secrets, blobs etc.
|
||||||
|
@ -792,7 +784,10 @@ pub enum Value {
|
||||||
DeviceKey(Uuid, String, DeviceKeyV4),
|
DeviceKey(Uuid, String, DeviceKeyV4),
|
||||||
|
|
||||||
TrustedDeviceEnrollment(Uuid),
|
TrustedDeviceEnrollment(Uuid),
|
||||||
AuthSession(Uuid),
|
Session(Uuid, Session),
|
||||||
|
|
||||||
|
JwsKeyEs256(JwsSigner),
|
||||||
|
JwsKeyRs256(JwsSigner),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for Value {
|
impl PartialEq for Value {
|
||||||
|
@ -831,13 +826,16 @@ impl PartialEq for Value {
|
||||||
// OauthScopeMap
|
// OauthScopeMap
|
||||||
(Value::OauthScopeMap(a, c), Value::OauthScopeMap(b, d)) => a.eq(b) && c.eq(d),
|
(Value::OauthScopeMap(a, c), Value::OauthScopeMap(b, d)) => a.eq(b) && c.eq(d),
|
||||||
|
|
||||||
// Address
|
|
||||||
// PrivateBinary
|
|
||||||
// SecretValue
|
|
||||||
(Value::Address(_), Value::Address(_))
|
(Value::Address(_), Value::Address(_))
|
||||||
| (Value::PrivateBinary(_), Value::PrivateBinary(_))
|
| (Value::PrivateBinary(_), Value::PrivateBinary(_))
|
||||||
| (Value::SecretValue(_), Value::SecretValue(_)) => false,
|
| (Value::SecretValue(_), Value::SecretValue(_)) => false,
|
||||||
_ => false,
|
// Specifically related to migrations, we allow the invalid comparison.
|
||||||
|
(Value::Iutf8(_), Value::Iname(_)) | (Value::Iname(_), Value::Iutf8(_)) => false,
|
||||||
|
(l, r) => {
|
||||||
|
error!(?l, ?r, "mismatched value types");
|
||||||
|
debug_assert!(false);
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1492,9 +1490,9 @@ impl Value {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_authsession(self) -> Option<(Uuid, ())> {
|
pub fn to_session(self) -> Option<(Uuid, Session)> {
|
||||||
match self {
|
match self {
|
||||||
Value::AuthSession(u) => Some((u, ())),
|
Value::Session(u, s) => Some((u, s)),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1584,7 +1582,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_value_syntax_tryfrom() {
|
fn test_value_syntax_tryfrom() {
|
||||||
let r1 = SyntaxType::try_from("UTF8STRING");
|
let r1 = SyntaxType::try_from("UTF8STRING");
|
||||||
assert_eq!(r1, Ok(SyntaxType::UTF8STRING));
|
assert_eq!(r1, Ok(SyntaxType::Utf8String));
|
||||||
|
|
||||||
let r2 = SyntaxType::try_from("UTF8STRING_INSENSITIVE");
|
let r2 = SyntaxType::try_from("UTF8STRING_INSENSITIVE");
|
||||||
assert_eq!(r2, Ok(SyntaxType::Utf8StringInsensitive));
|
assert_eq!(r2, Ok(SyntaxType::Utf8StringInsensitive));
|
||||||
|
@ -1593,10 +1591,10 @@ mod tests {
|
||||||
assert_eq!(r3, Ok(SyntaxType::Boolean));
|
assert_eq!(r3, Ok(SyntaxType::Boolean));
|
||||||
|
|
||||||
let r4 = SyntaxType::try_from("SYNTAX_ID");
|
let r4 = SyntaxType::try_from("SYNTAX_ID");
|
||||||
assert_eq!(r4, Ok(SyntaxType::SYNTAX_ID));
|
assert_eq!(r4, Ok(SyntaxType::SyntaxId));
|
||||||
|
|
||||||
let r5 = SyntaxType::try_from("INDEX_ID");
|
let r5 = SyntaxType::try_from("INDEX_ID");
|
||||||
assert_eq!(r5, Ok(SyntaxType::INDEX_ID));
|
assert_eq!(r5, Ok(SyntaxType::IndexId));
|
||||||
|
|
||||||
let r6 = SyntaxType::try_from("zzzzantheou");
|
let r6 = SyntaxType::try_from("zzzzantheou");
|
||||||
assert_eq!(r6, Err(()));
|
assert_eq!(r6, Err(()));
|
||||||
|
|
|
@ -86,7 +86,7 @@ impl ValueSetT for ValueSetIndex {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn syntax(&self) -> SyntaxType {
|
fn syntax(&self) -> SyntaxType {
|
||||||
SyntaxType::INDEX_ID
|
SyntaxType::IndexId
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
|
fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
|
||||||
|
|
|
@ -96,7 +96,7 @@ impl ValueSetT for ValueSetJsonFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn syntax(&self) -> SyntaxType {
|
fn syntax(&self) -> SyntaxType {
|
||||||
SyntaxType::JSON_FILTER
|
SyntaxType::JsonFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
|
fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
|
||||||
|
|
319
kanidmd/idm/src/valueset/jws.rs
Normal file
319
kanidmd/idm/src/valueset/jws.rs
Normal file
|
@ -0,0 +1,319 @@
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::schema::SchemaAttribute;
|
||||||
|
use crate::valueset::DbValueSetV2;
|
||||||
|
use crate::valueset::ValueSet;
|
||||||
|
use hashbrown::HashSet;
|
||||||
|
|
||||||
|
use compact_jwt::{JwaAlg, JwsSigner};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ValueSetJwsKeyEs256 {
|
||||||
|
set: HashSet<JwsSigner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ValueSetJwsKeyEs256 {
|
||||||
|
pub fn new(k: JwsSigner) -> Box<Self> {
|
||||||
|
debug_assert!(k.get_jwa_alg() == JwaAlg::ES256);
|
||||||
|
let mut set = HashSet::new();
|
||||||
|
set.insert(k);
|
||||||
|
Box::new(ValueSetJwsKeyEs256 { set })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(&mut self, k: JwsSigner) -> bool {
|
||||||
|
debug_assert!(k.get_jwa_alg() == JwaAlg::ES256);
|
||||||
|
self.set.insert(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_dbvs2(data: Vec<Vec<u8>>) -> Result<ValueSet, OperationError> {
|
||||||
|
let set = data
|
||||||
|
.iter()
|
||||||
|
.map(|b| {
|
||||||
|
JwsSigner::from_es256_der(b).map_err(|e| {
|
||||||
|
debug!(?e, "Error occured parsing ES256 DER");
|
||||||
|
OperationError::InvalidValueState
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<HashSet<_>, _>>()?;
|
||||||
|
Ok(Box::new(ValueSetJwsKeyEs256 { set }))
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to allow this, because rust doesn't allow us to impl FromIterator on foreign
|
||||||
|
// types, and jwssigner is foreign
|
||||||
|
#[allow(clippy::should_implement_trait)]
|
||||||
|
pub fn from_iter<T>(iter: T) -> Option<Box<ValueSetJwsKeyEs256>>
|
||||||
|
where
|
||||||
|
T: IntoIterator<Item = JwsSigner>,
|
||||||
|
{
|
||||||
|
let set: HashSet<JwsSigner> = iter.into_iter().collect();
|
||||||
|
debug_assert!(set.iter().all(|k| k.get_jwa_alg() == JwaAlg::ES256));
|
||||||
|
Some(Box::new(ValueSetJwsKeyEs256 { set }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ValueSetT for ValueSetJwsKeyEs256 {
|
||||||
|
fn insert_checked(&mut self, value: Value) -> Result<bool, OperationError> {
|
||||||
|
match value {
|
||||||
|
Value::JwsKeyEs256(k) => Ok(self.set.insert(k)),
|
||||||
|
_ => {
|
||||||
|
debug_assert!(false);
|
||||||
|
Err(OperationError::InvalidValueState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear(&mut self) {
|
||||||
|
self.set.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&mut self, pv: &PartialValue) -> bool {
|
||||||
|
match pv {
|
||||||
|
PartialValue::Iutf8(kid) => {
|
||||||
|
let x = self.set.len();
|
||||||
|
self.set.retain(|k| k.get_kid() != kid);
|
||||||
|
x != self.set.len()
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains(&self, _pv: &PartialValue) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn substring(&self, _pv: &PartialValue) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lessthan(&self, _pv: &PartialValue) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
self.set.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_idx_eq_keys(&self) -> Vec<String> {
|
||||||
|
self.set.iter().map(|k| k.get_kid().to_string()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn syntax(&self) -> SyntaxType {
|
||||||
|
SyntaxType::JwsKeyEs256
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
|
||||||
|
self.set.iter().all(|k| k.get_jwa_alg() == JwaAlg::ES256)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_proto_string_clone_iter(&self) -> Box<dyn Iterator<Item = String> + '_> {
|
||||||
|
Box::new(self.set.iter().map(|k| k.get_kid().to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||||
|
DbValueSetV2::JwsKeyEs256(self.set.iter()
|
||||||
|
.filter_map(|k| k.private_key_to_der()
|
||||||
|
.map_err(|e| {
|
||||||
|
error!(?e, "Unable to process private key to der, likely corrupted - this key will be LOST");
|
||||||
|
})
|
||||||
|
.ok())
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
|
||||||
|
Box::new(
|
||||||
|
self.set
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(|k| PartialValue::new_iutf8(k.get_kid())),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_value_iter(&self) -> Box<dyn Iterator<Item = Value> + '_> {
|
||||||
|
Box::new(self.set.iter().cloned().map(Value::JwsKeyEs256))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn equal(&self, other: &ValueSet) -> bool {
|
||||||
|
if let Some(other) = other.as_jws_key_es256_set() {
|
||||||
|
&self.set == other
|
||||||
|
} else {
|
||||||
|
debug_assert!(false);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn merge(&mut self, other: &ValueSet) -> Result<(), OperationError> {
|
||||||
|
if let Some(b) = other.as_jws_key_es256_set() {
|
||||||
|
mergesets!(self.set, b)
|
||||||
|
} else {
|
||||||
|
debug_assert!(false);
|
||||||
|
Err(OperationError::InvalidValueState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_jws_key_es256_single(&self) -> Option<&JwsSigner> {
|
||||||
|
if self.set.len() == 1 {
|
||||||
|
self.set.iter().take(1).next()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_jws_key_es256_set(&self) -> Option<&HashSet<JwsSigner>> {
|
||||||
|
Some(&self.set)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ValueSetJwsKeyRs256 {
|
||||||
|
set: HashSet<JwsSigner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ValueSetJwsKeyRs256 {
|
||||||
|
pub fn new(k: JwsSigner) -> Box<Self> {
|
||||||
|
debug_assert!(k.get_jwa_alg() == JwaAlg::RS256);
|
||||||
|
let mut set = HashSet::new();
|
||||||
|
set.insert(k);
|
||||||
|
Box::new(ValueSetJwsKeyRs256 { set })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(&mut self, k: JwsSigner) -> bool {
|
||||||
|
debug_assert!(k.get_jwa_alg() == JwaAlg::RS256);
|
||||||
|
self.set.insert(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_dbvs2(data: Vec<Vec<u8>>) -> Result<ValueSet, OperationError> {
|
||||||
|
let set = data
|
||||||
|
.iter()
|
||||||
|
.map(|b| {
|
||||||
|
JwsSigner::from_rs256_der(b).map_err(|e| {
|
||||||
|
debug!(?e, "Error occured parsing RS256 DER");
|
||||||
|
OperationError::InvalidValueState
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<HashSet<_>, _>>()?;
|
||||||
|
Ok(Box::new(ValueSetJwsKeyRs256 { set }))
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to allow this, because rust doesn't allow us to impl FromIterator on foreign
|
||||||
|
// types, and jwssigner is foreign
|
||||||
|
#[allow(clippy::should_implement_trait)]
|
||||||
|
pub fn from_iter<T>(iter: T) -> Option<Box<ValueSetJwsKeyRs256>>
|
||||||
|
where
|
||||||
|
T: IntoIterator<Item = JwsSigner>,
|
||||||
|
{
|
||||||
|
let set: HashSet<JwsSigner> = iter.into_iter().collect();
|
||||||
|
debug_assert!(set.iter().all(|k| k.get_jwa_alg() == JwaAlg::RS256));
|
||||||
|
Some(Box::new(ValueSetJwsKeyRs256 { set }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ValueSetT for ValueSetJwsKeyRs256 {
|
||||||
|
fn insert_checked(&mut self, value: Value) -> Result<bool, OperationError> {
|
||||||
|
match value {
|
||||||
|
Value::JwsKeyRs256(k) => Ok(self.set.insert(k)),
|
||||||
|
_ => {
|
||||||
|
debug_assert!(false);
|
||||||
|
Err(OperationError::InvalidValueState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear(&mut self) {
|
||||||
|
self.set.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&mut self, pv: &PartialValue) -> bool {
|
||||||
|
match pv {
|
||||||
|
PartialValue::Iutf8(kid) => {
|
||||||
|
let x = self.set.len();
|
||||||
|
self.set.retain(|k| k.get_kid() != kid);
|
||||||
|
x != self.set.len()
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains(&self, _pv: &PartialValue) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn substring(&self, _pv: &PartialValue) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lessthan(&self, _pv: &PartialValue) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
self.set.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_idx_eq_keys(&self) -> Vec<String> {
|
||||||
|
self.set.iter().map(|k| k.get_kid().to_string()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn syntax(&self) -> SyntaxType {
|
||||||
|
SyntaxType::JwsKeyRs256
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
|
||||||
|
self.set.iter().all(|k| k.get_jwa_alg() == JwaAlg::RS256)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_proto_string_clone_iter(&self) -> Box<dyn Iterator<Item = String> + '_> {
|
||||||
|
Box::new(self.set.iter().map(|k| k.get_kid().to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||||
|
DbValueSetV2::JwsKeyRs256(self.set.iter()
|
||||||
|
.filter_map(|k| k.private_key_to_der()
|
||||||
|
.map_err(|e| {
|
||||||
|
error!(?e, "Unable to process private key to der, likely corrupted - this key will be LOST");
|
||||||
|
})
|
||||||
|
.ok())
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
|
||||||
|
Box::new(
|
||||||
|
self.set
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(|k| PartialValue::new_iutf8(k.get_kid())),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_value_iter(&self) -> Box<dyn Iterator<Item = Value> + '_> {
|
||||||
|
Box::new(self.set.iter().cloned().map(Value::JwsKeyRs256))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn equal(&self, other: &ValueSet) -> bool {
|
||||||
|
if let Some(other) = other.as_jws_key_rs256_set() {
|
||||||
|
&self.set == other
|
||||||
|
} else {
|
||||||
|
debug_assert!(false);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn merge(&mut self, other: &ValueSet) -> Result<(), OperationError> {
|
||||||
|
if let Some(b) = other.as_jws_key_rs256_set() {
|
||||||
|
mergesets!(self.set, b)
|
||||||
|
} else {
|
||||||
|
debug_assert!(false);
|
||||||
|
Err(OperationError::InvalidValueState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_jws_key_rs256_single(&self) -> Option<&JwsSigner> {
|
||||||
|
if self.set.len() == 1 {
|
||||||
|
self.set.iter().take(1).next()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_jws_key_rs256_set(&self) -> Option<&HashSet<JwsSigner>> {
|
||||||
|
Some(&self.set)
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,12 +6,15 @@ use crate::schema::SchemaAttribute;
|
||||||
use crate::be::dbvalue::DbValueSetV2;
|
use crate::be::dbvalue::DbValueSetV2;
|
||||||
use crate::value::Address;
|
use crate::value::Address;
|
||||||
use crate::value::IntentTokenState;
|
use crate::value::IntentTokenState;
|
||||||
|
use crate::value::Session;
|
||||||
|
use compact_jwt::JwsSigner;
|
||||||
|
|
||||||
use kanidm_proto::v1::Filter as ProtoFilter;
|
use kanidm_proto::v1::Filter as ProtoFilter;
|
||||||
|
|
||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
|
||||||
use dyn_clone::DynClone;
|
use dyn_clone::DynClone;
|
||||||
|
use hashbrown::HashSet;
|
||||||
use smolset::SmolSet;
|
use smolset::SmolSet;
|
||||||
// use std::fmt::Debug;
|
// use std::fmt::Debug;
|
||||||
|
|
||||||
|
@ -30,10 +33,12 @@ mod iname;
|
||||||
mod index;
|
mod index;
|
||||||
mod iutf8;
|
mod iutf8;
|
||||||
mod json;
|
mod json;
|
||||||
|
mod jws;
|
||||||
mod nsuniqueid;
|
mod nsuniqueid;
|
||||||
mod oauth;
|
mod oauth;
|
||||||
mod restricted;
|
mod restricted;
|
||||||
mod secret;
|
mod secret;
|
||||||
|
mod session;
|
||||||
mod spn;
|
mod spn;
|
||||||
mod ssh;
|
mod ssh;
|
||||||
mod syntax;
|
mod syntax;
|
||||||
|
@ -52,10 +57,12 @@ pub use self::iname::ValueSetIname;
|
||||||
pub use self::index::ValueSetIndex;
|
pub use self::index::ValueSetIndex;
|
||||||
pub use self::iutf8::ValueSetIutf8;
|
pub use self::iutf8::ValueSetIutf8;
|
||||||
pub use self::json::ValueSetJsonFilter;
|
pub use self::json::ValueSetJsonFilter;
|
||||||
|
pub use self::jws::{ValueSetJwsKeyEs256, ValueSetJwsKeyRs256};
|
||||||
pub use self::nsuniqueid::ValueSetNsUniqueId;
|
pub use self::nsuniqueid::ValueSetNsUniqueId;
|
||||||
pub use self::oauth::{ValueSetOauthScope, ValueSetOauthScopeMap};
|
pub use self::oauth::{ValueSetOauthScope, ValueSetOauthScopeMap};
|
||||||
pub use self::restricted::ValueSetRestricted;
|
pub use self::restricted::ValueSetRestricted;
|
||||||
pub use self::secret::ValueSetSecret;
|
pub use self::secret::ValueSetSecret;
|
||||||
|
pub use self::session::ValueSetSession;
|
||||||
pub use self::spn::ValueSetSpn;
|
pub use self::spn::ValueSetSpn;
|
||||||
pub use self::ssh::ValueSetSshKey;
|
pub use self::ssh::ValueSetSshKey;
|
||||||
pub use self::syntax::ValueSetSyntax;
|
pub use self::syntax::ValueSetSyntax;
|
||||||
|
@ -465,6 +472,31 @@ pub trait ValueSetT: std::fmt::Debug + DynClone {
|
||||||
debug_assert!(false);
|
debug_assert!(false);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_session_map(&self) -> Option<&BTreeMap<Uuid, Session>> {
|
||||||
|
debug_assert!(false);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_jws_key_es256_single(&self) -> Option<&JwsSigner> {
|
||||||
|
debug_assert!(false);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_jws_key_es256_set(&self) -> Option<&HashSet<JwsSigner>> {
|
||||||
|
debug_assert!(false);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_jws_key_rs256_single(&self) -> Option<&JwsSigner> {
|
||||||
|
debug_assert!(false);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_jws_key_rs256_set(&self) -> Option<&HashSet<JwsSigner>> {
|
||||||
|
debug_assert!(false);
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for ValueSet {
|
impl PartialEq for ValueSet {
|
||||||
|
@ -516,7 +548,16 @@ pub fn from_result_value_iter(
|
||||||
Value::PublicBinary(t, b) => ValueSetPublicBinary::new(t, b),
|
Value::PublicBinary(t, b) => ValueSetPublicBinary::new(t, b),
|
||||||
Value::IntentToken(u, s) => ValueSetIntentToken::new(u, s),
|
Value::IntentToken(u, s) => ValueSetIntentToken::new(u, s),
|
||||||
Value::EmailAddress(a, _) => ValueSetEmailAddress::new(a),
|
Value::EmailAddress(a, _) => ValueSetEmailAddress::new(a),
|
||||||
_ => return Err(OperationError::InvalidValueState),
|
Value::PhoneNumber(_, _)
|
||||||
|
| Value::Passkey(_, _, _)
|
||||||
|
| Value::DeviceKey(_, _, _)
|
||||||
|
| Value::TrustedDeviceEnrollment(_)
|
||||||
|
| Value::Session(_, _)
|
||||||
|
| Value::JwsKeyEs256(_)
|
||||||
|
| Value::JwsKeyRs256(_) => {
|
||||||
|
debug_assert!(false);
|
||||||
|
return Err(OperationError::InvalidValueState);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for maybe_v in iter {
|
for maybe_v in iter {
|
||||||
|
@ -564,7 +605,13 @@ pub fn from_value_iter(mut iter: impl Iterator<Item = Value>) -> Result<ValueSet
|
||||||
Value::EmailAddress(a, _) => ValueSetEmailAddress::new(a),
|
Value::EmailAddress(a, _) => ValueSetEmailAddress::new(a),
|
||||||
Value::Passkey(u, t, k) => ValueSetPasskey::new(u, t, k),
|
Value::Passkey(u, t, k) => ValueSetPasskey::new(u, t, k),
|
||||||
Value::DeviceKey(u, t, k) => ValueSetDeviceKey::new(u, t, k),
|
Value::DeviceKey(u, t, k) => ValueSetDeviceKey::new(u, t, k),
|
||||||
_ => return Err(OperationError::InvalidValueState),
|
Value::JwsKeyEs256(k) => ValueSetJwsKeyEs256::new(k),
|
||||||
|
Value::JwsKeyRs256(k) => ValueSetJwsKeyRs256::new(k),
|
||||||
|
Value::Session(u, m) => ValueSetSession::new(u, m),
|
||||||
|
Value::PhoneNumber(_, _) | Value::TrustedDeviceEnrollment(_) => {
|
||||||
|
debug_assert!(false);
|
||||||
|
return Err(OperationError::InvalidValueState);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for v in iter {
|
for v in iter {
|
||||||
|
@ -603,11 +650,11 @@ pub fn from_db_valueset_v2(dbvs: DbValueSetV2) -> Result<ValueSet, OperationErro
|
||||||
DbValueSetV2::EmailAddress(primary, set) => ValueSetEmailAddress::from_dbvs2(primary, set),
|
DbValueSetV2::EmailAddress(primary, set) => ValueSetEmailAddress::from_dbvs2(primary, set),
|
||||||
DbValueSetV2::Passkey(set) => ValueSetPasskey::from_dbvs2(set),
|
DbValueSetV2::Passkey(set) => ValueSetPasskey::from_dbvs2(set),
|
||||||
DbValueSetV2::DeviceKey(set) => ValueSetDeviceKey::from_dbvs2(set),
|
DbValueSetV2::DeviceKey(set) => ValueSetDeviceKey::from_dbvs2(set),
|
||||||
/*
|
DbValueSetV2::Session(set) => ValueSetSession::from_dbvs2(set),
|
||||||
DbValueSetV2::PhoneNumber(set) =>
|
DbValueSetV2::JwsKeyEs256(set) => ValueSetJwsKeyEs256::from_dbvs2(set),
|
||||||
DbValueSetV2::TrustedDeviceEnrollment(set) =>
|
DbValueSetV2::JwsKeyRs256(set) => ValueSetJwsKeyEs256::from_dbvs2(set),
|
||||||
DbValueSetV2::AuthSession(set) =>
|
DbValueSetV2::PhoneNumber(_, _) | DbValueSetV2::TrustedDeviceEnrollment(_) => {
|
||||||
*/
|
unimplemented!()
|
||||||
_ => unimplemented!(),
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -214,14 +214,14 @@ impl ValueSetT for ValueSetOauthScopeMap {
|
||||||
|
|
||||||
fn remove(&mut self, pv: &PartialValue) -> bool {
|
fn remove(&mut self, pv: &PartialValue) -> bool {
|
||||||
match pv {
|
match pv {
|
||||||
PartialValue::OauthScopeMap(u) | PartialValue::Refer(u) => self.map.remove(u).is_some(),
|
PartialValue::Refer(u) => self.map.remove(u).is_some(),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn contains(&self, pv: &PartialValue) -> bool {
|
fn contains(&self, pv: &PartialValue) -> bool {
|
||||||
match pv {
|
match pv {
|
||||||
PartialValue::OauthScopeMap(u) | PartialValue::Refer(u) => self.map.contains_key(u),
|
PartialValue::Refer(u) => self.map.contains_key(u),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -277,7 +277,7 @@ impl ValueSetT for ValueSetOauthScopeMap {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
|
fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
|
||||||
Box::new(self.map.keys().cloned().map(PartialValue::OauthScopeMap))
|
Box::new(self.map.keys().cloned().map(PartialValue::Refer))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_value_iter(&self) -> Box<dyn Iterator<Item = Value> + '_> {
|
fn to_value_iter(&self) -> Box<dyn Iterator<Item = Value> + '_> {
|
||||||
|
|
236
kanidmd/idm/src/valueset/session.rs
Normal file
236
kanidmd/idm/src/valueset/session.rs
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
use crate::be::dbvalue::{DbValueIdentityId, DbValueSession};
|
||||||
|
use crate::identity::IdentityId;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::schema::SchemaAttribute;
|
||||||
|
use crate::value::Session;
|
||||||
|
use crate::valueset::DbValueSetV2;
|
||||||
|
use crate::valueset::ValueSet;
|
||||||
|
use std::collections::btree_map::Entry as BTreeEntry;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
|
use crate::valueset::uuid_to_proto_string;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ValueSetSession {
|
||||||
|
map: BTreeMap<Uuid, Session>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ValueSetSession {
|
||||||
|
pub fn new(u: Uuid, m: Session) -> Box<Self> {
|
||||||
|
let mut map = BTreeMap::new();
|
||||||
|
map.insert(u, m);
|
||||||
|
Box::new(ValueSetSession { map })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(&mut self, u: Uuid, m: Session) -> bool {
|
||||||
|
self.map.insert(u, m).is_none()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_dbvs2(data: Vec<DbValueSession>) -> Result<ValueSet, OperationError> {
|
||||||
|
let map = data
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|dbv| {
|
||||||
|
match dbv {
|
||||||
|
DbValueSession::V1 {
|
||||||
|
refer,
|
||||||
|
label,
|
||||||
|
expiry,
|
||||||
|
issued_at,
|
||||||
|
issued_by,
|
||||||
|
} => {
|
||||||
|
// Convert things.
|
||||||
|
let issued_at = OffsetDateTime::parse(issued_at, time::Format::Rfc3339)
|
||||||
|
.map(|odt| odt.to_offset(time::UtcOffset::UTC))
|
||||||
|
.map_err(|e| {
|
||||||
|
admin_error!(
|
||||||
|
?e,
|
||||||
|
"Invalidating session {} due to invalid issued_at timestamp",
|
||||||
|
refer
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
// This is a bit annoying. In the case we can't parse the optional
|
||||||
|
// expiry, we need to NOT return the session so that it's immediately
|
||||||
|
// invalidated. To do this we have to invert some of the options involved
|
||||||
|
// here.
|
||||||
|
let expiry = expiry
|
||||||
|
.map(|e_inner| {
|
||||||
|
OffsetDateTime::parse(e_inner, time::Format::Rfc3339)
|
||||||
|
.map(|odt| odt.to_offset(time::UtcOffset::UTC))
|
||||||
|
// We now have an
|
||||||
|
// Option<Result<ODT, _>>
|
||||||
|
})
|
||||||
|
.transpose()
|
||||||
|
// Result<Option<ODT>, _>
|
||||||
|
.map_err(|e| {
|
||||||
|
admin_error!(
|
||||||
|
?e,
|
||||||
|
"Invalidating session {} due to invalid expiry timestamp",
|
||||||
|
refer
|
||||||
|
)
|
||||||
|
})
|
||||||
|
// Option<Option<ODT>>
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
let issued_by = match issued_by {
|
||||||
|
DbValueIdentityId::V1Internal => IdentityId::Internal,
|
||||||
|
DbValueIdentityId::V1Uuid(u) => IdentityId::User(u),
|
||||||
|
};
|
||||||
|
|
||||||
|
Some((
|
||||||
|
refer,
|
||||||
|
Session {
|
||||||
|
label,
|
||||||
|
expiry,
|
||||||
|
issued_at,
|
||||||
|
issued_by,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Ok(Box::new(ValueSetSession { map }))
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to allow this, because rust doesn't allow us to impl FromIterator on foreign
|
||||||
|
// types, and tuples are always foreign.
|
||||||
|
#[allow(clippy::should_implement_trait)]
|
||||||
|
pub fn from_iter<T>(iter: T) -> Option<Box<Self>>
|
||||||
|
where
|
||||||
|
T: IntoIterator<Item = (Uuid, Session)>,
|
||||||
|
{
|
||||||
|
let map = iter.into_iter().collect();
|
||||||
|
Some(Box::new(ValueSetSession { map }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ValueSetT for ValueSetSession {
|
||||||
|
fn insert_checked(&mut self, value: Value) -> Result<bool, OperationError> {
|
||||||
|
match value {
|
||||||
|
Value::Session(u, m) => {
|
||||||
|
if let BTreeEntry::Vacant(e) = self.map.entry(u) {
|
||||||
|
e.insert(m);
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err(OperationError::InvalidValueState),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear(&mut self) {
|
||||||
|
self.map.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&mut self, pv: &PartialValue) -> bool {
|
||||||
|
match pv {
|
||||||
|
PartialValue::Refer(u) => self.map.remove(u).is_some(),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains(&self, pv: &PartialValue) -> bool {
|
||||||
|
match pv {
|
||||||
|
PartialValue::Refer(u) => self.map.contains_key(u),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn substring(&self, _pv: &PartialValue) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lessthan(&self, _pv: &PartialValue) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
self.map.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_idx_eq_keys(&self) -> Vec<String> {
|
||||||
|
self.map
|
||||||
|
.keys()
|
||||||
|
.map(|u| u.as_hyphenated().to_string())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn syntax(&self) -> SyntaxType {
|
||||||
|
SyntaxType::Session
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_proto_string_clone_iter(&self) -> Box<dyn Iterator<Item = String> + '_> {
|
||||||
|
Box::new(
|
||||||
|
self.map
|
||||||
|
.iter()
|
||||||
|
.map(|(u, m)| format!("{}: {:?}", uuid_to_proto_string(*u), m)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||||
|
DbValueSetV2::Session(
|
||||||
|
self.map
|
||||||
|
.iter()
|
||||||
|
.map(|(u, m)| DbValueSession::V1 {
|
||||||
|
refer: *u,
|
||||||
|
label: m.label.clone(),
|
||||||
|
expiry: m.expiry.map(|odt| {
|
||||||
|
debug_assert!(odt.offset() == time::UtcOffset::UTC);
|
||||||
|
odt.format(time::Format::Rfc3339)
|
||||||
|
}),
|
||||||
|
issued_at: {
|
||||||
|
debug_assert!(m.issued_at.offset() == time::UtcOffset::UTC);
|
||||||
|
m.issued_at.format(time::Format::Rfc3339)
|
||||||
|
},
|
||||||
|
issued_by: match m.issued_by {
|
||||||
|
IdentityId::Internal => DbValueIdentityId::V1Internal,
|
||||||
|
IdentityId::User(u) => DbValueIdentityId::V1Uuid(u),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
|
||||||
|
Box::new(self.map.keys().cloned().map(PartialValue::Refer))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_value_iter(&self) -> Box<dyn Iterator<Item = Value> + '_> {
|
||||||
|
Box::new(self.map.iter().map(|(u, m)| Value::Session(*u, m.clone())))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn equal(&self, other: &ValueSet) -> bool {
|
||||||
|
if let Some(other) = other.as_session_map() {
|
||||||
|
&self.map == other
|
||||||
|
} else {
|
||||||
|
debug_assert!(false);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn merge(&mut self, other: &ValueSet) -> Result<(), OperationError> {
|
||||||
|
if let Some(b) = other.as_session_map() {
|
||||||
|
mergemaps!(self.map, b)
|
||||||
|
} else {
|
||||||
|
debug_assert!(false);
|
||||||
|
Err(OperationError::InvalidValueState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_session_map(&self) -> Option<&BTreeMap<Uuid, Session>> {
|
||||||
|
Some(&self.map)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_ref_uuid_iter(&self) -> Option<Box<dyn Iterator<Item = Uuid> + '_>> {
|
||||||
|
// This is what ties us as a type that can be refint checked.
|
||||||
|
Some(Box::new(self.map.keys().copied()))
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,7 +20,7 @@ impl ValueSetSyntax {
|
||||||
self.set.insert(s)
|
self.set.insert(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_dbvs2(data: Vec<usize>) -> Result<ValueSet, OperationError> {
|
pub fn from_dbvs2(data: Vec<u16>) -> Result<ValueSet, OperationError> {
|
||||||
let set: Result<_, _> = data.into_iter().map(SyntaxType::try_from).collect();
|
let set: Result<_, _> = data.into_iter().map(SyntaxType::try_from).collect();
|
||||||
let set = set.map_err(|()| OperationError::InvalidValueState)?;
|
let set = set.map_err(|()| OperationError::InvalidValueState)?;
|
||||||
Ok(Box::new(ValueSetSyntax { set }))
|
Ok(Box::new(ValueSetSyntax { set }))
|
||||||
|
@ -86,7 +86,7 @@ impl ValueSetT for ValueSetSyntax {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn syntax(&self) -> SyntaxType {
|
fn syntax(&self) -> SyntaxType {
|
||||||
SyntaxType::SYNTAX_ID
|
SyntaxType::SyntaxId
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
|
fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
|
||||||
|
@ -98,7 +98,7 @@ impl ValueSetT for ValueSetSyntax {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
|
||||||
DbValueSetV2::SyntaxType(self.set.iter().map(|s| s.to_usize()).collect())
|
DbValueSetV2::SyntaxType(self.set.iter().map(|s| *s as u16).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
|
fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
|
||||||
|
|
|
@ -89,7 +89,7 @@ impl ValueSetT for ValueSetUint32 {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn syntax(&self) -> SyntaxType {
|
fn syntax(&self) -> SyntaxType {
|
||||||
SyntaxType::UINT32
|
SyntaxType::Uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
|
fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
|
||||||
|
|
|
@ -78,7 +78,7 @@ impl ValueSetT for ValueSetUtf8 {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn syntax(&self) -> SyntaxType {
|
fn syntax(&self) -> SyntaxType {
|
||||||
SyntaxType::UTF8STRING
|
SyntaxType::Utf8String
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
|
fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
|
||||||
|
|
|
@ -241,7 +241,7 @@ impl ValueSetT for ValueSetRefer {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn syntax(&self) -> SyntaxType {
|
fn syntax(&self) -> SyntaxType {
|
||||||
SyntaxType::REFERENCE_UUID
|
SyntaxType::ReferenceUuid
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
|
fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
|
||||||
|
|
|
@ -44,7 +44,7 @@ profiles = { path = "../../profiles" }
|
||||||
kanidm_client = { path = "../../kanidm_client" }
|
kanidm_client = { path = "../../kanidm_client" }
|
||||||
futures = "^0.3.21"
|
futures = "^0.3.21"
|
||||||
|
|
||||||
webauthn-authenticator-rs = "0.4.5"
|
webauthn-authenticator-rs.workspace = true
|
||||||
oauth2_ext = { package = "oauth2", version = "^4.1.0", default-features = false }
|
oauth2_ext = { package = "oauth2", version = "^4.1.0", default-features = false }
|
||||||
base64 = "^0.13.0"
|
base64 = "^0.13.0"
|
||||||
|
|
||||||
|
|
|
@ -73,6 +73,8 @@ pub trait RequestExtensions {
|
||||||
|
|
||||||
fn get_url_param(&self, param: &str) -> Result<String, tide::Error>;
|
fn get_url_param(&self, param: &str) -> Result<String, tide::Error>;
|
||||||
|
|
||||||
|
fn get_url_param_uuid(&self, param: &str) -> Result<Uuid, tide::Error>;
|
||||||
|
|
||||||
fn new_eventid(&self) -> (Uuid, String);
|
fn new_eventid(&self) -> (Uuid, String);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,16 +94,6 @@ impl RequestExtensions for tide::Request<AppState> {
|
||||||
})
|
})
|
||||||
.map(|s| s.to_string())
|
.map(|s| s.to_string())
|
||||||
.or_else(|| self.session().get::<String>("bearer"))
|
.or_else(|| self.session().get::<String>("bearer"))
|
||||||
/*
|
|
||||||
.and_then(|ts| {
|
|
||||||
// Take the token str and attempt to decrypt
|
|
||||||
// Attempt to re-inflate a UAT from bytes.
|
|
||||||
//
|
|
||||||
// NOTE: UAT expiry validation is performed in event.rs!
|
|
||||||
let uat: Option<UserAuthToken> = kref.verify(ts).ok();
|
|
||||||
uat
|
|
||||||
})
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_current_auth_session_id(&self) -> Option<Uuid> {
|
fn get_current_auth_session_id(&self) -> Option<Uuid> {
|
||||||
|
@ -119,7 +111,7 @@ impl RequestExtensions for tide::Request<AppState> {
|
||||||
})
|
})
|
||||||
.and_then(|jwsu| {
|
.and_then(|jwsu| {
|
||||||
jwsu.validate(kref)
|
jwsu.validate(kref)
|
||||||
.map(|jws: Jws<SessionId>| jws.inner.sessionid)
|
.map(|jws: Jws<SessionId>| jws.into_inner().sessionid)
|
||||||
.ok()
|
.ok()
|
||||||
})
|
})
|
||||||
// If not there, get from the cookie instead.
|
// If not there, get from the cookie instead.
|
||||||
|
@ -133,6 +125,20 @@ impl RequestExtensions for tide::Request<AppState> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_url_param_uuid(&self, param: &str) -> Result<Uuid, tide::Error> {
|
||||||
|
self.param(param)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!(?e);
|
||||||
|
tide::Error::from_str(tide::StatusCode::ImATeapot, "teapot")
|
||||||
|
})
|
||||||
|
.and_then(|s| {
|
||||||
|
Uuid::try_parse(s).map_err(|e| {
|
||||||
|
error!(?e);
|
||||||
|
tide::Error::from_str(tide::StatusCode::ImATeapot, "teapot")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn new_eventid(&self) -> (Uuid, String) {
|
fn new_eventid(&self) -> (Uuid, String) {
|
||||||
let eventid = sketching::tracing_forest::id();
|
let eventid = sketching::tracing_forest::id();
|
||||||
let hv = eventid.as_hyphenated().to_string();
|
let hv = eventid.as_hyphenated().to_string();
|
||||||
|
@ -686,6 +692,14 @@ pub fn create_https_server(
|
||||||
.at("/:id/_into_person")
|
.at("/:id/_into_person")
|
||||||
.mapped_post(&mut routemap, service_account_into_person);
|
.mapped_post(&mut routemap, service_account_into_person);
|
||||||
|
|
||||||
|
service_account_route
|
||||||
|
.at("/:id/_api_token")
|
||||||
|
.mapped_post(&mut routemap, service_account_api_token_post)
|
||||||
|
.mapped_get(&mut routemap, service_account_api_token_get);
|
||||||
|
service_account_route
|
||||||
|
.at("/:id/_api_token/:token_id")
|
||||||
|
.mapped_delete(&mut routemap, service_account_api_token_delete);
|
||||||
|
|
||||||
service_account_route
|
service_account_route
|
||||||
.at("/:id/_credential")
|
.at("/:id/_credential")
|
||||||
.mapped_get(&mut routemap, do_nothing);
|
.mapped_get(&mut routemap, do_nothing);
|
||||||
|
|
|
@ -6,9 +6,9 @@ use kanidm::status::StatusRequestEvent;
|
||||||
|
|
||||||
use kanidm_proto::v1::Entry as ProtoEntry;
|
use kanidm_proto::v1::Entry as ProtoEntry;
|
||||||
use kanidm_proto::v1::{
|
use kanidm_proto::v1::{
|
||||||
AccountUnixExtend, AuthRequest, AuthResponse, AuthState as ProtoAuthState, CUIntentToken,
|
AccountUnixExtend, ApiTokenGenerate, AuthRequest, AuthResponse, AuthState as ProtoAuthState,
|
||||||
CURequest, CUSessionToken, CreateRequest, DeleteRequest, GroupUnixExtend, ModifyRequest,
|
CUIntentToken, CURequest, CUSessionToken, CreateRequest, DeleteRequest, GroupUnixExtend,
|
||||||
OperationError, SearchRequest, SingleStringRequest,
|
ModifyRequest, OperationError, SearchRequest, SingleStringRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{to_tide_response, AppState, RequestExtensions, RouteMap};
|
use super::{to_tide_response, AppState, RequestExtensions, RouteMap};
|
||||||
|
@ -420,6 +420,52 @@ pub async fn service_account_into_person(req: tide::Request<AppState>) -> tide::
|
||||||
to_tide_response(res, hvalue)
|
to_tide_response(res, hvalue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Api Token
|
||||||
|
pub async fn service_account_api_token_get(req: tide::Request<AppState>) -> tide::Result {
|
||||||
|
let uat = req.get_current_uat();
|
||||||
|
let uuid_or_name = req.get_url_param("id")?;
|
||||||
|
|
||||||
|
let (eventid, hvalue) = req.new_eventid();
|
||||||
|
|
||||||
|
let res = req
|
||||||
|
.state()
|
||||||
|
.qe_r_ref
|
||||||
|
.handle_service_account_api_token_get(uat, uuid_or_name, eventid)
|
||||||
|
.await;
|
||||||
|
to_tide_response(res, hvalue)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn service_account_api_token_post(mut req: tide::Request<AppState>) -> tide::Result {
|
||||||
|
let uat = req.get_current_uat();
|
||||||
|
let uuid_or_name = req.get_url_param("id")?;
|
||||||
|
let ApiTokenGenerate { label, expiry } = req.body_json().await?;
|
||||||
|
|
||||||
|
let (eventid, hvalue) = req.new_eventid();
|
||||||
|
|
||||||
|
let res = req
|
||||||
|
.state()
|
||||||
|
.qe_w_ref
|
||||||
|
.handle_service_account_api_token_generate(uat, uuid_or_name, label, expiry, eventid)
|
||||||
|
.await;
|
||||||
|
to_tide_response(res, hvalue)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn service_account_api_token_delete(req: tide::Request<AppState>) -> tide::Result {
|
||||||
|
let uat = req.get_current_uat();
|
||||||
|
let uuid_or_name = req.get_url_param("id")?;
|
||||||
|
let token_id = req.get_url_param_uuid("token_id")?;
|
||||||
|
|
||||||
|
let (eventid, hvalue) = req.new_eventid();
|
||||||
|
|
||||||
|
let res = req
|
||||||
|
.state()
|
||||||
|
.qe_w_ref
|
||||||
|
.handle_service_account_api_token_destroy(uat, uuid_or_name, token_id, eventid)
|
||||||
|
.await;
|
||||||
|
to_tide_response(res, hvalue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Account stuff
|
||||||
pub async fn account_id_get_attr(req: tide::Request<AppState>) -> tide::Result {
|
pub async fn account_id_get_attr(req: tide::Request<AppState>) -> tide::Result {
|
||||||
let filter = filter_all!(f_eq("class", PartialValue::new_class("account")));
|
let filter = filter_all!(f_eq("class", PartialValue::new_class("account")));
|
||||||
json_rest_event_get_id_attr(req, filter).await
|
json_rest_event_get_id_attr(req, filter).await
|
||||||
|
@ -960,9 +1006,7 @@ pub async fn auth(mut req: tide::Request<AppState>) -> tide::Result {
|
||||||
.and_then(|_| {
|
.and_then(|_| {
|
||||||
let kref = &req.state().jws_signer;
|
let kref = &req.state().jws_signer;
|
||||||
|
|
||||||
let jws = Jws {
|
let jws = Jws::new(SessionId { sessionid });
|
||||||
inner: SessionId { sessionid },
|
|
||||||
};
|
|
||||||
// Get the header token ready.
|
// Get the header token ready.
|
||||||
jws.sign(&kref)
|
jws.sign(&kref)
|
||||||
.map(|jwss| {
|
.map(|jwss| {
|
||||||
|
@ -989,9 +1033,7 @@ pub async fn auth(mut req: tide::Request<AppState>) -> tide::Result {
|
||||||
.and_then(|_| {
|
.and_then(|_| {
|
||||||
let kref = &req.state().jws_signer;
|
let kref = &req.state().jws_signer;
|
||||||
// Get the header token ready.
|
// Get the header token ready.
|
||||||
let jws = Jws {
|
let jws = Jws::new(SessionId { sessionid });
|
||||||
inner: SessionId { sessionid },
|
|
||||||
};
|
|
||||||
jws.sign(&kref)
|
jws.sign(&kref)
|
||||||
.map(|jwss| {
|
.map(|jwss| {
|
||||||
auth_session_id_tok = Some(jwss.to_string());
|
auth_session_id_tok = Some(jwss.to_string());
|
||||||
|
|
|
@ -4,10 +4,14 @@ use std::time::SystemTime;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use kanidm::credential::totp::Totp;
|
use kanidm::credential::totp::Totp;
|
||||||
use kanidm_proto::v1::{CURegState, CredentialDetailType, Entry, Filter, Modify, ModifyList};
|
use kanidm_proto::v1::{
|
||||||
|
ApiToken, CURegState, CredentialDetailType, Entry, Filter, Modify, ModifyList,
|
||||||
|
};
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
use crate::common::{setup_async_test, ADMIN_TEST_PASSWORD};
|
use crate::common::{setup_async_test, ADMIN_TEST_PASSWORD};
|
||||||
|
use compact_jwt::JwsUnverified;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use webauthn_authenticator_rs::{softpasskey::SoftPasskey, WebauthnAuthenticator};
|
use webauthn_authenticator_rs::{softpasskey::SoftPasskey, WebauthnAuthenticator};
|
||||||
|
|
||||||
|
@ -78,12 +82,13 @@ async fn test_server_whoami_anonymous() {
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
|
|
||||||
// Now do a whoami.
|
// Now do a whoami.
|
||||||
let (_e, uat) = match rsclient.whoami().await.unwrap() {
|
let e = rsclient
|
||||||
Some((e, uat)) => (e, uat),
|
.whoami()
|
||||||
None => panic!(),
|
.await
|
||||||
};
|
.expect("Unable to call whoami")
|
||||||
debug!("{}", uat);
|
.expect("No entry matching self returned");
|
||||||
assert!(uat.spn == "anonymous@localhost");
|
debug!(?e);
|
||||||
|
assert!(e.attrs.get("spn") == Some(&vec!["anonymous@localhost".to_string()]));
|
||||||
|
|
||||||
// Do a check of the auth/valid endpoint, tells us if our token
|
// Do a check of the auth/valid endpoint, tells us if our token
|
||||||
// is okay.
|
// is okay.
|
||||||
|
@ -105,12 +110,13 @@ async fn test_server_whoami_admin_simple_password() {
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
|
|
||||||
// Now do a whoami.
|
// Now do a whoami.
|
||||||
let (_e, uat) = match rsclient.whoami().await.unwrap() {
|
let e = rsclient
|
||||||
Some((e, uat)) => (e, uat),
|
.whoami()
|
||||||
None => panic!(),
|
.await
|
||||||
};
|
.expect("Unable to call whoami")
|
||||||
debug!("{}", uat);
|
.expect("No entry matching self returned");
|
||||||
assert!(uat.spn == "admin@localhost");
|
debug!(?e);
|
||||||
|
assert!(e.attrs.get("spn") == Some(&vec!["admin@localhost".to_string()]));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -1164,3 +1170,62 @@ async fn test_server_credential_update_session_passkey() {
|
||||||
let res = rsclient.auth_passkey_complete(pkc).await;
|
let res = rsclient.auth_passkey_complete(pkc).await;
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_server_api_token_lifecycle() {
|
||||||
|
let rsclient = setup_async_test().await;
|
||||||
|
let res = rsclient
|
||||||
|
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||||
|
.await;
|
||||||
|
assert!(res.is_ok());
|
||||||
|
|
||||||
|
// Not recommended in production!
|
||||||
|
rsclient
|
||||||
|
.idm_group_add_members("idm_admins", &["admin"])
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
rsclient
|
||||||
|
.idm_service_account_create("test_service", "Test Service")
|
||||||
|
.await
|
||||||
|
.expect("Failed to create service account");
|
||||||
|
|
||||||
|
let tokens = rsclient
|
||||||
|
.idm_service_account_list_api_token("test_service")
|
||||||
|
.await
|
||||||
|
.expect("Failed to list service account api tokens");
|
||||||
|
assert!(tokens.is_empty());
|
||||||
|
|
||||||
|
let token = rsclient
|
||||||
|
.idm_service_account_generate_api_token("test_service", "test token", None)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create service account api token");
|
||||||
|
|
||||||
|
// Decode it?
|
||||||
|
let token_unverified = JwsUnverified::from_str(&token).expect("Failed to parse apitoken");
|
||||||
|
|
||||||
|
let token: ApiToken = token_unverified
|
||||||
|
.validate_embeded()
|
||||||
|
.map(|j| j.into_inner())
|
||||||
|
.expect("Embedded jwk not found");
|
||||||
|
|
||||||
|
let tokens = rsclient
|
||||||
|
.idm_service_account_list_api_token("test_service")
|
||||||
|
.await
|
||||||
|
.expect("Failed to list service account api tokens");
|
||||||
|
|
||||||
|
assert!(tokens == vec![token.clone()]);
|
||||||
|
|
||||||
|
rsclient
|
||||||
|
.idm_service_account_destroy_api_token(&token.account_id.to_string(), token.token_id)
|
||||||
|
.await
|
||||||
|
.expect("Failed to destroy service account api token");
|
||||||
|
|
||||||
|
let tokens = rsclient
|
||||||
|
.idm_service_account_list_api_token("test_service")
|
||||||
|
.await
|
||||||
|
.expect("Failed to list service account api tokens");
|
||||||
|
assert!(tokens.is_empty());
|
||||||
|
|
||||||
|
// No need to test expiry, that's validated in the server internal tests.
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ authors = [
|
||||||
"William Brown <william@blackhats.net.au>",
|
"William Brown <william@blackhats.net.au>",
|
||||||
"James Hodgkinson <james@terminaloutcomes.com>",
|
"James Hodgkinson <james@terminaloutcomes.com>",
|
||||||
]
|
]
|
||||||
rust-version = "1.59"
|
rust-version = "1.64"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
description = "Kanidm Server Web User Interface"
|
description = "Kanidm Server Web User Interface"
|
||||||
|
@ -33,7 +33,7 @@ serde = { version = "^1.0.142", features = ["derive"] }
|
||||||
serde_json = "^1.0.83"
|
serde_json = "^1.0.83"
|
||||||
serde-wasm-bindgen = "0.4"
|
serde-wasm-bindgen = "0.4"
|
||||||
uuid = "^1.1.2"
|
uuid = "^1.1.2"
|
||||||
wasm-bindgen = { version = "^0.2.81", features = ["serde-serialize"] }
|
wasm-bindgen = { version = "^0.2.81" }
|
||||||
wasm-bindgen-futures = { version = "^0.4.30" }
|
wasm-bindgen-futures = { version = "^0.4.30" }
|
||||||
wasm-bindgen-test = "0.3.33"
|
wasm-bindgen-test = "0.3.33"
|
||||||
yew = "^0.19.3"
|
yew = "^0.19.3"
|
||||||
|
|
File diff suppressed because it is too large
Load diff
Binary file not shown.
|
@ -14,9 +14,11 @@
|
||||||
"files": [
|
"files": [
|
||||||
"kanidmd_web_ui_bg.wasm",
|
"kanidmd_web_ui_bg.wasm",
|
||||||
"kanidmd_web_ui.js",
|
"kanidmd_web_ui.js",
|
||||||
|
"kanidmd_web_ui.d.ts",
|
||||||
"LICENSE.md"
|
"LICENSE.md"
|
||||||
],
|
],
|
||||||
"module": "kanidmd_web_ui.js",
|
"module": "kanidmd_web_ui.js",
|
||||||
"homepage": "https://github.com/kanidm/kanidm/",
|
"homepage": "https://github.com/kanidm/kanidm/",
|
||||||
|
"types": "kanidmd_web_ui.d.ts",
|
||||||
"sideEffects": false
|
"sideEffects": false
|
||||||
}
|
}
|
|
@ -262,9 +262,9 @@ impl ChangeUnixPassword {
|
||||||
|
|
||||||
let uat: Jws<UserAuthToken> = jwtu
|
let uat: Jws<UserAuthToken> = jwtu
|
||||||
.unsafe_release_without_verification()
|
.unsafe_release_without_verification()
|
||||||
.expect_throw("Unvalid UAT, unable to release ");
|
.expect_throw("Invalid UAT, unable to release ");
|
||||||
|
|
||||||
let id = uat.inner.uuid.to_string();
|
let id = uat.into_inner().uuid.to_string();
|
||||||
let changereq_jsvalue = serde_json::to_string(&SingleStringRequest {
|
let changereq_jsvalue = serde_json::to_string(&SingleStringRequest {
|
||||||
value: new_password,
|
value: new_password,
|
||||||
})
|
})
|
||||||
|
|
|
@ -100,7 +100,9 @@ impl LoginApp {
|
||||||
|
|
||||||
let window = utils::window();
|
let window = utils::window();
|
||||||
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
|
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
|
||||||
let resp: Response = resp_value.dyn_into().expect_throw("Invalid response type");
|
let resp: Response = resp_value
|
||||||
|
.dyn_into()
|
||||||
|
.expect_throw("Invalid response type - auth_init::Response");
|
||||||
let status = resp.status();
|
let status = resp.status();
|
||||||
let headers = resp.headers();
|
let headers = resp.headers();
|
||||||
|
|
||||||
|
@ -111,8 +113,8 @@ impl LoginApp {
|
||||||
.flatten()
|
.flatten()
|
||||||
.unwrap_or_else(|| "".to_string());
|
.unwrap_or_else(|| "".to_string());
|
||||||
let jsval = JsFuture::from(resp.json()?).await?;
|
let jsval = JsFuture::from(resp.json()?).await?;
|
||||||
let state: AuthResponse =
|
let state: AuthResponse = serde_wasm_bindgen::from_value(jsval)
|
||||||
serde_wasm_bindgen::from_value(jsval).expect_throw("Invalid response type");
|
.expect_throw("Invalid response type - auth_init::AuthResponse");
|
||||||
Ok(LoginAppMsg::Start(session_id, state))
|
Ok(LoginAppMsg::Start(session_id, state))
|
||||||
} else if status == 404 {
|
} else if status == 404 {
|
||||||
let kopid = headers.get("x-kanidm-opid").ok().flatten();
|
let kopid = headers.get("x-kanidm-opid").ok().flatten();
|
||||||
|
@ -156,14 +158,20 @@ impl LoginApp {
|
||||||
|
|
||||||
let window = utils::window();
|
let window = utils::window();
|
||||||
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
|
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
|
||||||
let resp: Response = resp_value.dyn_into().expect_throw("Invalid response type");
|
let resp: Response = resp_value
|
||||||
|
.dyn_into()
|
||||||
|
.expect_throw("Invalid response type - auth_step::Response");
|
||||||
let status = resp.status();
|
let status = resp.status();
|
||||||
let headers = resp.headers();
|
let headers = resp.headers();
|
||||||
|
|
||||||
if status == 200 {
|
if status == 200 {
|
||||||
let jsval = JsFuture::from(resp.json()?).await?;
|
let jsval = JsFuture::from(resp.json()?).await?;
|
||||||
let state: AuthResponse =
|
let state: AuthResponse = serde_wasm_bindgen::from_value(jsval)
|
||||||
serde_wasm_bindgen::from_value(jsval).expect_throw("Invalid response type.");
|
.map_err(|e| {
|
||||||
|
console::error!(format!("auth_step::AuthResponse: {:?}", e));
|
||||||
|
e
|
||||||
|
})
|
||||||
|
.expect_throw("Invalid response type - auth_step::AuthResponse");
|
||||||
Ok(LoginAppMsg::Next(state))
|
Ok(LoginAppMsg::Next(state))
|
||||||
} else {
|
} else {
|
||||||
let kopid = headers.get("x-kanidm-opid").ok().flatten();
|
let kopid = headers.get("x-kanidm-opid").ok().flatten();
|
||||||
|
|
|
@ -3,9 +3,10 @@ use crate::error::*;
|
||||||
use crate::manager::Route;
|
use crate::manager::Route;
|
||||||
use crate::models;
|
use crate::models;
|
||||||
use crate::utils;
|
use crate::utils;
|
||||||
use gloo::console;
|
use compact_jwt::{Jws, JwsUnverified};
|
||||||
use kanidm_proto::v1::WhoamiResponse;
|
use kanidm_proto::v1::UserAuthToken;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::str::FromStr;
|
||||||
use wasm_bindgen::{JsCast, UnwrapThrowExt};
|
use wasm_bindgen::{JsCast, UnwrapThrowExt};
|
||||||
use wasm_bindgen_futures::JsFuture;
|
use wasm_bindgen_futures::JsFuture;
|
||||||
use web_sys::{Request, RequestInit, RequestMode, Response};
|
use web_sys::{Request, RequestInit, RequestMode, Response};
|
||||||
|
@ -76,18 +77,20 @@ enum State {
|
||||||
#[derive(PartialEq, Eq, Properties)]
|
#[derive(PartialEq, Eq, Properties)]
|
||||||
pub struct ViewProps {
|
pub struct ViewProps {
|
||||||
pub token: String,
|
pub token: String,
|
||||||
pub current_user: Option<WhoamiResponse>,
|
// pub current_user_entry: Option<Entry>,
|
||||||
|
pub current_user_uat: Option<UserAuthToken>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ViewsApp {
|
pub struct ViewsApp {
|
||||||
state: State,
|
state: State,
|
||||||
current_user: Option<WhoamiResponse>,
|
// pub current_user_entry: Option<Entry>,
|
||||||
|
pub current_user_uat: Option<UserAuthToken>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ViewsMsg {
|
pub enum ViewsMsg {
|
||||||
Verified(String),
|
Verified(String),
|
||||||
Logout,
|
Logout,
|
||||||
ProfileInfoRecieved(WhoamiResponse),
|
ProfileInfoRecieved { uat: UserAuthToken },
|
||||||
Error { emsg: String, kopid: Option<String> },
|
Error { emsg: String, kopid: Option<String> },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,7 +131,7 @@ impl Component for ViewsApp {
|
||||||
|
|
||||||
ViewsApp {
|
ViewsApp {
|
||||||
state,
|
state,
|
||||||
current_user: None,
|
current_user_uat: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,8 +162,8 @@ impl Component for ViewsApp {
|
||||||
self.state = State::LoginRequired;
|
self.state = State::LoginRequired;
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
ViewsMsg::ProfileInfoRecieved(profile) => {
|
ViewsMsg::ProfileInfoRecieved { uat } => {
|
||||||
self.current_user = Some(profile);
|
self.current_user_uat = Some(uat);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
ViewsMsg::Error { emsg, kopid } => {
|
ViewsMsg::Error { emsg, kopid } => {
|
||||||
|
@ -234,7 +237,7 @@ impl Component for ViewsApp {
|
||||||
impl ViewsApp {
|
impl ViewsApp {
|
||||||
/// The base page for the user dashboard
|
/// The base page for the user dashboard
|
||||||
fn view_authenticated(&self, ctx: &Context<Self>) -> Html {
|
fn view_authenticated(&self, ctx: &Context<Self>) -> Html {
|
||||||
let current_user = self.current_user.clone();
|
let current_user_uat = self.current_user_uat.clone();
|
||||||
|
|
||||||
// WARN set dash-body against body here?
|
// WARN set dash-body against body here?
|
||||||
html! {
|
html! {
|
||||||
|
@ -325,8 +328,8 @@ impl ViewsApp {
|
||||||
<Switch<AdminRoute> render={ Switch::render(admin_routes) } />
|
<Switch<AdminRoute> render={ Switch::render(admin_routes) } />
|
||||||
},
|
},
|
||||||
ViewRoute::Apps => html! { <AppsApp /> },
|
ViewRoute::Apps => html! { <AppsApp /> },
|
||||||
ViewRoute::Profile => html! { <ProfileApp token={ token } current_user={ current_user.clone() } /> },
|
ViewRoute::Profile => html! { <ProfileApp token={ token } current_user_uat={ current_user_uat.clone() } /> },
|
||||||
ViewRoute::Security => html! { <SecurityApp token={ token } current_user={ current_user.clone() } /> },
|
ViewRoute::Security => html! { <SecurityApp token={ token } current_user_uat={ current_user_uat.clone() } /> },
|
||||||
ViewRoute::NotFound => html! {
|
ViewRoute::NotFound => html! {
|
||||||
<Redirect<Route> to={Route::NotFound}/>
|
<Redirect<Route> to={Route::NotFound}/>
|
||||||
},
|
},
|
||||||
|
@ -370,42 +373,20 @@ impl ViewsApp {
|
||||||
Ok(ViewsMsg::Error { emsg, kopid })
|
Ok(ViewsMsg::Error { emsg, kopid })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_user_data(token: String) -> Result<ViewsMsg, FetchError> {
|
async fn fetch_user_data(token: String) -> Result<ViewsMsg, FetchError> {
|
||||||
let mut opts = RequestInit::new();
|
let jwtu = JwsUnverified::from_str(&token).expect_throw("Invalid UAT, unable to parse");
|
||||||
opts.method("GET");
|
|
||||||
opts.mode(RequestMode::SameOrigin);
|
|
||||||
|
|
||||||
let request = Request::new_with_str_and_init("/v1/self", &opts)?;
|
let uat: Jws<UserAuthToken> = jwtu
|
||||||
request
|
.unsafe_release_without_verification()
|
||||||
.headers()
|
.expect_throw("Invalid UAT, unable to release");
|
||||||
.set("content-type", "application/json")
|
|
||||||
.expect_throw("failed to set header");
|
|
||||||
request
|
|
||||||
.headers()
|
|
||||||
.set("authorization", format!("Bearer {}", token).as_str())
|
|
||||||
.expect_throw("failed to set header");
|
|
||||||
|
|
||||||
let window = utils::window();
|
// We could get rid of this since the token is all we need?
|
||||||
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
|
//
|
||||||
let resp: Response = resp_value.dyn_into().expect_throw("Invalid response type");
|
// How will we manage this on changes?
|
||||||
let status = resp.status();
|
Ok(ViewsMsg::ProfileInfoRecieved {
|
||||||
let headers = resp.headers();
|
uat: uat.into_inner(),
|
||||||
let kopid = headers.get("x-kanidm-opid").ok().flatten();
|
})
|
||||||
|
|
||||||
if status == 200 {
|
|
||||||
let jsval = JsFuture::from(resp.json()?).await?;
|
|
||||||
let whoamiresponse: WhoamiResponse = serde_wasm_bindgen::from_value(jsval)
|
|
||||||
.map_err(|e| {
|
|
||||||
let e_msg = format!("serde error getting user data -> {:?}", e);
|
|
||||||
console::error!(e_msg.as_str());
|
|
||||||
})
|
|
||||||
.expect_throw("Invalid response type");
|
|
||||||
Ok(ViewsMsg::ProfileInfoRecieved(whoamiresponse))
|
|
||||||
} else {
|
|
||||||
let text = JsFuture::from(resp.text()?).await?;
|
|
||||||
let emsg = text.as_string().unwrap_or_else(|| "".to_string());
|
|
||||||
Ok(ViewsMsg::Error { emsg, kopid })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ impl Component for ProfileApp {
|
||||||
fn changed(&mut self, ctx: &Context<Self>) -> bool {
|
fn changed(&mut self, ctx: &Context<Self>) -> bool {
|
||||||
console::debug!(format!(
|
console::debug!(format!(
|
||||||
"views::profile::changed current_user: {:?}",
|
"views::profile::changed current_user: {:?}",
|
||||||
ctx.props().current_user,
|
ctx.props().current_user_uat,
|
||||||
));
|
));
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ impl Component for ProfileApp {
|
||||||
fn update(&mut self, ctx: &Context<Self>, _msg: Self::Message) -> bool {
|
fn update(&mut self, ctx: &Context<Self>, _msg: Self::Message) -> bool {
|
||||||
console::debug!(format!(
|
console::debug!(format!(
|
||||||
"views::profile::update current_user: {:?}",
|
"views::profile::update current_user: {:?}",
|
||||||
ctx.props().current_user,
|
ctx.props().current_user_uat,
|
||||||
));
|
));
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ impl Component for ProfileApp {
|
||||||
|
|
||||||
/// UI view for the user profile
|
/// UI view for the user profile
|
||||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
let pagecontent = match &ctx.props().current_user {
|
let pagecontent = match &ctx.props().current_user_uat {
|
||||||
None => {
|
None => {
|
||||||
html! {
|
html! {
|
||||||
<h2>
|
<h2>
|
||||||
|
@ -50,8 +50,8 @@ impl Component for ProfileApp {
|
||||||
</h2>
|
</h2>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(userinfo) => {
|
Some(uat) => {
|
||||||
let mail_primary = match userinfo.uat.mail_primary.as_ref() {
|
let mail_primary = match uat.mail_primary.as_ref() {
|
||||||
Some(email_address) => {
|
Some(email_address) => {
|
||||||
html! {
|
html! {
|
||||||
<a href={ format!("mailto:{}", &email_address)}>
|
<a href={ format!("mailto:{}", &email_address)}>
|
||||||
|
@ -62,12 +62,19 @@ impl Component for ProfileApp {
|
||||||
None => html! { {"<primary email is unset>"}},
|
None => html! { {"<primary email is unset>"}},
|
||||||
};
|
};
|
||||||
|
|
||||||
let spn = &userinfo.uat.spn.to_owned();
|
let spn = &uat.spn.to_owned();
|
||||||
let spn_split = spn.split('@');
|
let spn_split = spn.split('@');
|
||||||
let username = &spn_split.clone().next().unwrap_throw();
|
let username = &spn_split.clone().next().unwrap_throw();
|
||||||
let domain = &spn_split.clone().last().unwrap_throw();
|
let domain = &spn_split.clone().last().unwrap_throw();
|
||||||
let display_name = userinfo.uat.displayname.to_owned();
|
let display_name = uat.displayname.to_owned();
|
||||||
let user_groups = userinfo.youare.attrs.get("memberof");
|
let user_groups: Vec<String> = uat
|
||||||
|
.groups
|
||||||
|
.iter()
|
||||||
|
.map(|group| {
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
group.spn.split('@').next().unwrap().to_string()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<dl class="row">
|
<dl class="row">
|
||||||
|
@ -81,22 +88,18 @@ impl Component for ProfileApp {
|
||||||
<dd class="col">
|
<dd class="col">
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
{
|
{
|
||||||
match user_groups {
|
if user_groups.is_empty() {
|
||||||
Some(grouplist) => html!{
|
html!{
|
||||||
{
|
<li>{"Not a member of any groups"}</li>
|
||||||
for grouplist.iter()
|
}
|
||||||
.map(|group|
|
} else {
|
||||||
{
|
html!{
|
||||||
html!{ <li>{
|
{
|
||||||
#[allow(clippy::unwrap_used)]
|
for user_groups.iter()
|
||||||
group.split('@').next().unwrap().to_string()
|
.map(|group|
|
||||||
}</li> }
|
html!{ <li>{ group }</li> }
|
||||||
|
)
|
||||||
})
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
None => html!{
|
|
||||||
<li>{"Not a member of any groups"}</li>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,7 +118,7 @@ impl Component for ProfileApp {
|
||||||
{ "User's UUID" }
|
{ "User's UUID" }
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="col">
|
<dd class="col">
|
||||||
{ format!("{}", &userinfo.uat.uuid ) }
|
{ format!("{}", &uat.uuid ) }
|
||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
</dl>
|
</dl>
|
||||||
|
|
|
@ -14,7 +14,7 @@ use std::str::FromStr;
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use yew_router::prelude::*;
|
use yew_router::prelude::*;
|
||||||
|
|
||||||
use kanidm_proto::v1::{CUSessionToken, CUStatus, UserAuthToken};
|
use kanidm_proto::v1::{CUSessionToken, CUStatus, UiHint, UserAuthToken};
|
||||||
|
|
||||||
use wasm_bindgen::{JsCast, UnwrapThrowExt};
|
use wasm_bindgen::{JsCast, UnwrapThrowExt};
|
||||||
use wasm_bindgen_futures::JsFuture;
|
use wasm_bindgen_futures::JsFuture;
|
||||||
|
@ -86,7 +86,7 @@ impl Component for SecurityApp {
|
||||||
.unsafe_release_without_verification()
|
.unsafe_release_without_verification()
|
||||||
.expect_throw("Unvalid UAT, unable to release ");
|
.expect_throw("Unvalid UAT, unable to release ");
|
||||||
|
|
||||||
let id = uat.inner.uuid.to_string();
|
let id = uat.into_inner().uuid.to_string();
|
||||||
|
|
||||||
ctx.link().send_future(async {
|
ctx.link().send_future(async {
|
||||||
match Self::fetch_token_valid(id, token).await {
|
match Self::fetch_token_valid(id, token).await {
|
||||||
|
@ -145,7 +145,7 @@ impl Component for SecurityApp {
|
||||||
_ => html! { <></> },
|
_ => html! { <></> },
|
||||||
};
|
};
|
||||||
|
|
||||||
let current_user = ctx.props().current_user.clone();
|
let current_user_uat = ctx.props().current_user_uat.clone();
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<>
|
<>
|
||||||
|
@ -169,8 +169,8 @@ impl Component for SecurityApp {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<hr/>
|
<hr/>
|
||||||
if let Some(user) = current_user {
|
if let Some(uat) = current_user_uat {
|
||||||
if user.youare.attrs.get("class").map(|x| x.contains(&String::from("posixaccount"))).unwrap_or(true) {
|
if uat.ui_hints.contains(&UiHint::PosixAccount) {
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
<ChangeUnixPassword token={ctx.props().token.clone()}></ChangeUnixPassword>
|
<ChangeUnixPassword token={ctx.props().token.clone()}></ChangeUnixPassword>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
name = "profiles"
|
name = "profiles"
|
||||||
version = "1.1.0-alpha.9"
|
version = "1.1.0-alpha.9"
|
||||||
authors = ["William Brown <william@blackhats.net.au>"]
|
authors = ["William Brown <william@blackhats.net.au>"]
|
||||||
rust-version = "1.59"
|
rust-version = "1.64"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
description = "Kanidm Build System Profiles"
|
description = "Kanidm Build System Profiles"
|
||||||
|
|
Loading…
Reference in a new issue