mirror of
https://github.com/kanidm/kanidm.git
synced 2025-06-09 01:27:46 +02:00
20220817 ldap service tokens (#1002)
This commit is contained in:
parent
e6d4cd2d84
commit
925c03b3fb
Cargo.lockinsecure_generate_tls.sh
kanidm_book/src
SUMMARY.mdaccounts_and_groups.mdinstalling_client_tools.mdinstalling_the_server.md
integrations
intro.mdposix_accounts.mdrecycle_bin.mdserver_configuration.mdssh_key_dist.mdkanidm_client/src
kanidm_proto/src
kanidm_tools/src
kanidm_unix_int/tests
kanidmd
daemon
idm/src
access.rs
actors
be
constants
credential
entry.rsfilter.rsidm
interval.rsldap.rsmacros.rsplugins
repl
schema.rsserver.rsvalueset
score
kanidmd_web_ui/src
orca/src
sketching
169
Cargo.lock
generated
169
Cargo.lock
generated
|
@ -137,7 +137,7 @@ dependencies = [
|
|||
"num-traits",
|
||||
"rusticata-macros",
|
||||
"thiserror",
|
||||
"time 0.3.13",
|
||||
"time 0.3.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -213,9 +213,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "async-global-executor"
|
||||
version = "2.2.0"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5262ed948da60dd8956c6c5aca4d4163593dddb7b32d73267c93dab7b2e98940"
|
||||
checksum = "0da5b41ee986eed3f524c380e6d64965aea573882a8907682ad100f7859305ca"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-executor",
|
||||
|
@ -223,7 +223,6 @@ dependencies = [
|
|||
"async-lock",
|
||||
"blocking",
|
||||
"futures-lite",
|
||||
"num_cpus",
|
||||
"once_cell",
|
||||
"tokio",
|
||||
]
|
||||
|
@ -246,10 +245,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "async-io"
|
||||
version = "1.7.0"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5e18f61464ae81cde0a23e713ae8fd299580c54d697a35820cfd0625b8b0e07"
|
||||
checksum = "0ab006897723d9352f63e2b13047177c3982d8d79709d713ce7747a8f19fd1b0"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"concurrent-queue",
|
||||
"futures-lite",
|
||||
"libc",
|
||||
|
@ -274,11 +274,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "async-process"
|
||||
version = "1.4.0"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf2c06e30a24e8c78a3987d07f0930edf76ef35e027e7bdb063fccafdad1f60c"
|
||||
checksum = "02111fd8655a613c25069ea89fc8d9bb89331fa77486eb3bc059ee757cfa481c"
|
||||
dependencies = [
|
||||
"async-io",
|
||||
"autocfg",
|
||||
"blocking",
|
||||
"cfg-if 1.0.0",
|
||||
"event-listener",
|
||||
|
@ -585,9 +586,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.10.0"
|
||||
version = "3.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3"
|
||||
checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d"
|
||||
|
||||
[[package]]
|
||||
name = "byte-tools"
|
||||
|
@ -684,9 +685,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.2.17"
|
||||
version = "3.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29e724a68d9319343bb3328c9cc2dfde263f4b3142ee1059a9980580171c954b"
|
||||
checksum = "b15f2ea93df33549dbe2e8eecd1ca55269d63ae0b3ba1f55db030817d1c2867f"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
|
@ -705,14 +706,14 @@ version = "3.2.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4179da71abd56c26b54dd0c248cc081c1f43b0a1a7e8448e28e57a29baa993d"
|
||||
dependencies = [
|
||||
"clap 3.2.17",
|
||||
"clap 3.2.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "3.2.17"
|
||||
version = "3.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13547f7012c01ab4a0e8f8967730ada8f9fdf419e8b6c792788f39cf4e46eefa"
|
||||
checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-error",
|
||||
|
@ -842,7 +843,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "94d4706de1b0fa5b132270cddffa8585166037822e260a944fe161acd137ca05"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
"time 0.3.13",
|
||||
"time 0.3.14",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
|
@ -858,7 +859,7 @@ dependencies = [
|
|||
"publicsuffix",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"time 0.3.13",
|
||||
"time 0.3.14",
|
||||
"url",
|
||||
]
|
||||
|
||||
|
@ -880,9 +881,9 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
|||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.2"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b"
|
||||
checksum = "dc948ebb96241bb40ab73effeb80d9f93afaad49359d159a5e61be51619fe813"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
@ -1082,7 +1083,7 @@ dependencies = [
|
|||
name = "daemon"
|
||||
version = "1.1.0-alpha.9"
|
||||
dependencies = [
|
||||
"clap 3.2.17",
|
||||
"clap 3.2.18",
|
||||
"clap_complete",
|
||||
"kanidm",
|
||||
"kanidm_proto",
|
||||
|
@ -1185,9 +1186,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "devd-rs"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9861a4115f5148d32f7bbbadcdc562d5754ed75c8d27316a587440dd2ac4df84"
|
||||
checksum = "0c315b8fe6f26aea3091b030c28aabbdf491376ae39033978f06e468ab42360c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"nom 7.1.1",
|
||||
|
@ -1277,9 +1278,9 @@ checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2"
|
|||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.7.0"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be"
|
||||
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
|
@ -1298,9 +1299,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "erased-serde"
|
||||
version = "0.3.22"
|
||||
version = "0.3.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "003000e712ad0f95857bd4d2ef8d1890069e06554101697d12050668b2f6f020"
|
||||
checksum = "54558e0ba96fbe24280072642eceb9d7d442e32c7ec0ea9e7ecd7b4ea2cf4e11"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
@ -1438,9 +1439,9 @@ checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394"
|
|||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.23"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab30e97ab6aacfe635fad58f22c2bb06c8b685f7421eb1e064a729e2a5f481fa"
|
||||
checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
|
@ -1453,9 +1454,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.23"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bfc52cbddcfd745bf1740338492bb0bd83d76c67b445f91c5fb29fae29ecaa1"
|
||||
checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
|
@ -1463,15 +1464,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.23"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2acedae88d38235936c3922476b10fced7b2b68136f5e3c03c2d5be348a1115"
|
||||
checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.23"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d11aa21b5b587a64682c0094c2bdd4df0076c5324961a40cc3abd7f37930528"
|
||||
checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
|
@ -1480,9 +1481,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.23"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93a66fc6d035a26a3ae255a6d2bca35eda63ae4c5512bef54449113f7a1228e5"
|
||||
checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
|
@ -1501,9 +1502,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.23"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0db9cce532b0eae2ccf2766ab246f114b56b9cf6d445e00c2549fbc100ca045d"
|
||||
checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1512,21 +1513,21 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.23"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca0bae1fe9752cf7fd9b0064c674ae63f97b37bc714d745cbde0afb7ec4e6765"
|
||||
checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.23"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "842fc63b931f4056a24d59de13fb1272134ce261816e063e634ad0c15cdc5306"
|
||||
checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.23"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0828a5471e340229c11c77ca80017937ce3c58cb788a17e5f1c2d5c485a9577"
|
||||
checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
|
@ -1645,10 +1646,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gloo-console"
|
||||
version = "0.2.1"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3907f786f65bbb4f419e918b0c5674175ef1c231ecda93b2dbd65fd1e8882637"
|
||||
checksum = "82b7ce3c05debe147233596904981848862b068862e9ec3e34be446077190d3f"
|
||||
dependencies = [
|
||||
"gloo-utils",
|
||||
"js-sys",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
|
@ -1706,9 +1708,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gloo-net"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "351e6f94c76579cc9f9323a15f209086fc7bd428bff4288723d3a417851757b2"
|
||||
checksum = "ec897194fb9ac576c708f63d35604bc58f2a262b8cec0fabfed26f3991255f21"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
|
@ -1736,9 +1738,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gloo-storage"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1caa4ba51c99de680dee3ad99c32ca45e9f13311be72079154d222c3f9a6b6f5"
|
||||
checksum = "5d6ab60bf5dbfd6f0ed1f7843da31b41010515c745735c970e821945ca91e480"
|
||||
dependencies = [
|
||||
"gloo-utils",
|
||||
"js-sys",
|
||||
|
@ -1763,20 +1765,22 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gloo-utils"
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "929c53c913bb7a88d75d9dc3e9705f963d8c2b9001510b25ddaf671b9fb7049d"
|
||||
checksum = "40913a05c8297adca04392f707b1e73b12ba7b8eab7244a4961580b1fd34063c"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gloo-worker"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9caac1b89bbe1e1454bb23e4d046a3fc92438ae2e95fb429c41685789e1fcbaa"
|
||||
checksum = "13471584da78061a28306d1359dd0178d8d6fc1c7c80e5e35d27260346e0516a"
|
||||
dependencies = [
|
||||
"anymap2",
|
||||
"bincode",
|
||||
|
@ -1985,9 +1989,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.45"
|
||||
version = "0.1.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef5528d9c2817db4e10cc78f8d4c8228906e5854f389ff6b076cee3572a09d35"
|
||||
checksum = "ad2bfd338099682614d3ee3fe0cd72e0b6a41ca6a87f6a74a3bd593c91650501"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
|
@ -2205,7 +2209,7 @@ dependencies = [
|
|||
name = "kanidm_tools"
|
||||
version = "1.1.0-alpha.9"
|
||||
dependencies = [
|
||||
"clap 3.2.17",
|
||||
"clap 3.2.18",
|
||||
"clap_complete",
|
||||
"compact_jwt",
|
||||
"dialoguer",
|
||||
|
@ -2233,7 +2237,7 @@ name = "kanidm_unix_int"
|
|||
version = "1.1.0-alpha.9"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"clap 3.2.17",
|
||||
"clap 3.2.18",
|
||||
"clap_complete",
|
||||
"futures",
|
||||
"kanidm",
|
||||
|
@ -2436,9 +2440,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
|||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.7"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53"
|
||||
checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
|
@ -2784,7 +2788,7 @@ dependencies = [
|
|||
name = "orca"
|
||||
version = "1.1.0-alpha.9"
|
||||
dependencies = [
|
||||
"clap 3.2.17",
|
||||
"clap 3.2.18",
|
||||
"crossbeam",
|
||||
"csv",
|
||||
"dialoguer",
|
||||
|
@ -2949,9 +2953,9 @@ checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
|
|||
|
||||
[[package]]
|
||||
name = "plotters"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9428003b84df1496fb9d6eeee9c5f8145cb41ca375eb0dad204328888832811f"
|
||||
checksum = "716b4eeb6c4a1d3ecc956f75b43ec2e8e8ba80026413e70a3f41fd3313d3492b"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"plotters-backend",
|
||||
|
@ -2968,19 +2972,20 @@ checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142"
|
|||
|
||||
[[package]]
|
||||
name = "plotters-svg"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0918736323d1baff32ee0eade54984f6f201ad7e97d5cfb5d6ab4a358529615"
|
||||
checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f"
|
||||
dependencies = [
|
||||
"plotters-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "2.2.0"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259"
|
||||
checksum = "899b00b9c8ab553c743b3e11e87c5c7d423b2a2de229ba95b24a756344748011"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"log",
|
||||
|
@ -3500,9 +3505,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.6.1"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc"
|
||||
checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"core-foundation",
|
||||
|
@ -3538,9 +3543,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.143"
|
||||
version = "1.0.144"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553"
|
||||
checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
@ -3578,9 +3583,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.143"
|
||||
version = "1.0.144"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391"
|
||||
checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -3598,9 +3603,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.83"
|
||||
version = "1.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7"
|
||||
checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44"
|
||||
dependencies = [
|
||||
"itoa 1.0.3",
|
||||
"ryu",
|
||||
|
@ -3789,9 +3794,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.4.4"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
|
||||
checksum = "10c98bba371b9b22a71a9414e420f92ddeb2369239af08200816169d5e2dd7aa"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
|
@ -4095,9 +4100,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.13"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db76ff9fa4b1458b3c7f077f3ff9887394058460d21e634355b273aaf11eea45"
|
||||
checksum = "3c3f9a28b618c3a6b9251b6908e9c99e04b9e5c02e6581ccbb67d59c34ef7f9b"
|
||||
dependencies = [
|
||||
"itoa 1.0.3",
|
||||
"libc",
|
||||
|
@ -4286,7 +4291,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "tracing-forest"
|
||||
version = "0.1.4"
|
||||
source = "git+https://github.com/Firstyear/tracing-forest.git?rev=18d242a4dde060c4946ade0a2c4d5be1df048aea#18d242a4dde060c4946ade0a2c4d5be1df048aea"
|
||||
source = "git+https://github.com/QnnOkabayashi/tracing-forest.git?rev=48d78f7294ceee47a22eee5c80964143c4fb3fe1#48d78f7294ceee47a22eee5c80964143c4fb3fe1"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
|
@ -4824,7 +4829,7 @@ dependencies = [
|
|||
"oid-registry",
|
||||
"rusticata-macros",
|
||||
"thiserror",
|
||||
"time 0.3.13",
|
||||
"time 0.3.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4944,5 +4949,5 @@ dependencies = [
|
|||
"lazy_static",
|
||||
"quick-error",
|
||||
"regex",
|
||||
"time 0.3.13",
|
||||
"time 0.3.14",
|
||||
]
|
||||
|
|
|
@ -13,8 +13,11 @@ if [ -z "$KANI_TMP" ]; then
|
|||
fi
|
||||
|
||||
ALTNAME_FILE="${KANI_TMP}altnames.cnf"
|
||||
CANAME_FILE="${KANI_TMP}ca.cnf"
|
||||
CACERT="${KANI_TMP}ca.pem"
|
||||
CAKEY="${KANI_TMP}cakey.pem"
|
||||
CADB="${KANI_TMP}ca.txt"
|
||||
CASRL="${KANI_TMP}ca.srl"
|
||||
|
||||
KEYFILE="${KANI_TMP}key.pem"
|
||||
CERTFILE="${KANI_TMP}cert.pem"
|
||||
|
@ -27,7 +30,78 @@ if [ ! -d "${KANI_TMP}" ]; then
|
|||
mkdir -p "${KANI_TMP}"
|
||||
fi
|
||||
|
||||
cat > "${CANAME_FILE}" << DEVEOF
|
||||
[req]
|
||||
nsComment = "Certificate Authority"
|
||||
distinguished_name = req_distinguished_name
|
||||
req_extensions = v3_ca
|
||||
|
||||
[ req_distinguished_name ]
|
||||
|
||||
countryName = Country Name (2 letter code)
|
||||
countryName_default = AU
|
||||
countryName_min = 2
|
||||
countryName_max = 2
|
||||
|
||||
stateOrProvinceName = State or Province Name (full name)
|
||||
stateOrProvinceName_default = Queensland
|
||||
|
||||
localityName = Locality Name (eg, city)
|
||||
localityName_default = Brisbane
|
||||
|
||||
0.organizationName = Organization Name (eg, company)
|
||||
0.organizationName_default = INSECURE EXAMPLE
|
||||
|
||||
organizationalUnitName = Organizational Unit Name (eg, section)
|
||||
organizationalUnitName_default = kanidm
|
||||
|
||||
commonName = Common Name (eg, your name or your server\'s hostname)
|
||||
commonName_max = 64
|
||||
commonName_default = insecure.ca.localhost
|
||||
|
||||
[ v3_ca ]
|
||||
subjectKeyIdentifier = hash
|
||||
basicConstraints = critical, CA:true
|
||||
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
|
||||
|
||||
DEVEOF
|
||||
|
||||
cat > "${ALTNAME_FILE}" << DEVEOF
|
||||
|
||||
[ca]
|
||||
default_ca = CA_default
|
||||
|
||||
[ CA_default ]
|
||||
# Directory and file locations.
|
||||
dir = ${KANI_TMP}
|
||||
certs = ${KANI_TMP}
|
||||
crl_dir = ${KANI_TMP}
|
||||
new_certs_dir = ${KANI_TMP}
|
||||
database = ${CADB}
|
||||
serial = ${CASRL}
|
||||
|
||||
# The root key and root certificate.
|
||||
private_key = ${CAKEY}
|
||||
certificate = ${CACERT}
|
||||
|
||||
# SHA-1 is deprecated, so use SHA-2 instead.
|
||||
default_md = sha256
|
||||
|
||||
name_opt = ca_default
|
||||
cert_opt = ca_default
|
||||
default_days = 3650
|
||||
preserve = no
|
||||
policy = policy_loose
|
||||
|
||||
[ policy_loose ]
|
||||
countryName = optional
|
||||
stateOrProvinceName = optional
|
||||
localityName = optional
|
||||
organizationName = optional
|
||||
organizationalUnitName = optional
|
||||
commonName = supplied
|
||||
emailAddress = optional
|
||||
|
||||
[req]
|
||||
nsComment = "Certificate"
|
||||
distinguished_name = req_distinguished_name
|
||||
|
@ -54,14 +128,15 @@ organizationalUnitName_default = kanidm
|
|||
|
||||
commonName = Common Name (eg, your name or your server\'s hostname)
|
||||
commonName_max = 64
|
||||
commonName_default = localhost
|
||||
commonName_default = ${CERT_HOSTNAME}
|
||||
|
||||
[ v3_req ]
|
||||
|
||||
# Extensions to add to a certificate request
|
||||
|
||||
basicConstraints = CA:FALSE
|
||||
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
||||
nsCertType = server
|
||||
nsComment = "Server Certificate"
|
||||
subjectKeyIdentifier = hash
|
||||
keyUsage = critical, digitalSignature, keyEncipherment
|
||||
extendedKeyUsage = serverAuth
|
||||
subjectAltName = @alt_names
|
||||
|
||||
[alt_names]
|
||||
|
@ -70,34 +145,37 @@ IP.1 = 127.0.0.1
|
|||
|
||||
DEVEOF
|
||||
|
||||
# Make the ca
|
||||
openssl req -x509 -new -newkey rsa:4096 -sha256 \
|
||||
-keyout "${CAKEY}" \
|
||||
-out "${CACERT}" \
|
||||
-days +31 \
|
||||
-subj "/C=AU/ST=Queensland/L=Brisbane/O=INSECURE/CN=insecure.ca.localhost" -nodes
|
||||
touch ${CADB}
|
||||
echo 1000 > ${CASRL}
|
||||
|
||||
# generate the ca private key
|
||||
openssl genrsa -out "${KEYFILE}" 4096
|
||||
# Make the ca key
|
||||
openssl ecparam -genkey -name prime256v1 -noout -out "${CAKEY}"
|
||||
|
||||
# Self sign the CA.
|
||||
openssl req -config "${CANAME_FILE}" \
|
||||
-key "${CAKEY}" \
|
||||
-new -x509 -days +31 \
|
||||
-sha256 -extensions v3_ca \
|
||||
-out "${CACERT}" \
|
||||
-nodes
|
||||
|
||||
# generate the server private key
|
||||
openssl ecparam -genkey -name prime256v1 -noout -out "${KEYFILE}"
|
||||
|
||||
# generate the certficate signing request
|
||||
openssl req -sha256 \
|
||||
-config "${ALTNAME_FILE}" \
|
||||
-new -extensions v3_req \
|
||||
openssl req -sha256 -new \
|
||||
-config "${ALTNAME_FILE}" -extensions v3_req \
|
||||
-key "${KEYFILE}"\
|
||||
-subj "/C=AU/ST=Queensland/L=Brisbane/O=INSECURE/CN=${CERT_HOSTNAME}" \
|
||||
-nodes \
|
||||
-out "${CSRFILE}"
|
||||
|
||||
# sign the cert
|
||||
openssl x509 -req -days 31 \
|
||||
-extfile "${ALTNAME_FILE}" \
|
||||
-CA "${CACERT}" \
|
||||
-CAkey "${CAKEY}" \
|
||||
-CAcreateserial \
|
||||
openssl ca -config "${ALTNAME_FILE}" \
|
||||
-extensions v3_req \
|
||||
-days 31 -notext -md sha256 \
|
||||
-in "${CSRFILE}" \
|
||||
-out "${CERTFILE}" \
|
||||
-extensions v3_req -sha256
|
||||
-out "${CERTFILE}"
|
||||
|
||||
# Create the chain
|
||||
cat "${CERTFILE}" "${CACERT}" > "${CHAINFILE}"
|
||||
|
||||
|
@ -107,4 +185,4 @@ openssl dhparam -in "${CAFILE}" -out "${DHFILE}" 2048
|
|||
echo "Certificate chain is at: ${CHAINFILE}"
|
||||
echo "Private key is at: ${KEYFILE}"
|
||||
echo ""
|
||||
echo "**Remember** the default action is to store the files in /tmp/ so they'll be deleted on reboot! Set the KANI_TMP environment variable before running this script if you want to change that. You'll need to update server config elsewhere if you do, however."
|
||||
echo "**Remember** the default action is to store the files in /tmp/ so they'll be deleted on reboot! Set the KANI_TMP environment variable before running this script if you want to change that. You'll need to update server config elsewhere if you do, however."
|
||||
|
|
|
@ -15,16 +15,6 @@
|
|||
- [The Recycle Bin](recycle_bin.md)
|
||||
- [Why TLS?](why_tls.md)
|
||||
|
||||
# For Developers
|
||||
|
||||
- [Developer Guide](DEVELOPER_README.md)
|
||||
- [Design Documents]()
|
||||
- [Access Profiles](developers/designs/access_profiles_and_security.md)
|
||||
- [REST Interface](developers/designs/rest_interface.md)
|
||||
- [Python Module](developers/python.md)
|
||||
- [RADIUS Integration](developers/radius.md)
|
||||
|
||||
|
||||
# Integrations
|
||||
|
||||
- [Oauth2](integrations/oauth2.md)
|
||||
|
@ -36,7 +26,17 @@
|
|||
|
||||
- [Kubernetes Ingress](examples/k8s_ingress_example.md)
|
||||
|
||||
# Packaging
|
||||
# For Developers
|
||||
|
||||
- [Developer Guide](DEVELOPER_README.md)
|
||||
- [Design Documents]()
|
||||
- [Access Profiles](developers/designs/access_profiles_and_security.md)
|
||||
- [REST Interface](developers/designs/rest_interface.md)
|
||||
- [Python Module](developers/python.md)
|
||||
- [RADIUS Integration](developers/radius.md)
|
||||
|
||||
## Packaging
|
||||
|
||||
- [Packaging](packaging.md)
|
||||
- [Debian/Ubuntu](packaging_debs.md)
|
||||
- [Debian/Ubuntu](packaging_debs.md)
|
||||
|
||||
|
|
|
@ -3,92 +3,140 @@
|
|||
Accounts and Groups are the primary reasons for Kanidm to exist. Kanidm is optimised as a repository
|
||||
for these data. As a result, there are many concepts and important details to understand.
|
||||
|
||||
## Service Accounts vs Person Accounts
|
||||
|
||||
Kanidm seperates accounts into two types. Person accounts (or persons) are intended for use by humans
|
||||
that will access the system in an interactive way. Service accounts are intended for use by computers
|
||||
or services that need to identify themself to Kanidm. Generally a person or group of persons will
|
||||
be responsible for and will manage service accounts. Because of this distinction these classes of
|
||||
accounts have different properties and methods of authentication and management.
|
||||
|
||||
## Groups
|
||||
|
||||
Groups represent a collection of entities. This generally is a collection of persons or service accounts.
|
||||
Groups are commonly used to assign privileges to the accouts that are members of a group. This allows
|
||||
easier administration over larger systems where privileges can be assigned to groups in a logical
|
||||
manner, and then only membership of the groups need administration, rather than needing to assign
|
||||
privileges to each entity directly and uniquely.
|
||||
|
||||
Groups may also be nested, where a group can contain another group as a member. This allows hierarchies
|
||||
to be created again for easier administration.
|
||||
|
||||
## Default Accounts and Groups
|
||||
|
||||
Kanidm ships with a number of default accounts and groups. This is to give you the best
|
||||
out-of-box experience possible, as well as supplying best practice examples related to modern
|
||||
Kanidm ships with a number of default service accounts and groups. This is to give you the best
|
||||
out-of-box experience possible, as well as supplying best practice examples related to modern
|
||||
Identity Management (IDM) systems.
|
||||
|
||||
The system administrator account has limited privileges (see
|
||||
[Recovering the Initial idm_admin Account](#recovering-the-initial-idm_admin-account)) to learn
|
||||
how to access the inbuilt admin account).
|
||||
It manages only high-privilege accounts and services. This is to help separate system administration
|
||||
from identity administration actions. An idm_admin user is also provided that is only for management
|
||||
of accounts and groups.
|
||||
There are two builtin system administration accounts.
|
||||
|
||||
Both the admin and the idm_admin user should *NOT* be used for daily activities - they exist for initial
|
||||
`admin` is the default service account which has privileges to configure and administer kanidm as a whole.
|
||||
This account can manage access controls, schema, integrations and more. However the `admin` can not
|
||||
manage persons by default to seperate the priviliges. As this is a service account is is intended
|
||||
for limited use.
|
||||
|
||||
`idm_admin` is the default service account which has privileges to create persons and to manage these
|
||||
accounts and groups. They can perform credential resets and more.
|
||||
|
||||
Both the `admin` and the `idm_admin` user should *NOT* be used for daily activities - they exist for initial
|
||||
system configuration, and for disaster recovery scenarios. You should delegate permissions
|
||||
as required to named user accounts instead.
|
||||
|
||||
The majority of the provided content is privilege groups that provide rights over Kanidm
|
||||
The majority of the builtin groups are privilige groups that provide rights over Kanidm
|
||||
administrative actions. These include groups for account management, person management (personal
|
||||
and sensitive data), group management, and more.
|
||||
|
||||
## Recovering the Initial idm_admin Account
|
||||
## Recovering the Initial Admin Accounts
|
||||
|
||||
By default the idm_admin user has no password, and can not be accessed. You should recover it with the
|
||||
admin (system admin) account.
|
||||
By default the `admin` and `idm_admin` accounts have no password, and can not be accessed. They need
|
||||
to be "recovered" from the server that is running the kanidmd server.
|
||||
|
||||
{{#template
|
||||
{{#template
|
||||
templates/kani-warning.md
|
||||
imagepath=images
|
||||
text=Warning: The server must not be running at this point, as it requires exclusive access to the database.
|
||||
}}
|
||||
|
||||
We recommend the use of the "recover_account" functionality as it provides a high strength, random password.
|
||||
|
||||
```shell
|
||||
kanidmd recover_account -c /etc/kanidm/server.toml -n idm_admin
|
||||
Successfully recovered account 'idm_admin' - password reset to -> j9YUv...
|
||||
kanidmd recover_account admin -c /etc/kanidm/server.toml
|
||||
# Successfully recovered account 'admin' - password reset to -> j9YUv...
|
||||
```
|
||||
|
||||
To do this in Docker, you'll need to stop the existing container and run it with `bash` as the "command" argument, to get a shell, then run the `kanidmd` command above.
|
||||
|
||||
For example, if I'm using the developer image in my test environment:
|
||||
To do this with Docker, you'll need to stop the existing container and use the "command" argument to
|
||||
access the kanidmd binary.
|
||||
|
||||
```shell
|
||||
docker run --rm -it \
|
||||
-v/tmp/kanidm:/data\
|
||||
--name kanidmd \
|
||||
--hostname kanidmd \
|
||||
ghcr.io/kanidm/kanidmd:devel \
|
||||
bash
|
||||
kanidmd:/# kanidmd recover_account -c /data/server.toml -n idm_admin
|
||||
Successfully recovered account 'idm_admin' - password reset to -> j9YUv...
|
||||
kanidm/server:latest \
|
||||
kanidmd recover_account admin -c /data/server.toml
|
||||
```
|
||||
|
||||
Once that's done, exit the shell and start your server container again.
|
||||
After the recovery is complete the server can be started again.
|
||||
|
||||
## Creating Accounts
|
||||
|
||||
You can now use the idm_admin user to create initial groups and accounts.
|
||||
Once you have access to the admin account, it is able to reset the credentials of the `idm_admin`
|
||||
account.
|
||||
|
||||
```shell
|
||||
kanidm login --name idm_admin
|
||||
kanidm group create demo_group --name idm_admin
|
||||
kanidm account create demo_user "Demonstration User" --name idm_admin
|
||||
kanidm group add_members demo_group demo_user --name idm_admin
|
||||
kanidm group list_members demo_group --name idm_admin
|
||||
kanidm account get demo_user --name idm_admin
|
||||
kanidm login -D admin
|
||||
kanidm service-account credential generate-pw -D admin idm_admin
|
||||
# Success: wJX...
|
||||
```
|
||||
|
||||
You can also use anonymous to view users and groups - note that you won't see as many fields due
|
||||
to the limits of the anonymous access profile.
|
||||
|
||||
kanidm login --name anonymous
|
||||
kanidm account get demo_user --name anonymous
|
||||
These accounts will be used through the remainder of this document for managing the server.
|
||||
|
||||
## Viewing Default Groups
|
||||
|
||||
You should take some time to inspect the default groups which are related to
|
||||
default permissions. These can be viewed with:
|
||||
|
||||
kanidm group list
|
||||
kanidm group get <name>
|
||||
```
|
||||
kanidm group list
|
||||
kanidm group get <name>
|
||||
```
|
||||
|
||||
## Resetting Account Credentials
|
||||
## Creating Person Accounts
|
||||
|
||||
Members of the `idm_account_manage_priv` group have the rights to manage other users'
|
||||
By default `idm_admin` has the privileges to create new persons in the system.
|
||||
|
||||
```shell
|
||||
kanidm login --name idm_admin
|
||||
kanidm account create demo_user "Demonstration User" --name idm_admin
|
||||
kanidm account get demo_user --name idm_admin
|
||||
|
||||
kanidm group create demo_group --name idm_admin
|
||||
kanidm group add_members demo_group demo_user --name idm_admin
|
||||
kanidm group list_members demo_group --name idm_admin
|
||||
```
|
||||
|
||||
You can also use anonymous to view accounts and groups - note that you won't see certain fields due
|
||||
to the limits of the access control anonymous access profile.
|
||||
|
||||
```
|
||||
kanidm login --name anonymous
|
||||
kanidm account get demo_user --name anonymous
|
||||
```
|
||||
|
||||
Kanidm allows person accounts to include human related attributes, such as their legal name and email address.
|
||||
|
||||
Initially, a person does not have these attributes. If desired, a person may be modified to have these attributes.
|
||||
|
||||
```shell
|
||||
# Note, both the --legalname and --mail flags may be omitted
|
||||
kanidm account person update demo_user --legalname "initial name" --mail "initial@email.address"
|
||||
```
|
||||
|
||||
{{#template
|
||||
templates/kani-warning.md
|
||||
imagepath=images
|
||||
text=Warning: Persons may change their own displayname, name, and legal name at any time. You MUST NOT use these values as primary keys in external systems. You MUST use the `uuid` attribute present on all entries as an external primary key.
|
||||
}}
|
||||
|
||||
## Resetting Person Account Credentials
|
||||
|
||||
Members of the `idm_account_manage_priv` group have the rights to manage person and service
|
||||
accounts security and login aspects. This includes resetting account credentials.
|
||||
|
||||
You can perform a password reset on the demo_user, for example as the idm_admin user, who is
|
||||
|
@ -118,6 +166,26 @@ kanidm login --name demo_user
|
|||
kanidm self whoami --name demo_user
|
||||
```
|
||||
|
||||
## Creating Service Accounts
|
||||
|
||||
The `admin` service account can be used to create service accounts.
|
||||
|
||||
```shell
|
||||
kanidm service-account create demo_service "Demonstration Service" --name admin
|
||||
kanidm service-account get demo_service --name admin
|
||||
```
|
||||
|
||||
## Resetting Service Account Credentials
|
||||
|
||||
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.
|
||||
|
||||
To re-generate this password to an account
|
||||
|
||||
```shell
|
||||
kanidm service-account credential generate-pw demo_service --name admin
|
||||
```
|
||||
|
||||
## Nested Groups
|
||||
|
||||
Kanidm supports groups being members of groups, allowing nested groups. These nesting relationships
|
||||
|
@ -138,10 +206,8 @@ kanidm account get nest_example --name anonymous
|
|||
|
||||
## Account Validity
|
||||
|
||||
Kanidm supports accounts that are only able to be authenticated between specific date and time
|
||||
date where authentication can succeed, and an expiry date where the account will no longer
|
||||
windows. This takes the form of a "valid from" attribute that defines the earliest start
|
||||
allow authentication.
|
||||
Kanidm supports accounts that are only able authenticate between a pair of dates and times; the "valid
|
||||
from" and "expires" timestamps define these points in time.
|
||||
|
||||
This can be displayed with:
|
||||
|
||||
|
@ -153,7 +219,15 @@ These datetimes are stored in the server as UTC, but presented according to your
|
|||
to aid correct understanding of when the events will occur.
|
||||
|
||||
To set the values, an account with account management permission is required (for example, idm_admin).
|
||||
Again, these values will correctly translated from the entered local timezone to UTC.
|
||||
|
||||
You may set these time and date values in any timezone you wish (such as your local timezone), and the
|
||||
server will transform these to UTC. These time values are in iso8601 format, and you should specify this
|
||||
as:
|
||||
|
||||
```
|
||||
YYYY-MM-DDThh:mm:ssZ+-hh:mm
|
||||
Year-Month-Day T hour:minutes:seconds Z +- timezone offset
|
||||
```
|
||||
|
||||
Set the earliest time the account can start authenticating:
|
||||
|
||||
|
@ -181,30 +255,6 @@ where the "valid from" is *after* the expire_at, the expire_at will be respected
|
|||
|
||||
These validity settings impact all authentication functions of the account (kanidm, ldap, radius).
|
||||
|
||||
## People Accounts
|
||||
|
||||
Kanidm allows extending accounts to include additional "people" attributes,
|
||||
such as their legal name and email address.
|
||||
|
||||
Initially, an account does not have these attributes. If desired, an account
|
||||
may be modified to have these "person" attributes like so:
|
||||
|
||||
# Note, both the --legalname and --mail flags may be omitted
|
||||
kanidm account person extend demo_user --legalname "initial name" --mail "initial@email.address"
|
||||
|
||||
Once an account has been extended, the "person" attributes may be set by the
|
||||
user of the account, or anyone with enough privileges.
|
||||
|
||||
Whether an account is currently a "person" or not can be identified from the "account get" output:
|
||||
|
||||
kanidm account get demo_user
|
||||
# ---
|
||||
# class: person
|
||||
# ... (other output omitted)
|
||||
|
||||
The presence of a "class: person" stanza indicates that this account may have
|
||||
"people" attributes.
|
||||
|
||||
### Allowing people accounts to change their mail attribute
|
||||
|
||||
By default, Kanidm allows an account to change some attributes, but not their
|
||||
|
|
|
@ -66,8 +66,8 @@ Now you can check your instance is working. You may need to provide a CA certifi
|
|||
with the -C parameter:
|
||||
|
||||
kanidm login --name anonymous
|
||||
kanidm self whoami -C ../path/to/ca.pem -H https://localhost:8443 --name anonymous
|
||||
kanidm self whoami -H https://localhost:8443 --name anonymous
|
||||
kanidm self whoami -C ../path/to/ca.pem -H https://localhost:8443 --name anonymous
|
||||
|
||||
Now you can take some time to look at what commands are available - please
|
||||
[ask for help at any time](https://github.com/kanidm/kanidm#getting-in-contact--questions).
|
||||
Now you can take some time to look at what commands are available - please
|
||||
[ask for help at any time](https://github.com/kanidm/kanidm#getting-in-contact--questions).
|
||||
|
|
|
@ -121,7 +121,6 @@ OR for a shell into the volume:
|
|||
docker run --rm -i -t -v kanidmd:/data opensuse/leap:latest /bin/sh
|
||||
|
||||
|
||||
|
||||
# Continue on to [Configuring the Server](server_configuration.md)
|
||||
|
||||
|
||||
|
|
|
@ -293,8 +293,7 @@ oauth:
|
|||
user_info_url: https://idm.wherekanidmruns.com/oauth2/openid/<oauth2_rs_name>/userinfo
|
||||
```
|
||||
|
||||
The `email` scope needs to be passed and thus the attribute needs to exist in
|
||||
the account:
|
||||
The `email` scope needs to be passed and thus the mail attribute needs to exist on the account:
|
||||
|
||||
kanidm person update <ID> --mail "YYYY@somedomain.com" --name idm_admin
|
||||
|
||||
kanidm login --name idm_admin
|
||||
kanidm account person extend YYYY --mail "YYYY@somedomain.com" --name idm_admin
|
||||
|
|
|
@ -16,23 +16,27 @@ It's worth noting some disclaimers about Kanidm's RADIUS integration.
|
|||
|
||||
### One Credential - One Account
|
||||
|
||||
Kanidm normally attempts to have credentials for each *device* and
|
||||
Kanidm normally attempts to have credentials for each *device* and
|
||||
*application* rather than the legacy model of one to one.
|
||||
|
||||
The RADIUS protocol is only able to attest a *single* credential in an
|
||||
authentication attempt, which limits us to storing a single RADIUS credential
|
||||
per account. However, despite this limitation, it still greatly improves the
|
||||
situation by isolating the RADIUS credential from the primary or application
|
||||
credentials of the account. This solves many common security concerns around
|
||||
credential loss or disclosure, and prevents rogue devices from locking out
|
||||
The RADIUS protocol is only able to attest a *single* password based credential in an
|
||||
authentication attempt, which limits us to storing a single RADIUS password credential
|
||||
per account. However, despite this limitation, it still greatly improves the
|
||||
situation by isolating the RADIUS credential from the primary or application
|
||||
credentials of the account. This solves many common security concerns around
|
||||
credential loss or disclosure, and prevents rogue devices from locking out
|
||||
accounts as they attempt to authenticate to Wi-Fi with expired credentials.
|
||||
|
||||
Alternatelly, Kanidm supports mapping users with special configuration of certificates
|
||||
allowing some systems to use EAP-TLS for RADIUS authentication. This returns to the
|
||||
"per device" credential model.
|
||||
|
||||
### Cleartext Credential Storage
|
||||
|
||||
RADIUS offers many different types of tunnels and authentication mechanisms.
|
||||
However, most client devices "out of the box" only attempt a single type when
|
||||
a WPA2-Enterprise network is selected: MSCHAPv2 with PEAP. This is a
|
||||
challenge-response protocol that requires clear text or Windows NT LAN
|
||||
However, most client devices "out of the box" only attempt a single type when
|
||||
a WPA2-Enterprise network is selected: MSCHAPv2 with PEAP. This is a
|
||||
challenge-response protocol that requires clear text or Windows NT LAN
|
||||
Manager (NTLM) credentials.
|
||||
|
||||
As MSCHAPv2 with PEAP is the only practical, universal RADIUS-type supported
|
||||
|
@ -41,8 +45,8 @@ that it MUST be supported as the default. Esoteric RADIUS types can be used
|
|||
as well, but this is up to administrators to test and configure.
|
||||
|
||||
Due to this requirement, we must store the RADIUS material as clear text or
|
||||
NTLM hashes. It would be silly to think that NTLM is secure as it relies on
|
||||
the obsolete and deprecated MD4 cryptographic hash, providing only an
|
||||
NTLM hashes. It would be silly to think that NTLM is secure as it relies on
|
||||
the obsolete and deprecated MD4 cryptographic hash, providing only an
|
||||
illusion of security.
|
||||
|
||||
This means Kanidm stores RADIUS credentials in the database as clear text.
|
||||
|
@ -51,19 +55,25 @@ We believe this is a reasonable decision and is a low risk to security because:
|
|||
|
||||
* The access controls around RADIUS secrets by default are strong, limited
|
||||
to only self-account read and RADIUS-server read.
|
||||
* As RADIUS credentials are separate from the primary account credentials and
|
||||
have no other rights, their disclosure is not going to lead to a full
|
||||
* As RADIUS credentials are separate from the primary account credentials and
|
||||
have no other rights, their disclosure is not going to lead to a full
|
||||
account compromise.
|
||||
* Having the credentials in clear text allows a better user experience as
|
||||
* Having the credentials in clear text allows a better user experience as
|
||||
clients can view the credentials at any time to enroll further devices.
|
||||
|
||||
### Service Accounts Do Not Have Radius Access
|
||||
|
||||
Due to the design of service accounts, they do not have access to radius for credential assignemnt.
|
||||
If you require RADIUS usage with a service account you *may* need to use EAP-TLS or some other
|
||||
authentication method.
|
||||
|
||||
## Account Credential Configuration
|
||||
|
||||
For an account to use RADIUS they must first generate a RADIUS secret unique
|
||||
to that account. By default, all accounts can self-create this secret.
|
||||
|
||||
kanidm account radius generate_secret --name william william
|
||||
kanidm account radius show_secret --name william william
|
||||
kanidm person radius generate_secret --name william william
|
||||
kanidm person radius show_secret --name william william
|
||||
|
||||
## Account Group Configuration
|
||||
|
||||
|
@ -82,17 +92,17 @@ To read these secrets, the RADIUS server requires an account with the
|
|||
correct privileges. This can be created and assigned through the group
|
||||
"idm_radius_servers", which is provided by default.
|
||||
|
||||
First, create the account and add it to the group:
|
||||
First, create the service account and add it to the group:
|
||||
|
||||
```shell
|
||||
kanidm account create --name admin radius_service_account "Radius Service Account"
|
||||
kanidm service-account create --name admin radius_service_account "Radius Service Account"
|
||||
kanidm group add_members --name admin idm_radius_servers radius_service_account
|
||||
```
|
||||
|
||||
Now reset the account password, using the `admin` account:
|
||||
|
||||
```shell
|
||||
kanidm account credential update --name admin radius_service_account
|
||||
kanidm service-account credential generate-pw --name admin radius_service_account
|
||||
```
|
||||
|
||||
## Deploying a RADIUS Container
|
||||
|
|
|
@ -10,8 +10,8 @@ The intent of the Kanidm project is to:
|
|||
* Make system, network, application and web authentication easy and accessible.
|
||||
|
||||
|
||||
{{#template
|
||||
templates/kani-warning.md
|
||||
{{#template
|
||||
templates/kani-warning.md
|
||||
imagepath=images
|
||||
title=NOTICE
|
||||
text=This is a pre-release project. While all effort has been made to ensure no data loss or security flaws, you should still be careful when using this in your environment.
|
||||
|
@ -23,8 +23,8 @@ Looking for the `rustdoc` documentation for the libraries themselves? [Click her
|
|||
|
||||
## Why do I want Kanidm?
|
||||
|
||||
Whether you work in a business, a volunteer organisation, or are an enthusiast who manages
|
||||
their personal services, you need methods of authenticating and identifying
|
||||
Whether you work in a business, a volunteer organisation, or are an enthusiast who manages
|
||||
their personal services, you need methods of authenticating and identifying
|
||||
to your systems, and subsequently, ways to determine what authorisation and privileges you have
|
||||
while accessing these systems.
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
# POSIX Accounts and Groups
|
||||
|
||||
Kanidm has features that enable its accounts and groups to be consumed on
|
||||
POSIX-like machines, such as Linux, FreeBSD, or others.
|
||||
POSIX-like machines, such as Linux, FreeBSD, or others. Both service accounts
|
||||
and person accounts can be used on POSIX systems.
|
||||
|
||||
## Notes on POSIX Features
|
||||
|
||||
|
@ -38,8 +39,9 @@ two independent items. For example in /etc/passwd and /etc/group:
|
|||
# group
|
||||
william:x:654401105:
|
||||
|
||||
Other systems like FreeIPA use a plugin that generates a UPG as a database record on
|
||||
creation of the account.
|
||||
Other systems like FreeIPA use a plugin that generates a UPG as a seperate group entry on
|
||||
creation of the account. This means there are two entries for an account, and they must
|
||||
be kept in lock-step.
|
||||
|
||||
Kanidm does neither of these. As the GID number of the user must be unique, and a user
|
||||
implies the UPG must exist, we can generate UPG's on-demand from the account.
|
||||
|
@ -48,19 +50,18 @@ UPG - given the nature of a user private group, this is the point.
|
|||
|
||||
### GID Number Generation
|
||||
|
||||
In the future, Kanidm plans to have asynchronous replication as a feature between writable
|
||||
Kanidm will have asynchronous replication as a feature between writable
|
||||
database servers. In this case, we need to be able to allocate stable and reliable
|
||||
GID numbers to accounts on replicas that may not be in continual communication.
|
||||
|
||||
To do this, we use the last 32 bits of the account or group's UUID to
|
||||
generate the GID number.
|
||||
To do this, we use the last 32 bits of the account or group's UUID to generate the GID number.
|
||||
|
||||
A valid concern is the possibility of duplication in the lower 32 bits. Given the
|
||||
birthday problem, if you have 77,000 groups and accounts, you have a 50% chance
|
||||
of duplication. With 50,000 you have a 20% chance, 9,300 you have a 1% chance and
|
||||
with 2900 you have a 0.1% chance.
|
||||
|
||||
We advise that if you have a site with >10,000 users you should use an external system
|
||||
We advise that if you have a site with >10,000 users you should use an external system
|
||||
to allocate GID numbers serially or consistently to avoid potential duplication events.
|
||||
|
||||
This design decision is made as most small sites will benefit greatly from the
|
||||
|
@ -72,19 +73,25 @@ capable of supplying this kind of data in batch jobs.
|
|||
|
||||
### Enabling POSIX Attributes on Accounts
|
||||
|
||||
To enable POSIX account features and IDs on an account, you require the permission
|
||||
To enable POSIX account features and IDs on an account, you require the permission
|
||||
`idm_account_unix_extend_priv`. This is provided to `idm_admins` in the default database.
|
||||
|
||||
You can then use the following command to enable POSIX extensions.
|
||||
You can then use the following command to enable POSIX extensions on a person or service account.
|
||||
|
||||
kanidm account posix set --name idm_admin <account_id> [--shell SHELL --gidnumber GID]
|
||||
kanidm account posix set --name idm_admin demo_user
|
||||
kanidm account posix set --name idm_admin demo_user --shell /bin/zsh
|
||||
kanidm account posix set --name idm_admin demo_user --gidnumber 2001
|
||||
kanidm [person OR service-account] posix set --name idm_admin <account_id> [--shell SHELL --gidnumber GID]
|
||||
|
||||
kanidm person posix set --name idm_admin demo_user
|
||||
kanidm person posix set --name idm_admin demo_user --shell /bin/zsh
|
||||
kanidm person posix set --name idm_admin demo_user --gidnumber 2001
|
||||
|
||||
kanidm service-account posix set --name idm_admin demo_account
|
||||
kanidm service-account posix set --name idm_admin demo_account --shell /bin/zsh
|
||||
kanidm service-account posix set --name idm_admin demo_account --gidnumber 2001
|
||||
|
||||
You can view the accounts POSIX token details with:
|
||||
|
||||
kanidm account posix show --name anonymous demo_user
|
||||
kanidm person posix show --name anonymous demo_user
|
||||
kanidm service-account posix show --name anonymous demo_account
|
||||
|
||||
### Enabling POSIX Attributes on Groups
|
||||
|
||||
|
@ -115,4 +122,4 @@ with Kanidm accounts may fail with an error such as:
|
|||
|
||||
This is a fault in Podman and how it attempts to provide non-root containers, when UID/GIDs
|
||||
are greater than 65535. In this case you may manually allocate your users GID number to be
|
||||
between 1000 - 65535, which may not trigger the fault.
|
||||
between 1000 - 65535, which may not trigger the fault.
|
||||
|
|
|
@ -29,15 +29,15 @@ Currently they stay up to 1 week before they are removed.
|
|||
|
||||
You can display all items in the Recycle Bin with:
|
||||
|
||||
kanidm recycle_bin list --name admin
|
||||
kanidm recycle-bin list --name admin
|
||||
|
||||
You can show a single items with:
|
||||
You can show a single item with:
|
||||
|
||||
kanidm recycle_bin get --name admin <id>
|
||||
kanidm recycle-bin get --name admin <uuid>
|
||||
|
||||
An entry can be revived with:
|
||||
|
||||
kanidm recycle_bin revive --name admin <id>
|
||||
kanidm recycle-bin revive --name admin <uuid>
|
||||
|
||||
## Edge Cases
|
||||
|
||||
|
@ -71,4 +71,4 @@ These issues could be looked at again in the future, but for now we think that d
|
|||
groups is rare - we expect recycle bin to save you in "opps" moments, and in a majority
|
||||
of cases you may delete a group or a user and then restore them. To handle this series
|
||||
of steps requires extra code complexity in how we flag operations. For more,
|
||||
see [This issue on github](https://github.com/kanidm/kanidm/issues/177).
|
||||
see [This issue on github](https://github.com/kanidm/kanidm/issues/177).
|
||||
|
|
|
@ -104,7 +104,7 @@ You need a configuration file in the volume named `server.toml`. (Within the con
|
|||
|
||||
An example is located in [examples/server.toml](https://github.com/kanidm/kanidm/blob/master/examples/server.toml).
|
||||
|
||||
{{#template
|
||||
{{#template
|
||||
templates/kani-warning.md
|
||||
imagepath=images
|
||||
title=Warning!
|
||||
|
|
|
@ -1,33 +1,33 @@
|
|||
# SSH Key Distribution
|
||||
|
||||
To support SSH authentication securely to a large set of hosts running SSH, we support
|
||||
distribution of SSH public keys via the Kanidm server.
|
||||
To support SSH authentication securely to a large set of hosts running SSH, we support
|
||||
distribution of SSH public keys via the Kanidm server. Both persons and service accounts
|
||||
support SSH public keys on their accounts.
|
||||
|
||||
## Configuring Accounts
|
||||
|
||||
To view the current SSH public keys on accounts, you can use:
|
||||
|
||||
kanidm account ssh list_publickeys --name <login user> <account to view>
|
||||
kanidm account ssh list_publickeys --name idm_admin william
|
||||
kanidm person|service-account ssh list_publickeys --name <login user> <account to view>
|
||||
kanidm person|service-account ssh list_publickeys --name idm_admin william
|
||||
|
||||
All users by default can self-manage their SSH public keys. To upload a key, a command like this
|
||||
is the best way to do so:
|
||||
|
||||
kanidm account ssh add_publickey --name william william 'test-key' "`cat ~/.ssh/id_rsa.pub`"
|
||||
kanidm person|service-account ssh add_publickey --name william william 'test-key' "`cat ~/.ssh/id_rsa.pub`"
|
||||
|
||||
To remove (revoke) an SSH public key, delete them by the tag name:
|
||||
|
||||
kanidm account ssh delete_publickey --name william william 'test-key'
|
||||
kanidm person|service-account ssh delete_publickey --name william william 'test-key'
|
||||
|
||||
## Security Notes
|
||||
|
||||
As a security feature, Kanidm validates *all* public keys to ensure they are valid SSH public keys.
|
||||
Uploading a private key or other data will be rejected. For example:
|
||||
|
||||
kanidm account ssh add_publickey --name william william 'test-key' "invalid"
|
||||
kanidm person|service-account ssh add_publickey --name william william 'test-key' "invalid"
|
||||
Enter password:
|
||||
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value:
|
||||
Http(400, Some(SchemaViolation(InvalidAttributeSyntax)))', src/libcore/result.rs:1084:5
|
||||
... Some(SchemaViolation(InvalidAttributeSyntax)))' ...
|
||||
|
||||
## Server Configuration
|
||||
|
||||
|
@ -101,4 +101,4 @@ contain the lines:
|
|||
Restart sshd, and then attempt to authenticate with the keys.
|
||||
|
||||
It's highly recommended you keep your client configuration and sshd_configuration in a configuration
|
||||
management tool such as salt or ansible.
|
||||
management tool such as salt or ansible.
|
||||
|
|
|
@ -42,6 +42,9 @@ use webauthn_rs_proto::{
|
|||
PublicKeyCredential, RegisterPublicKeyCredential, RequestChallengeResponse,
|
||||
};
|
||||
|
||||
mod person;
|
||||
mod service_account;
|
||||
|
||||
pub const APPLICATION_JSON: &str = "application/json";
|
||||
pub const KOPID: &str = "X-KANIDM-OPID";
|
||||
pub const KSESSIONID: &str = "X-KANIDM-AUTH-SESSION-ID";
|
||||
|
@ -1300,8 +1303,6 @@ impl KanidmClient {
|
|||
}
|
||||
|
||||
pub async fn idm_group_unix_token_get(&self, id: &str) -> Result<UnixGroupToken, ClientError> {
|
||||
// Format doesn't work in async
|
||||
// format!("/v1/account/{}/_unix/_token", id).as_str()
|
||||
self.perform_get_request(["/v1/group/", id, "/_unix/_token"].concat().as_str())
|
||||
.await
|
||||
}
|
||||
|
@ -1312,33 +1313,6 @@ impl KanidmClient {
|
|||
}
|
||||
|
||||
// ==== ACCOUNTS
|
||||
pub async fn idm_account_list(&self) -> Result<Vec<Entry>, ClientError> {
|
||||
self.perform_get_request("/v1/account").await
|
||||
}
|
||||
|
||||
pub async fn idm_account_create(&self, name: &str, dn: &str) -> Result<(), ClientError> {
|
||||
let mut new_acct = Entry {
|
||||
attrs: BTreeMap::new(),
|
||||
};
|
||||
new_acct
|
||||
.attrs
|
||||
.insert("name".to_string(), vec![name.to_string()]);
|
||||
new_acct
|
||||
.attrs
|
||||
.insert("displayname".to_string(), vec![dn.to_string()]);
|
||||
self.perform_post_request("/v1/account", new_acct).await
|
||||
}
|
||||
|
||||
pub async fn idm_account_set_password(&self, cleartext: String) -> Result<(), ClientError> {
|
||||
let s = SingleStringRequest { value: cleartext };
|
||||
|
||||
self.perform_post_request("/v1/self/_credential/primary/set_password", s)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_account_set_displayname(&self, id: &str, dn: &str) -> Result<(), ClientError> {
|
||||
self.idm_account_set_attr(id, "displayname", &[dn]).await
|
||||
}
|
||||
|
||||
pub async fn idm_account_unix_token_get(&self, id: &str) -> Result<UnixUserToken, ClientError> {
|
||||
// Format doesn't work in async
|
||||
|
@ -1347,293 +1321,12 @@ impl KanidmClient {
|
|||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_account_delete(&self, id: &str) -> Result<(), ClientError> {
|
||||
self.perform_delete_request(["/v1/account/", id].concat().as_str())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_account_get(&self, id: &str) -> Result<Option<Entry>, ClientError> {
|
||||
self.perform_get_request(format!("/v1/account/{}", id).as_str())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_account_add_attr(
|
||||
&self,
|
||||
id: &str,
|
||||
attr: &str,
|
||||
values: &[&str],
|
||||
) -> Result<(), ClientError> {
|
||||
let msg: Vec<_> = values.iter().map(|v| (*v).to_string()).collect();
|
||||
self.perform_post_request(format!("/v1/account/{}/_attr/{}", id, attr).as_str(), msg)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_account_set_attr(
|
||||
&self,
|
||||
id: &str,
|
||||
attr: &str,
|
||||
values: &[&str],
|
||||
) -> Result<(), ClientError> {
|
||||
let m: Vec<_> = values.iter().map(|v| (*v).to_string()).collect();
|
||||
self.perform_put_request(format!("/v1/account/{}/_attr/{}", id, attr).as_str(), m)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_account_get_attr(
|
||||
&self,
|
||||
id: &str,
|
||||
attr: &str,
|
||||
) -> Result<Option<Vec<String>>, ClientError> {
|
||||
self.perform_get_request(format!("/v1/account/{}/_attr/{}", id, attr).as_str())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_account_purge_attr(&self, id: &str, attr: &str) -> Result<(), ClientError> {
|
||||
self.perform_delete_request(format!("/v1/account/{}/_attr/{}", id, attr).as_str())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_account_primary_credential_set_password(
|
||||
&self,
|
||||
id: &str,
|
||||
pw: &str,
|
||||
) -> Result<SetCredentialResponse, ClientError> {
|
||||
let r = SetCredentialRequest::Password(pw.to_string());
|
||||
self.perform_put_request(
|
||||
format!("/v1/account/{}/_credential/primary", id).as_str(),
|
||||
r,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_account_primary_credential_import_password(
|
||||
&self,
|
||||
id: &str,
|
||||
pw: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
self.perform_put_request(
|
||||
format!("/v1/account/{}/_attr/password_import", id).as_str(),
|
||||
vec![pw.to_string()],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_account_primary_credential_set_generated(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<String, ClientError> {
|
||||
let r = SetCredentialRequest::GeneratePassword;
|
||||
let res: Result<SetCredentialResponse, ClientError> = self
|
||||
.perform_put_request(
|
||||
format!("/v1/account/{}/_credential/primary", id).as_str(),
|
||||
r,
|
||||
)
|
||||
.await;
|
||||
match res {
|
||||
Ok(SetCredentialResponse::Token(p)) => Ok(p),
|
||||
Ok(_) => Err(ClientError::EmptyResponse),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
// Reg intent for totp
|
||||
pub async fn idm_account_primary_credential_generate_totp(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<(Uuid, TotpSecret), ClientError> {
|
||||
let r = SetCredentialRequest::TotpGenerate;
|
||||
let res: Result<SetCredentialResponse, ClientError> = self
|
||||
.perform_put_request(
|
||||
format!("/v1/account/{}/_credential/primary", id).as_str(),
|
||||
r,
|
||||
)
|
||||
.await;
|
||||
match res {
|
||||
Ok(SetCredentialResponse::TotpCheck(u, s)) => Ok((u, s)),
|
||||
Ok(_) => Err(ClientError::EmptyResponse),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the totp
|
||||
pub async fn idm_account_primary_credential_verify_totp(
|
||||
&self,
|
||||
id: &str,
|
||||
otp: u32,
|
||||
session: Uuid,
|
||||
) -> Result<(), ClientError> {
|
||||
let r = SetCredentialRequest::TotpVerify(session, otp);
|
||||
let res: Result<SetCredentialResponse, ClientError> = self
|
||||
.perform_put_request(
|
||||
format!("/v1/account/{}/_credential/primary", id).as_str(),
|
||||
r,
|
||||
)
|
||||
.await;
|
||||
match res {
|
||||
Ok(SetCredentialResponse::Success) => Ok(()),
|
||||
Ok(SetCredentialResponse::TotpCheck(u, s)) => Err(ClientError::TotpVerifyFailed(u, s)),
|
||||
Ok(SetCredentialResponse::TotpInvalidSha1(u)) => Err(ClientError::TotpInvalidSha1(u)),
|
||||
Ok(_) => Err(ClientError::EmptyResponse),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
// Accept a sha1 totp
|
||||
pub async fn idm_account_primary_credential_accept_sha1_totp(
|
||||
&self,
|
||||
id: &str,
|
||||
session: Uuid,
|
||||
) -> Result<(), ClientError> {
|
||||
let r = SetCredentialRequest::TotpAcceptSha1(session);
|
||||
let res: Result<SetCredentialResponse, ClientError> = self
|
||||
.perform_put_request(
|
||||
format!("/v1/account/{}/_credential/primary", id).as_str(),
|
||||
r,
|
||||
)
|
||||
.await;
|
||||
match res {
|
||||
Ok(SetCredentialResponse::Success) => Ok(()),
|
||||
Ok(_) => Err(ClientError::EmptyResponse),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn idm_account_primary_credential_remove_totp(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
let r = SetCredentialRequest::TotpRemove;
|
||||
let res: Result<SetCredentialResponse, ClientError> = self
|
||||
.perform_put_request(
|
||||
format!("/v1/account/{}/_credential/primary", id).as_str(),
|
||||
r,
|
||||
)
|
||||
.await;
|
||||
match res {
|
||||
Ok(SetCredentialResponse::Success) => Ok(()),
|
||||
Ok(_) => Err(ClientError::EmptyResponse),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
pub async fn idm_account_primary_credential_register_webauthn(
|
||||
&self,
|
||||
id: &str,
|
||||
label: &str,
|
||||
) -> Result<(Uuid, CreationChallengeResponse), ClientError> {
|
||||
let r = SetCredentialRequest::WebauthnBegin(label.to_string());
|
||||
let res: Result<SetCredentialResponse, ClientError> = self
|
||||
.perform_put_request(
|
||||
format!("/v1/account/{}/_credential/primary", id).as_str(),
|
||||
r,
|
||||
)
|
||||
.await;
|
||||
match res {
|
||||
Ok(SetCredentialResponse::WebauthnCreateChallenge(u, s)) => Ok((u, s)),
|
||||
Ok(_) => Err(ClientError::EmptyResponse),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn idm_account_primary_credential_complete_webuthn_registration(
|
||||
&self,
|
||||
id: &str,
|
||||
rego: RegisterPublicKeyCredential,
|
||||
session: Uuid,
|
||||
) -> Result<(), ClientError> {
|
||||
let r = SetCredentialRequest::WebauthnRegister(session, rego);
|
||||
let res: Result<SetCredentialResponse, ClientError> = self
|
||||
.perform_put_request(
|
||||
format!("/v1/account/{}/_credential/primary", id).as_str(),
|
||||
r,
|
||||
)
|
||||
.await;
|
||||
match res {
|
||||
Ok(SetCredentialResponse::Success) => Ok(()),
|
||||
Ok(_) => Err(ClientError::EmptyResponse),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn idm_account_primary_credential_remove_webauthn(
|
||||
&self,
|
||||
id: &str,
|
||||
label: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
let r = SetCredentialRequest::WebauthnRemove(label.to_string());
|
||||
let res: Result<SetCredentialResponse, ClientError> = self
|
||||
.perform_put_request(
|
||||
format!("/v1/account/{}/_credential/primary", id).as_str(),
|
||||
r,
|
||||
)
|
||||
.await;
|
||||
match res {
|
||||
Ok(SetCredentialResponse::Success) => Ok(()),
|
||||
Ok(_) => Err(ClientError::EmptyResponse),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
pub async fn idm_account_primary_credential_generate_backup_code(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<Vec<String>, ClientError> {
|
||||
let r = SetCredentialRequest::BackupCodeGenerate;
|
||||
let res: Result<SetCredentialResponse, ClientError> = self
|
||||
.perform_put_request(
|
||||
format!("/v1/account/{}/_credential/primary", id).as_str(),
|
||||
r,
|
||||
)
|
||||
.await;
|
||||
match res {
|
||||
Ok(SetCredentialResponse::BackupCodes(s)) => Ok(s),
|
||||
Ok(_) => Err(ClientError::EmptyResponse),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn idm_account_primary_credential_remove_backup_code(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
let r = SetCredentialRequest::BackupCodeRemove;
|
||||
let res: Result<SetCredentialResponse, ClientError> = self
|
||||
.perform_put_request(
|
||||
format!("/v1/account/{}/_credential/primary", id).as_str(),
|
||||
r,
|
||||
)
|
||||
.await;
|
||||
match res {
|
||||
Ok(SetCredentialResponse::Success) => Ok(()),
|
||||
Ok(_) => Err(ClientError::EmptyResponse),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn idm_account_get_credential_status(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<CredentialStatus, ClientError> {
|
||||
let res: Result<CredentialStatus, ClientError> = self
|
||||
.perform_get_request(format!("/v1/account/{}/_credential/_status", id).as_str())
|
||||
.await;
|
||||
res.and_then(|cs| {
|
||||
if cs.creds.is_empty() {
|
||||
Err(ClientError::EmptyResponse)
|
||||
} else {
|
||||
Ok(cs)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// == new credential update session code.
|
||||
pub async fn idm_account_credential_update_intent(
|
||||
pub async fn idm_person_account_credential_update_intent(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<CUIntentToken, ClientError> {
|
||||
self.perform_get_request(format!("/v1/account/{}/_credential/_update_intent", id).as_str())
|
||||
self.perform_get_request(format!("/v1/person/{}/_credential/_update_intent", id).as_str())
|
||||
.await
|
||||
}
|
||||
|
||||
|
@ -1641,7 +1334,7 @@ impl KanidmClient {
|
|||
&self,
|
||||
id: &str,
|
||||
) -> Result<(CUSessionToken, CUStatus), ClientError> {
|
||||
self.perform_get_request(format!("/v1/account/{}/_credential/_update", id).as_str())
|
||||
self.perform_get_request(format!("/v1/person/{}/_credential/_update", id).as_str())
|
||||
.await
|
||||
}
|
||||
|
||||
|
@ -1774,26 +1467,7 @@ impl KanidmClient {
|
|||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_account_radius_credential_get(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<Option<String>, ClientError> {
|
||||
self.perform_get_request(format!("/v1/account/{}/_radius", id).as_str())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_account_radius_credential_regenerate(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<String, ClientError> {
|
||||
self.perform_post_request(format!("/v1/account/{}/_radius", id).as_str(), ())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_account_radius_credential_delete(&self, id: &str) -> Result<(), ClientError> {
|
||||
self.perform_delete_request(format!("/v1/account/{}/_radius", id).as_str())
|
||||
.await
|
||||
}
|
||||
// == radius
|
||||
|
||||
pub async fn idm_account_radius_token_get(
|
||||
&self,
|
||||
|
@ -1803,36 +1477,6 @@ impl KanidmClient {
|
|||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_account_unix_extend(
|
||||
&self,
|
||||
id: &str,
|
||||
gidnumber: Option<u32>,
|
||||
shell: Option<&str>,
|
||||
) -> Result<(), ClientError> {
|
||||
let ux = AccountUnixExtend {
|
||||
shell: shell.map(str::to_string),
|
||||
gidnumber,
|
||||
};
|
||||
self.perform_post_request(format!("/v1/account/{}/_unix", id).as_str(), ux)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_account_unix_cred_put(&self, id: &str, cred: &str) -> Result<(), ClientError> {
|
||||
let req = SingleStringRequest {
|
||||
value: cred.to_string(),
|
||||
};
|
||||
self.perform_put_request(
|
||||
["/v1/account/", id, "/_unix/_credential"].concat().as_str(),
|
||||
req,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_account_unix_cred_delete(&self, id: &str) -> Result<(), ClientError> {
|
||||
self.perform_delete_request(["/v1/account/", id, "/_unix/_credential"].concat().as_str())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_account_unix_cred_verify(
|
||||
&self,
|
||||
id: &str,
|
||||
|
@ -1845,64 +1489,7 @@ impl KanidmClient {
|
|||
.await
|
||||
}
|
||||
|
||||
/*
|
||||
pub async fn idm_account_orgperson_extend(
|
||||
&self,
|
||||
id: &str,
|
||||
mail: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
let x = AccountOrgPersonExtend {
|
||||
mail: mail.to_string(),
|
||||
};
|
||||
self.perform_post_request(format!("/v1/account/{}/_orgperson", id).as_str(), x)
|
||||
.await
|
||||
}
|
||||
*/
|
||||
|
||||
pub async fn idm_account_get_ssh_pubkeys(&self, id: &str) -> Result<Vec<String>, ClientError> {
|
||||
self.perform_get_request(format!("/v1/account/{}/_ssh_pubkeys", id).as_str())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_account_post_ssh_pubkey(
|
||||
&self,
|
||||
id: &str,
|
||||
tag: &str,
|
||||
pubkey: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
let sk = (tag.to_string(), pubkey.to_string());
|
||||
self.perform_post_request(format!("/v1/account/{}/_ssh_pubkeys", id).as_str(), sk)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_account_person_extend(
|
||||
&self,
|
||||
id: &str,
|
||||
mail: Option<&[String]>,
|
||||
legalname: Option<&str>,
|
||||
) -> Result<(), ClientError> {
|
||||
let px = AccountPersonSet {
|
||||
mail: mail.map(|s| s.to_vec()),
|
||||
legalname: legalname.map(str::to_string),
|
||||
};
|
||||
self.perform_post_request(format!("/v1/account/{}/_person/_extend", id).as_str(), px)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_account_person_set(
|
||||
&self,
|
||||
id: &str,
|
||||
mail: Option<&[String]>,
|
||||
legalname: Option<&str>,
|
||||
) -> Result<(), ClientError> {
|
||||
let px = AccountPersonSet {
|
||||
mail: mail.map(|s| s.to_vec()),
|
||||
legalname: legalname.map(str::to_string),
|
||||
};
|
||||
self.perform_post_request(format!("/v1/account/{}/_person/_set", id).as_str(), px)
|
||||
.await
|
||||
}
|
||||
|
||||
// == generic ssh key handlers
|
||||
pub async fn idm_account_get_ssh_pubkey(
|
||||
&self,
|
||||
id: &str,
|
||||
|
@ -1912,12 +1499,8 @@ impl KanidmClient {
|
|||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_account_delete_ssh_pubkey(
|
||||
&self,
|
||||
id: &str,
|
||||
tag: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
self.perform_delete_request(format!("/v1/account/{}/_ssh_pubkeys/{}", id, tag).as_str())
|
||||
pub async fn idm_account_get_ssh_pubkeys(&self, id: &str) -> Result<Vec<String>, ClientError> {
|
||||
self.perform_get_request(format!("/v1/account/{}/_ssh_pubkeys", id).as_str())
|
||||
.await
|
||||
}
|
||||
|
||||
|
|
236
kanidm_client/src/person.rs
Normal file
236
kanidm_client/src/person.rs
Normal file
|
@ -0,0 +1,236 @@
|
|||
use crate::ClientError;
|
||||
use crate::KanidmClient;
|
||||
use kanidm_proto::v1::AccountUnixExtend;
|
||||
use kanidm_proto::v1::CredentialStatus;
|
||||
use kanidm_proto::v1::Entry;
|
||||
use kanidm_proto::v1::SingleStringRequest;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
impl KanidmClient {
|
||||
pub async fn idm_person_account_list(&self) -> Result<Vec<Entry>, ClientError> {
|
||||
self.perform_get_request("/v1/person").await
|
||||
}
|
||||
|
||||
pub async fn idm_person_account_get(&self, id: &str) -> Result<Option<Entry>, ClientError> {
|
||||
self.perform_get_request(format!("/v1/person/{}", id).as_str())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_person_account_create(
|
||||
&self,
|
||||
name: &str,
|
||||
displayname: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
let mut new_acct = Entry {
|
||||
attrs: BTreeMap::new(),
|
||||
};
|
||||
new_acct
|
||||
.attrs
|
||||
.insert("name".to_string(), vec![name.to_string()]);
|
||||
new_acct
|
||||
.attrs
|
||||
.insert("displayname".to_string(), vec![displayname.to_string()]);
|
||||
self.perform_post_request("/v1/person", new_acct).await
|
||||
}
|
||||
|
||||
pub async fn idm_person_account_update(
|
||||
&self,
|
||||
id: &str,
|
||||
newname: Option<&str>,
|
||||
displayname: Option<&str>,
|
||||
legalname: Option<&str>,
|
||||
mail: Option<&[String]>,
|
||||
) -> Result<(), ClientError> {
|
||||
let mut update_entry = Entry {
|
||||
attrs: BTreeMap::new(),
|
||||
};
|
||||
|
||||
if let Some(newname) = newname {
|
||||
update_entry
|
||||
.attrs
|
||||
.insert("name".to_string(), vec![newname.to_string()]);
|
||||
}
|
||||
if let Some(newdisplayname) = displayname {
|
||||
update_entry
|
||||
.attrs
|
||||
.insert("displayname".to_string(), vec![newdisplayname.to_string()]);
|
||||
}
|
||||
if let Some(newlegalname) = legalname {
|
||||
update_entry
|
||||
.attrs
|
||||
.insert("legalname".to_string(), vec![newlegalname.to_string()]);
|
||||
}
|
||||
if let Some(mail) = mail {
|
||||
update_entry.attrs.insert("mail".to_string(), mail.to_vec());
|
||||
}
|
||||
|
||||
self.perform_patch_request(format!("/v1/person/{}", id).as_str(), update_entry)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_person_account_delete(&self, id: &str) -> Result<(), ClientError> {
|
||||
self.perform_delete_request(["/v1/person/", id].concat().as_str())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_person_account_add_attr(
|
||||
&self,
|
||||
id: &str,
|
||||
attr: &str,
|
||||
values: &[&str],
|
||||
) -> Result<(), ClientError> {
|
||||
let msg: Vec<_> = values.iter().map(|v| (*v).to_string()).collect();
|
||||
self.perform_post_request(format!("/v1/person/{}/_attr/{}", id, attr).as_str(), msg)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_person_account_set_attr(
|
||||
&self,
|
||||
id: &str,
|
||||
attr: &str,
|
||||
values: &[&str],
|
||||
) -> Result<(), ClientError> {
|
||||
let m: Vec<_> = values.iter().map(|v| (*v).to_string()).collect();
|
||||
self.perform_put_request(format!("/v1/person/{}/_attr/{}", id, attr).as_str(), m)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_person_account_get_attr(
|
||||
&self,
|
||||
id: &str,
|
||||
attr: &str,
|
||||
) -> Result<Option<Vec<String>>, ClientError> {
|
||||
self.perform_get_request(format!("/v1/person/{}/_attr/{}", id, attr).as_str())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_person_account_purge_attr(
|
||||
&self,
|
||||
id: &str,
|
||||
attr: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
self.perform_delete_request(format!("/v1/person/{}/_attr/{}", id, attr).as_str())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_person_account_primary_credential_import_password(
|
||||
&self,
|
||||
id: &str,
|
||||
pw: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
self.perform_put_request(
|
||||
format!("/v1/person/{}/_attr/password_import", id).as_str(),
|
||||
vec![pw.to_string()],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_person_account_get_credential_status(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<CredentialStatus, ClientError> {
|
||||
let res: Result<CredentialStatus, ClientError> = self
|
||||
.perform_get_request(format!("/v1/person/{}/_credential/_status", id).as_str())
|
||||
.await;
|
||||
res.and_then(|cs| {
|
||||
if cs.creds.is_empty() {
|
||||
Err(ClientError::EmptyResponse)
|
||||
} else {
|
||||
Ok(cs)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// This helper calls through the credential update session wrappers to
|
||||
pub async fn idm_person_account_primary_credential_set_password(
|
||||
&self,
|
||||
id: &str,
|
||||
pw: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
let (session_tok, status) = self.idm_account_credential_update_begin(id).await?;
|
||||
trace!(?status);
|
||||
|
||||
let status = self
|
||||
.idm_account_credential_update_set_password(&session_tok, pw)
|
||||
.await?;
|
||||
trace!(?status);
|
||||
|
||||
self.idm_account_credential_update_commit(&session_tok)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_person_account_post_ssh_pubkey(
|
||||
&self,
|
||||
id: &str,
|
||||
tag: &str,
|
||||
pubkey: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
let sk = (tag.to_string(), pubkey.to_string());
|
||||
self.perform_post_request(format!("/v1/person/{}/_ssh_pubkeys", id).as_str(), sk)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_person_account_delete_ssh_pubkey(
|
||||
&self,
|
||||
id: &str,
|
||||
tag: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
self.perform_delete_request(format!("/v1/person/{}/_ssh_pubkeys/{}", id, tag).as_str())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_person_account_unix_extend(
|
||||
&self,
|
||||
id: &str,
|
||||
gidnumber: Option<u32>,
|
||||
shell: Option<&str>,
|
||||
) -> Result<(), ClientError> {
|
||||
let ux = AccountUnixExtend {
|
||||
shell: shell.map(str::to_string),
|
||||
gidnumber,
|
||||
};
|
||||
self.perform_post_request(format!("/v1/person/{}/_unix", id).as_str(), ux)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_person_account_unix_cred_put(
|
||||
&self,
|
||||
id: &str,
|
||||
cred: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
let req = SingleStringRequest {
|
||||
value: cred.to_string(),
|
||||
};
|
||||
self.perform_put_request(
|
||||
["/v1/person/", id, "/_unix/_credential"].concat().as_str(),
|
||||
req,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_person_account_unix_cred_delete(&self, id: &str) -> Result<(), ClientError> {
|
||||
self.perform_delete_request(["/v1/person/", id, "/_unix/_credential"].concat().as_str())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_account_radius_credential_get(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<Option<String>, ClientError> {
|
||||
self.perform_get_request(format!("/v1/person/{}/_radius", id).as_str())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_account_radius_credential_regenerate(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<String, ClientError> {
|
||||
self.perform_post_request(format!("/v1/person/{}/_radius", id).as_str(), ())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_account_radius_credential_delete(&self, id: &str) -> Result<(), ClientError> {
|
||||
self.perform_delete_request(format!("/v1/person/{}/_radius", id).as_str())
|
||||
.await
|
||||
}
|
||||
}
|
196
kanidm_client/src/service_account.rs
Normal file
196
kanidm_client/src/service_account.rs
Normal file
|
@ -0,0 +1,196 @@
|
|||
use crate::ClientError;
|
||||
use crate::KanidmClient;
|
||||
use kanidm_proto::v1::AccountUnixExtend;
|
||||
use kanidm_proto::v1::CredentialStatus;
|
||||
use kanidm_proto::v1::Entry;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
impl KanidmClient {
|
||||
pub async fn idm_service_account_list(&self) -> Result<Vec<Entry>, ClientError> {
|
||||
self.perform_get_request("/v1/service_account").await
|
||||
}
|
||||
|
||||
pub async fn idm_service_account_get(&self, id: &str) -> Result<Option<Entry>, ClientError> {
|
||||
self.perform_get_request(format!("/v1/service_account/{}", id).as_str())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_service_account_create(
|
||||
&self,
|
||||
name: &str,
|
||||
displayname: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
let mut new_acct = Entry {
|
||||
attrs: BTreeMap::new(),
|
||||
};
|
||||
new_acct
|
||||
.attrs
|
||||
.insert("name".to_string(), vec![name.to_string()]);
|
||||
new_acct
|
||||
.attrs
|
||||
.insert("displayname".to_string(), vec![displayname.to_string()]);
|
||||
self.perform_post_request("/v1/service_account", new_acct)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_service_account_delete(&self, id: &str) -> Result<(), ClientError> {
|
||||
self.perform_delete_request(["/v1/service_account/", id].concat().as_str())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_service_account_update(
|
||||
&self,
|
||||
id: &str,
|
||||
newname: Option<&str>,
|
||||
displayname: Option<&str>,
|
||||
mail: Option<&[String]>,
|
||||
) -> Result<(), ClientError> {
|
||||
let mut update_entry = Entry {
|
||||
attrs: BTreeMap::new(),
|
||||
};
|
||||
|
||||
if let Some(newname) = newname {
|
||||
update_entry
|
||||
.attrs
|
||||
.insert("name".to_string(), vec![newname.to_string()]);
|
||||
}
|
||||
if let Some(newdisplayname) = displayname {
|
||||
update_entry
|
||||
.attrs
|
||||
.insert("displayname".to_string(), vec![newdisplayname.to_string()]);
|
||||
}
|
||||
if let Some(mail) = mail {
|
||||
update_entry.attrs.insert("mail".to_string(), mail.to_vec());
|
||||
}
|
||||
|
||||
self.perform_patch_request(format!("/v1/service_account/{}", id).as_str(), update_entry)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_service_account_add_attr(
|
||||
&self,
|
||||
id: &str,
|
||||
attr: &str,
|
||||
values: &[&str],
|
||||
) -> Result<(), ClientError> {
|
||||
let msg: Vec<_> = values.iter().map(|v| (*v).to_string()).collect();
|
||||
self.perform_post_request(
|
||||
format!("/v1/service_account/{}/_attr/{}", id, attr).as_str(),
|
||||
msg,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_service_account_set_attr(
|
||||
&self,
|
||||
id: &str,
|
||||
attr: &str,
|
||||
values: &[&str],
|
||||
) -> Result<(), ClientError> {
|
||||
let m: Vec<_> = values.iter().map(|v| (*v).to_string()).collect();
|
||||
self.perform_put_request(
|
||||
format!("/v1/service_account/{}/_attr/{}", id, attr).as_str(),
|
||||
m,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_service_account_get_attr(
|
||||
&self,
|
||||
id: &str,
|
||||
attr: &str,
|
||||
) -> Result<Option<Vec<String>>, ClientError> {
|
||||
self.perform_get_request(format!("/v1/service_account/{}/_attr/{}", id, attr).as_str())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_service_account_purge_attr(
|
||||
&self,
|
||||
id: &str,
|
||||
attr: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
self.perform_delete_request(format!("/v1/service_account/{}/_attr/{}", id, attr).as_str())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_service_account_post_ssh_pubkey(
|
||||
&self,
|
||||
id: &str,
|
||||
tag: &str,
|
||||
pubkey: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
let sk = (tag.to_string(), pubkey.to_string());
|
||||
self.perform_post_request(
|
||||
format!("/v1/service_account/{}/_ssh_pubkeys", id).as_str(),
|
||||
sk,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_service_account_delete_ssh_pubkey(
|
||||
&self,
|
||||
id: &str,
|
||||
tag: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
self.perform_delete_request(
|
||||
format!("/v1/service_account/{}/_ssh_pubkeys/{}", id, tag).as_str(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_service_account_unix_extend(
|
||||
&self,
|
||||
id: &str,
|
||||
gidnumber: Option<u32>,
|
||||
shell: Option<&str>,
|
||||
) -> Result<(), ClientError> {
|
||||
let ux = AccountUnixExtend {
|
||||
shell: shell.map(str::to_string),
|
||||
gidnumber,
|
||||
};
|
||||
self.perform_post_request(format!("/v1/service_account/{}/_unix", id).as_str(), ux)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_service_account_into_person(&self, id: &str) -> Result<(), ClientError> {
|
||||
self.perform_post_request(
|
||||
format!("/v1/service_account/{}/_into_person", id).as_str(),
|
||||
(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_service_account_get_credential_status(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<CredentialStatus, ClientError> {
|
||||
let res: Result<CredentialStatus, ClientError> = self
|
||||
.perform_get_request(format!("/v1/service_account/{}/_credential/_status", id).as_str())
|
||||
.await;
|
||||
res.and_then(|cs| {
|
||||
if cs.creds.is_empty() {
|
||||
Err(ClientError::EmptyResponse)
|
||||
} else {
|
||||
Ok(cs)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn idm_service_account_generate_password(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<String, ClientError> {
|
||||
let res: Result<String, ClientError> = self
|
||||
.perform_get_request(
|
||||
format!("/v1/service_account/{}/_credential/_generate", id).as_str(),
|
||||
)
|
||||
.await;
|
||||
res.and_then(|pw| {
|
||||
if pw.is_empty() {
|
||||
Err(ClientError::EmptyResponse)
|
||||
} else {
|
||||
Ok(pw)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -84,7 +84,7 @@ impl From<String> for ConsoleOutputMode {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum MessageStatus {
|
||||
Failure,
|
||||
|
|
|
@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
|
|||
use std::collections::BTreeMap;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum CodeChallengeMethod {
|
||||
// default to plain if not requested as S256. Reject the auth?
|
||||
// plain
|
||||
|
@ -173,7 +173,7 @@ impl AccessTokenIntrospectResponse {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ResponseType {
|
||||
Code,
|
||||
|
@ -181,7 +181,7 @@ pub enum ResponseType {
|
|||
IdToken,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ResponseMode {
|
||||
Query,
|
||||
|
@ -192,7 +192,7 @@ fn response_modes_supported_default() -> Vec<ResponseMode> {
|
|||
vec![ResponseMode::Query, ResponseMode::Fragment]
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GrantType {
|
||||
#[serde(rename = "authorization_code")]
|
||||
|
@ -204,14 +204,14 @@ fn grant_types_supported_default() -> Vec<GrantType> {
|
|||
vec![GrantType::AuthorisationCode, GrantType::Implicit]
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum SubjectType {
|
||||
Pairwise,
|
||||
Public,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
// WE REFUSE TO SUPPORT NONE. DONT EVEN ASK. IT WONT HAPPEN.
|
||||
pub enum IdTokenSignAlg {
|
||||
|
@ -219,7 +219,7 @@ pub enum IdTokenSignAlg {
|
|||
RS256,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum TokenEndpointAuthMethod {
|
||||
ClientSecretPost,
|
||||
|
@ -232,7 +232,7 @@ fn token_endpoint_auth_methods_supported_default() -> Vec<TokenEndpointAuthMetho
|
|||
vec![TokenEndpointAuthMethod::ClientSecretBasic]
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum DisplayValue {
|
||||
Page,
|
||||
|
@ -241,7 +241,7 @@ pub enum DisplayValue {
|
|||
Wap,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
// https://openid.net/specs/openid-connect-core-1_0.html#ClaimTypes
|
||||
pub enum ClaimType {
|
||||
|
|
|
@ -12,7 +12,7 @@ use webauthn_rs_proto::{
|
|||
|
||||
/* ===== errors ===== */
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum SchemaError {
|
||||
NotImplemented,
|
||||
|
@ -22,12 +22,14 @@ pub enum SchemaError {
|
|||
InvalidAttribute(String),
|
||||
InvalidAttributeSyntax(String),
|
||||
AttributeNotValidForClass(String),
|
||||
SupplementsNotSatisfied(Vec<String>),
|
||||
ExcludesNotSatisfied(Vec<String>),
|
||||
EmptyFilter,
|
||||
Corrupted,
|
||||
PhantomAttribute(String),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum PluginError {
|
||||
AttrUnique(String),
|
||||
|
@ -37,7 +39,7 @@ pub enum PluginError {
|
|||
Oauth2Secrets,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ConsistencyError {
|
||||
Unknown,
|
||||
|
@ -452,12 +454,6 @@ pub struct AccountUnixExtend {
|
|||
pub shell: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct AccountPersonSet {
|
||||
pub mail: Option<Vec<String>>,
|
||||
pub legalname: Option<String>,
|
||||
}
|
||||
|
||||
/*
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct AccountOrgPersonExtend {
|
||||
|
@ -465,7 +461,7 @@ pub struct AccountOrgPersonExtend {
|
|||
}
|
||||
*/
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
pub enum CredentialDetailType {
|
||||
Password,
|
||||
GeneratedPassword,
|
||||
|
@ -552,7 +548,6 @@ impl fmt::Display for CredentialStatus {
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct BackupCodesView {
|
||||
// Or use SetCredentialResponse::BackupCodes?
|
||||
pub backup_codes: Vec<String>,
|
||||
}
|
||||
|
||||
|
@ -563,7 +558,7 @@ pub struct BackupCodesView {
|
|||
// the in memory server core entry type, without affecting the protoEntry type
|
||||
//
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
|
||||
pub struct Entry {
|
||||
pub attrs: BTreeMap<String, Vec<String>>,
|
||||
}
|
||||
|
@ -841,6 +836,7 @@ pub struct AuthResponse {
|
|||
}
|
||||
|
||||
// Types needed for setting credentials
|
||||
/*
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum SetCredentialRequest {
|
||||
|
@ -853,6 +849,7 @@ pub enum SetCredentialRequest {
|
|||
BackupCodeGenerate,
|
||||
BackupCodeRemove,
|
||||
}
|
||||
*/
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
|
@ -903,6 +900,7 @@ impl TotpSecret {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum SetCredentialResponse {
|
||||
|
@ -913,13 +911,14 @@ pub enum SetCredentialResponse {
|
|||
SecurityKeyCreateChallenge(Uuid, CreationChallengeResponse),
|
||||
BackupCodes(Vec<String>),
|
||||
}
|
||||
*/
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct CUIntentToken {
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct CUSessionToken {
|
||||
pub token: String,
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ impl CommonOpt {
|
|||
None => client_builder,
|
||||
};
|
||||
|
||||
let ca_path: Option<&str> = self.ca_path.as_ref().map(|p| p.to_str()).flatten();
|
||||
let ca_path: Option<&str> = self.ca_path.as_ref().and_then(|p| p.to_str());
|
||||
let client_builder = match ca_path {
|
||||
Some(p) => {
|
||||
debug!("Adding trusted CA cert {:?}", p);
|
||||
|
|
|
@ -17,20 +17,20 @@ use std::path::PathBuf;
|
|||
|
||||
include!("../opt/kanidm.rs");
|
||||
|
||||
pub mod account;
|
||||
pub mod common;
|
||||
pub mod domain;
|
||||
pub mod group;
|
||||
pub mod oauth2;
|
||||
pub mod person;
|
||||
pub mod raw;
|
||||
pub mod recycle;
|
||||
pub mod serviceaccount;
|
||||
pub mod session;
|
||||
|
||||
impl SelfOpt {
|
||||
pub fn debug(&self) -> bool {
|
||||
match self {
|
||||
SelfOpt::Whoami(copt) => copt.debug,
|
||||
SelfOpt::SetPassword(copt) => copt.debug,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,22 +55,6 @@ impl SelfOpt {
|
|||
Err(e) => println!("Error: {:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
SelfOpt::SetPassword(copt) => {
|
||||
let client = copt.to_client().await;
|
||||
|
||||
let password = match rpassword::prompt_password("Enter new password: ") {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
error!("Error -> {:?}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = client.idm_account_set_password(password).await {
|
||||
error!("Error -> {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -99,8 +83,9 @@ impl KanidmClientOpt {
|
|||
KanidmClientOpt::Logout(lopt) => lopt.debug(),
|
||||
KanidmClientOpt::Session { commands } => commands.debug(),
|
||||
KanidmClientOpt::CSelf { commands } => commands.debug(),
|
||||
KanidmClientOpt::Account { commands } => commands.debug(),
|
||||
KanidmClientOpt::Group { commands } => commands.debug(),
|
||||
KanidmClientOpt::Person { commands } => commands.debug(),
|
||||
KanidmClientOpt::ServiceAccount { commands } => commands.debug(),
|
||||
KanidmClientOpt::System { commands } => commands.debug(),
|
||||
KanidmClientOpt::Recycle { commands } => commands.debug(),
|
||||
KanidmClientOpt::Version {} => {
|
||||
|
@ -117,7 +102,8 @@ impl KanidmClientOpt {
|
|||
KanidmClientOpt::Logout(lopt) => lopt.exec().await,
|
||||
KanidmClientOpt::Session { commands } => commands.exec().await,
|
||||
KanidmClientOpt::CSelf { commands } => commands.exec().await,
|
||||
KanidmClientOpt::Account { commands } => commands.exec().await,
|
||||
KanidmClientOpt::Person { commands } => commands.exec().await,
|
||||
KanidmClientOpt::ServiceAccount { commands } => commands.exec().await,
|
||||
KanidmClientOpt::Group { commands } => commands.exec().await,
|
||||
KanidmClientOpt::System { commands } => commands.exec().await,
|
||||
KanidmClientOpt::Recycle { commands } => commands.exec().await,
|
||||
|
|
|
@ -125,7 +125,7 @@ impl Oauth2Opt {
|
|||
.idm_oauth2_rs_update(
|
||||
cbopt.nopt.name.as_str(),
|
||||
None,
|
||||
Some(&cbopt.displayname.as_str()),
|
||||
Some(cbopt.displayname.as_str()),
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
use crate::password_prompt;
|
||||
use crate::{
|
||||
AccountCredential, AccountOpt, AccountPerson, AccountPosix, AccountRadius, AccountSsh,
|
||||
AccountValidity,
|
||||
AccountCredential, AccountRadius, AccountSsh, AccountValidity, PersonOpt, PersonPosix,
|
||||
};
|
||||
use dialoguer::{theme::ColorfulTheme, Select};
|
||||
use dialoguer::{Confirm, Input, Password};
|
||||
use kanidm_client::ClientError::Http as ClientErrorHttp;
|
||||
use kanidm_client::KanidmClient;
|
||||
use kanidm_proto::messages::{AccountChangeMessage, ConsoleOutputMode, MessageStatus};
|
||||
use kanidm_proto::v1::OperationError::{InvalidAttribute, PasswordQuality};
|
||||
use kanidm_proto::v1::OperationError::PasswordQuality;
|
||||
use kanidm_proto::v1::{CUIntentToken, CURegState, CUSessionToken, CUStatus, TotpSecret};
|
||||
use qrcode::{render::unicode, QrCode};
|
||||
use std::fmt::{self, Debug};
|
||||
|
@ -19,34 +18,31 @@ use uuid::Uuid;
|
|||
|
||||
use webauthn_authenticator_rs::{u2fhid::U2FHid, WebauthnAuthenticator};
|
||||
|
||||
impl AccountOpt {
|
||||
impl PersonOpt {
|
||||
pub fn debug(&self) -> bool {
|
||||
match self {
|
||||
AccountOpt::Credential { commands } => commands.debug(),
|
||||
AccountOpt::Radius { commands } => match commands {
|
||||
PersonOpt::Credential { commands } => commands.debug(),
|
||||
PersonOpt::Radius { commands } => match commands {
|
||||
AccountRadius::Show(aro) => aro.copt.debug,
|
||||
AccountRadius::Generate(aro) => aro.copt.debug,
|
||||
AccountRadius::DeleteSecret(aro) => aro.copt.debug,
|
||||
},
|
||||
AccountOpt::Posix { commands } => match commands {
|
||||
AccountPosix::Show(apo) => apo.copt.debug,
|
||||
AccountPosix::Set(apo) => apo.copt.debug,
|
||||
AccountPosix::SetPassword(apo) => apo.copt.debug,
|
||||
PersonOpt::Posix { commands } => match commands {
|
||||
PersonPosix::Show(apo) => apo.copt.debug,
|
||||
PersonPosix::Set(apo) => apo.copt.debug,
|
||||
PersonPosix::SetPassword(apo) => apo.copt.debug,
|
||||
},
|
||||
AccountOpt::Person { commands } => match commands {
|
||||
AccountPerson::Extend(apo) => apo.copt.debug,
|
||||
AccountPerson::Set(apo) => apo.copt.debug,
|
||||
},
|
||||
AccountOpt::Ssh { commands } => match commands {
|
||||
PersonOpt::Ssh { commands } => match commands {
|
||||
AccountSsh::List(ano) => ano.copt.debug,
|
||||
AccountSsh::Add(ano) => ano.copt.debug,
|
||||
AccountSsh::Delete(ano) => ano.copt.debug,
|
||||
},
|
||||
AccountOpt::List(copt) => copt.debug,
|
||||
AccountOpt::Get(aopt) => aopt.copt.debug,
|
||||
AccountOpt::Delete(aopt) => aopt.copt.debug,
|
||||
AccountOpt::Create(aopt) => aopt.copt.debug,
|
||||
AccountOpt::Validity { commands } => match commands {
|
||||
PersonOpt::List(copt) => copt.debug,
|
||||
PersonOpt::Get(aopt) => aopt.copt.debug,
|
||||
PersonOpt::Update(aopt) => aopt.copt.debug,
|
||||
PersonOpt::Delete(aopt) => aopt.copt.debug,
|
||||
PersonOpt::Create(aopt) => aopt.copt.debug,
|
||||
PersonOpt::Validity { commands } => match commands {
|
||||
AccountValidity::Show(ano) => ano.copt.debug,
|
||||
AccountValidity::ExpireAt(ano) => ano.copt.debug,
|
||||
AccountValidity::BeginFrom(ano) => ano.copt.debug,
|
||||
|
@ -57,8 +53,8 @@ impl AccountOpt {
|
|||
pub async fn exec(&self) {
|
||||
match self {
|
||||
// id/cred/primary/set
|
||||
AccountOpt::Credential { commands } => commands.exec().await,
|
||||
AccountOpt::Radius { commands } => match commands {
|
||||
PersonOpt::Credential { commands } => commands.exec().await,
|
||||
PersonOpt::Radius { commands } => match commands {
|
||||
AccountRadius::Show(aopt) => {
|
||||
let client = aopt.copt.to_client().await;
|
||||
|
||||
|
@ -119,9 +115,9 @@ impl AccountOpt {
|
|||
}
|
||||
};
|
||||
}
|
||||
}, // end AccountOpt::Radius
|
||||
AccountOpt::Posix { commands } => match commands {
|
||||
AccountPosix::Show(aopt) => {
|
||||
}, // end PersonOpt::Radius
|
||||
PersonOpt::Posix { commands } => match commands {
|
||||
PersonPosix::Show(aopt) => {
|
||||
let client = aopt.copt.to_client().await;
|
||||
match client
|
||||
.idm_account_unix_token_get(aopt.aopts.account_id.as_str())
|
||||
|
@ -133,10 +129,10 @@ impl AccountOpt {
|
|||
}
|
||||
}
|
||||
}
|
||||
AccountPosix::Set(aopt) => {
|
||||
PersonPosix::Set(aopt) => {
|
||||
let client = aopt.copt.to_client().await;
|
||||
if let Err(e) = client
|
||||
.idm_account_unix_extend(
|
||||
.idm_person_account_unix_extend(
|
||||
aopt.aopts.account_id.as_str(),
|
||||
aopt.gidnumber,
|
||||
aopt.shell.as_deref(),
|
||||
|
@ -146,7 +142,7 @@ impl AccountOpt {
|
|||
error!("Error -> {:?}", e);
|
||||
}
|
||||
}
|
||||
AccountPosix::SetPassword(aopt) => {
|
||||
PersonPosix::SetPassword(aopt) => {
|
||||
let client = aopt.copt.to_client().await;
|
||||
let password = match password_prompt("Enter new posix (sudo) password: ") {
|
||||
Some(v) => v,
|
||||
|
@ -157,7 +153,7 @@ impl AccountOpt {
|
|||
};
|
||||
|
||||
if let Err(e) = client
|
||||
.idm_account_unix_cred_put(
|
||||
.idm_person_account_unix_cred_put(
|
||||
aopt.aopts.account_id.as_str(),
|
||||
password.as_str(),
|
||||
)
|
||||
|
@ -166,118 +162,8 @@ impl AccountOpt {
|
|||
error!("Error -> {:?}", e);
|
||||
}
|
||||
}
|
||||
}, // end AccountOpt::Posix
|
||||
|
||||
AccountOpt::Person { commands } => match commands {
|
||||
AccountPerson::Extend(aopt) => {
|
||||
let client = aopt.copt.to_client().await;
|
||||
let mut result_output = kanidm_proto::messages::AccountChangeMessage {
|
||||
output_mode: ConsoleOutputMode::Text,
|
||||
action: String::from("account_person_extend"),
|
||||
result: String::from("This is a filler message"),
|
||||
src_user: aopt
|
||||
.copt
|
||||
.username
|
||||
.to_owned()
|
||||
.unwrap_or(format!("{:?}", client.whoami().await)),
|
||||
dest_user: aopt.aopts.account_id.to_string(),
|
||||
status: kanidm_proto::messages::MessageStatus::Failure,
|
||||
};
|
||||
match client
|
||||
.idm_account_person_extend(
|
||||
aopt.aopts.account_id.as_str(),
|
||||
aopt.mail.as_deref(),
|
||||
aopt.legalname.as_deref(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
// TODO: JSON output for account person extend.
|
||||
Ok(_) => {
|
||||
result_output.status = kanidm_proto::messages::MessageStatus::Success;
|
||||
result_output.result = format!(
|
||||
"Successfully extended the user {}.",
|
||||
aopt.aopts.account_id
|
||||
);
|
||||
match &aopt.legalname {
|
||||
Some(legalname) => {
|
||||
result_output.result +=
|
||||
format!(" Set legalname to {}.", legalname).as_str()
|
||||
}
|
||||
_ => debug!("Didn't change legalname field."),
|
||||
};
|
||||
match &aopt.mail {
|
||||
Some(mail) => {
|
||||
result_output.result +=
|
||||
format!(" Set mail to {:?}.", mail.join(", ")).as_str()
|
||||
}
|
||||
_ => debug!("Didn't change mail field."),
|
||||
};
|
||||
println!("{}", result_output);
|
||||
}
|
||||
// TODO: consider a macro to unpack the KanidmClient result object
|
||||
Err(e) => {
|
||||
match e {
|
||||
ClientErrorHttp(_, result, _) => {
|
||||
if let Some(ia_error) = result {
|
||||
match ia_error {
|
||||
InvalidAttribute(msg) => {
|
||||
result_output.result =
|
||||
format!("Failed to set value: {}", msg)
|
||||
}
|
||||
_ => {
|
||||
result_output.result =
|
||||
format!("Operation Error - {:?}", ia_error)
|
||||
}
|
||||
};
|
||||
} else {
|
||||
result_output.result =
|
||||
format!("ClientError - {:?}", result);
|
||||
}
|
||||
}
|
||||
_ => result_output.result = format!("Error -> {:?}", e),
|
||||
};
|
||||
eprintln!("{:?}", result_output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: there should probably be an 'unset' variant of this
|
||||
AccountPerson::Set(aopt) => {
|
||||
let client = aopt.copt.to_client().await;
|
||||
let mut result_output = AccountChangeMessage {
|
||||
output_mode: ConsoleOutputMode::Text,
|
||||
action: String::from("account_person set"),
|
||||
result: String::from(""),
|
||||
src_user: aopt
|
||||
.copt
|
||||
.username
|
||||
.to_owned()
|
||||
.unwrap_or(format!("{:?}", client.whoami().await)),
|
||||
dest_user: aopt.aopts.account_id.to_string(),
|
||||
status: MessageStatus::Failure,
|
||||
};
|
||||
match client
|
||||
.idm_account_person_set(
|
||||
aopt.aopts.account_id.as_str(),
|
||||
aopt.mail.as_deref(),
|
||||
aopt.legalname.as_deref(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
result_output.status = kanidm_proto::messages::MessageStatus::Success;
|
||||
result_output.result = "set 'person' status on user".to_string();
|
||||
println!("{}", result_output)
|
||||
}
|
||||
// TODO: ponder macro'ing handling ClientError
|
||||
Err(e) => {
|
||||
result_output.result = format!("Error -> {:?}", e);
|
||||
eprintln!("{}", result_output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, // end AccountOpt::Person
|
||||
AccountOpt::Ssh { commands } => match commands {
|
||||
}, // end PersonOpt::Posix
|
||||
PersonOpt::Ssh { commands } => match commands {
|
||||
AccountSsh::List(aopt) => {
|
||||
let client = aopt.copt.to_client().await;
|
||||
|
||||
|
@ -294,7 +180,7 @@ impl AccountOpt {
|
|||
AccountSsh::Add(aopt) => {
|
||||
let client = aopt.copt.to_client().await;
|
||||
if let Err(e) = client
|
||||
.idm_account_post_ssh_pubkey(
|
||||
.idm_person_account_post_ssh_pubkey(
|
||||
aopt.aopts.account_id.as_str(),
|
||||
aopt.tag.as_str(),
|
||||
aopt.pubkey.as_str(),
|
||||
|
@ -307,7 +193,7 @@ impl AccountOpt {
|
|||
AccountSsh::Delete(aopt) => {
|
||||
let client = aopt.copt.to_client().await;
|
||||
if let Err(e) = client
|
||||
.idm_account_delete_ssh_pubkey(
|
||||
.idm_person_account_delete_ssh_pubkey(
|
||||
aopt.aopts.account_id.as_str(),
|
||||
aopt.tag.as_str(),
|
||||
)
|
||||
|
@ -316,23 +202,42 @@ impl AccountOpt {
|
|||
error!("Error -> {:?}", e);
|
||||
}
|
||||
}
|
||||
}, // end AccountOpt::Ssh
|
||||
AccountOpt::List(copt) => {
|
||||
}, // end PersonOpt::Ssh
|
||||
PersonOpt::List(copt) => {
|
||||
let client = copt.to_client().await;
|
||||
match client.idm_account_list().await {
|
||||
match client.idm_person_account_list().await {
|
||||
Ok(r) => r.iter().for_each(|ent| println!("{}", ent)),
|
||||
Err(e) => error!("Error -> {:?}", e),
|
||||
}
|
||||
}
|
||||
AccountOpt::Get(aopt) => {
|
||||
PersonOpt::Update(aopt) => {
|
||||
let client = aopt.copt.to_client().await;
|
||||
match client.idm_account_get(aopt.aopts.account_id.as_str()).await {
|
||||
match client
|
||||
.idm_person_account_update(
|
||||
aopt.aopts.account_id.as_str(),
|
||||
aopt.newname.as_deref(),
|
||||
aopt.displayname.as_deref(),
|
||||
aopt.legalname.as_deref(),
|
||||
aopt.mail.as_deref(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) => println!("Success"),
|
||||
Err(e) => error!("Error -> {:?}", e),
|
||||
}
|
||||
}
|
||||
PersonOpt::Get(aopt) => {
|
||||
let client = aopt.copt.to_client().await;
|
||||
match client
|
||||
.idm_person_account_get(aopt.aopts.account_id.as_str())
|
||||
.await
|
||||
{
|
||||
Ok(Some(e)) => println!("{}", e),
|
||||
Ok(None) => println!("No matching entries"),
|
||||
Err(e) => error!("Error -> {:?}", e),
|
||||
}
|
||||
}
|
||||
AccountOpt::Delete(aopt) => {
|
||||
PersonOpt::Delete(aopt) => {
|
||||
let client = aopt.copt.to_client().await;
|
||||
let mut modmessage = AccountChangeMessage {
|
||||
output_mode: ConsoleOutputMode::Text,
|
||||
|
@ -347,7 +252,7 @@ impl AccountOpt {
|
|||
status: MessageStatus::Success,
|
||||
};
|
||||
match client
|
||||
.idm_account_delete(aopt.aopts.account_id.as_str())
|
||||
.idm_person_account_delete(aopt.aopts.account_id.as_str())
|
||||
.await
|
||||
{
|
||||
Err(e) => {
|
||||
|
@ -361,10 +266,10 @@ impl AccountOpt {
|
|||
}
|
||||
};
|
||||
}
|
||||
AccountOpt::Create(acopt) => {
|
||||
PersonOpt::Create(acopt) => {
|
||||
let client = acopt.copt.to_client().await;
|
||||
if let Err(e) = client
|
||||
.idm_account_create(
|
||||
.idm_person_account_create(
|
||||
acopt.aopts.account_id.as_str(),
|
||||
acopt.display_name.as_str(),
|
||||
)
|
||||
|
@ -373,13 +278,16 @@ impl AccountOpt {
|
|||
error!("Error -> {:?}", e)
|
||||
}
|
||||
}
|
||||
AccountOpt::Validity { commands } => match commands {
|
||||
PersonOpt::Validity { commands } => match commands {
|
||||
AccountValidity::Show(ano) => {
|
||||
let client = ano.copt.to_client().await;
|
||||
|
||||
println!("user: {}", ano.aopts.account_id.as_str());
|
||||
let ex = match client
|
||||
.idm_account_get_attr(ano.aopts.account_id.as_str(), "account_expire")
|
||||
.idm_person_account_get_attr(
|
||||
ano.aopts.account_id.as_str(),
|
||||
"account_expire",
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(v) => v,
|
||||
|
@ -390,7 +298,10 @@ impl AccountOpt {
|
|||
};
|
||||
|
||||
let vf = match client
|
||||
.idm_account_get_attr(ano.aopts.account_id.as_str(), "account_valid_from")
|
||||
.idm_person_account_get_attr(
|
||||
ano.aopts.account_id.as_str(),
|
||||
"account_valid_from",
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(v) => v,
|
||||
|
@ -437,7 +348,10 @@ impl AccountOpt {
|
|||
if matches!(ano.datetime.as_str(), "never" | "clear") {
|
||||
// Unset the value
|
||||
match client
|
||||
.idm_account_purge_attr(ano.aopts.account_id.as_str(), "account_expire")
|
||||
.idm_person_account_purge_attr(
|
||||
ano.aopts.account_id.as_str(),
|
||||
"account_expire",
|
||||
)
|
||||
.await
|
||||
{
|
||||
Err(e) => error!("Error -> {:?}", e),
|
||||
|
@ -452,7 +366,7 @@ impl AccountOpt {
|
|||
}
|
||||
|
||||
match client
|
||||
.idm_account_set_attr(
|
||||
.idm_person_account_set_attr(
|
||||
ano.aopts.account_id.as_str(),
|
||||
"account_expire",
|
||||
&[ano.datetime.as_str()],
|
||||
|
@ -469,7 +383,7 @@ impl AccountOpt {
|
|||
if matches!(ano.datetime.as_str(), "any" | "clear" | "whenever") {
|
||||
// Unset the value
|
||||
match client
|
||||
.idm_account_purge_attr(
|
||||
.idm_person_account_purge_attr(
|
||||
ano.aopts.account_id.as_str(),
|
||||
"account_valid_from",
|
||||
)
|
||||
|
@ -488,7 +402,7 @@ impl AccountOpt {
|
|||
}
|
||||
|
||||
match client
|
||||
.idm_account_set_attr(
|
||||
.idm_person_account_set_attr(
|
||||
ano.aopts.account_id.as_str(),
|
||||
"account_valid_from",
|
||||
&[ano.datetime.as_str()],
|
||||
|
@ -500,7 +414,7 @@ impl AccountOpt {
|
|||
}
|
||||
}
|
||||
}
|
||||
}, // end AccountOpt::Validity
|
||||
}, // end PersonOpt::Validity
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -508,6 +422,7 @@ impl AccountOpt {
|
|||
impl AccountCredential {
|
||||
pub fn debug(&self) -> bool {
|
||||
match self {
|
||||
AccountCredential::Status(aopt) => aopt.copt.debug,
|
||||
AccountCredential::CreateResetToken(aopt) => aopt.copt.debug,
|
||||
AccountCredential::UseResetToken(aopt) => aopt.copt.debug,
|
||||
AccountCredential::Update(aopt) => aopt.copt.debug,
|
||||
|
@ -516,6 +431,20 @@ impl AccountCredential {
|
|||
|
||||
pub async fn exec(&self) {
|
||||
match self {
|
||||
AccountCredential::Status(aopt) => {
|
||||
let client = aopt.copt.to_client().await;
|
||||
match client
|
||||
.idm_person_account_get_credential_status(aopt.aopts.account_id.as_str())
|
||||
.await
|
||||
{
|
||||
Ok(cstatus) => {
|
||||
println!("{}", cstatus);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error getting credential status -> {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
AccountCredential::Update(aopt) => {
|
||||
let client = aopt.copt.to_client().await;
|
||||
match client
|
||||
|
@ -563,7 +492,7 @@ impl AccountCredential {
|
|||
|
||||
// What's the client url?
|
||||
match client
|
||||
.idm_account_credential_update_intent(aopt.aopts.account_id.as_str())
|
||||
.idm_person_account_credential_update_intent(aopt.aopts.account_id.as_str())
|
||||
.await
|
||||
{
|
||||
Ok(cuintent_token) => {
|
359
kanidm_tools/src/cli/serviceaccount.rs
Normal file
359
kanidm_tools/src/cli/serviceaccount.rs
Normal file
|
@ -0,0 +1,359 @@
|
|||
use crate::{
|
||||
AccountSsh, AccountValidity, ServiceAccountCredential, ServiceAccountOpt, ServiceAccountPosix,
|
||||
};
|
||||
use kanidm_proto::messages::{AccountChangeMessage, ConsoleOutputMode, MessageStatus};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
impl ServiceAccountOpt {
|
||||
pub fn debug(&self) -> bool {
|
||||
match self {
|
||||
ServiceAccountOpt::Credential { commands } => match commands {
|
||||
ServiceAccountCredential::Status(apo) => apo.copt.debug,
|
||||
ServiceAccountCredential::GeneratePw(apo) => apo.copt.debug,
|
||||
},
|
||||
ServiceAccountOpt::Posix { commands } => match commands {
|
||||
ServiceAccountPosix::Show(apo) => apo.copt.debug,
|
||||
ServiceAccountPosix::Set(apo) => apo.copt.debug,
|
||||
},
|
||||
ServiceAccountOpt::Ssh { commands } => match commands {
|
||||
AccountSsh::List(ano) => ano.copt.debug,
|
||||
AccountSsh::Add(ano) => ano.copt.debug,
|
||||
AccountSsh::Delete(ano) => ano.copt.debug,
|
||||
},
|
||||
ServiceAccountOpt::List(copt) => copt.debug,
|
||||
ServiceAccountOpt::Get(aopt) => aopt.copt.debug,
|
||||
ServiceAccountOpt::Update(aopt) => aopt.copt.debug,
|
||||
ServiceAccountOpt::Delete(aopt) => aopt.copt.debug,
|
||||
ServiceAccountOpt::Create(aopt) => aopt.copt.debug,
|
||||
ServiceAccountOpt::Validity { commands } => match commands {
|
||||
AccountValidity::Show(ano) => ano.copt.debug,
|
||||
AccountValidity::ExpireAt(ano) => ano.copt.debug,
|
||||
AccountValidity::BeginFrom(ano) => ano.copt.debug,
|
||||
},
|
||||
ServiceAccountOpt::IntoPerson(aopt) => aopt.copt.debug,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn exec(&self) {
|
||||
match self {
|
||||
ServiceAccountOpt::Credential { commands } => match commands {
|
||||
ServiceAccountCredential::Status(apo) => {
|
||||
let client = apo.copt.to_client().await;
|
||||
match client
|
||||
.idm_service_account_get_credential_status(apo.aopts.account_id.as_str())
|
||||
.await
|
||||
{
|
||||
Ok(cstatus) => {
|
||||
println!("{}", cstatus);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error getting credential status -> {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
ServiceAccountCredential::GeneratePw(apo) => {
|
||||
let client = apo.copt.to_client().await;
|
||||
match client
|
||||
.idm_service_account_generate_password(apo.aopts.account_id.as_str())
|
||||
.await
|
||||
{
|
||||
Ok(new_pw) => {
|
||||
println!("Success: {}", new_pw);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error generating service account credential-> {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ServiceAccountOpt::Posix { commands } => match commands {
|
||||
ServiceAccountPosix::Show(aopt) => {
|
||||
let client = aopt.copt.to_client().await;
|
||||
match client
|
||||
.idm_account_unix_token_get(aopt.aopts.account_id.as_str())
|
||||
.await
|
||||
{
|
||||
Ok(token) => println!("{}", token),
|
||||
Err(e) => {
|
||||
error!("Error -> {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
ServiceAccountPosix::Set(aopt) => {
|
||||
let client = aopt.copt.to_client().await;
|
||||
if let Err(e) = client
|
||||
.idm_service_account_unix_extend(
|
||||
aopt.aopts.account_id.as_str(),
|
||||
aopt.gidnumber,
|
||||
aopt.shell.as_deref(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
error!("Error -> {:?}", e);
|
||||
}
|
||||
}
|
||||
}, // end ServiceAccountOpt::Posix
|
||||
ServiceAccountOpt::Ssh { commands } => match commands {
|
||||
AccountSsh::List(aopt) => {
|
||||
let client = aopt.copt.to_client().await;
|
||||
|
||||
match client
|
||||
.idm_account_get_ssh_pubkeys(aopt.aopts.account_id.as_str())
|
||||
.await
|
||||
{
|
||||
Ok(pkeys) => pkeys.iter().for_each(|pkey| println!("{}", pkey)),
|
||||
Err(e) => {
|
||||
error!("Error -> {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
AccountSsh::Add(aopt) => {
|
||||
let client = aopt.copt.to_client().await;
|
||||
if let Err(e) = client
|
||||
.idm_service_account_post_ssh_pubkey(
|
||||
aopt.aopts.account_id.as_str(),
|
||||
aopt.tag.as_str(),
|
||||
aopt.pubkey.as_str(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
error!("Error -> {:?}", e);
|
||||
}
|
||||
}
|
||||
AccountSsh::Delete(aopt) => {
|
||||
let client = aopt.copt.to_client().await;
|
||||
if let Err(e) = client
|
||||
.idm_service_account_delete_ssh_pubkey(
|
||||
aopt.aopts.account_id.as_str(),
|
||||
aopt.tag.as_str(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
error!("Error -> {:?}", e);
|
||||
}
|
||||
}
|
||||
}, // end ServiceAccountOpt::Ssh
|
||||
ServiceAccountOpt::List(copt) => {
|
||||
let client = copt.to_client().await;
|
||||
match client.idm_service_account_list().await {
|
||||
Ok(r) => r.iter().for_each(|ent| println!("{}", ent)),
|
||||
Err(e) => error!("Error -> {:?}", e),
|
||||
}
|
||||
}
|
||||
ServiceAccountOpt::Update(aopt) => {
|
||||
let client = aopt.copt.to_client().await;
|
||||
match client
|
||||
.idm_service_account_update(
|
||||
aopt.aopts.account_id.as_str(),
|
||||
aopt.newname.as_deref(),
|
||||
aopt.displayname.as_deref(),
|
||||
aopt.mail.as_deref(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) => println!("Success"),
|
||||
Err(e) => error!("Error -> {:?}", e),
|
||||
}
|
||||
}
|
||||
ServiceAccountOpt::Get(aopt) => {
|
||||
let client = aopt.copt.to_client().await;
|
||||
match client
|
||||
.idm_service_account_get(aopt.aopts.account_id.as_str())
|
||||
.await
|
||||
{
|
||||
Ok(Some(e)) => println!("{}", e),
|
||||
Ok(None) => println!("No matching entries"),
|
||||
Err(e) => error!("Error -> {:?}", e),
|
||||
}
|
||||
}
|
||||
ServiceAccountOpt::Delete(aopt) => {
|
||||
let client = aopt.copt.to_client().await;
|
||||
let mut modmessage = AccountChangeMessage {
|
||||
output_mode: ConsoleOutputMode::Text,
|
||||
action: "account delete".to_string(),
|
||||
result: "deleted".to_string(),
|
||||
src_user: aopt
|
||||
.copt
|
||||
.username
|
||||
.to_owned()
|
||||
.unwrap_or(format!("{:?}", client.whoami().await)),
|
||||
dest_user: aopt.aopts.account_id.to_string(),
|
||||
status: MessageStatus::Success,
|
||||
};
|
||||
match client
|
||||
.idm_service_account_delete(aopt.aopts.account_id.as_str())
|
||||
.await
|
||||
{
|
||||
Err(e) => {
|
||||
modmessage.result = format!("Error -> {:?}", e);
|
||||
modmessage.status = MessageStatus::Failure;
|
||||
eprintln!("{}", modmessage);
|
||||
}
|
||||
Ok(result) => {
|
||||
debug!("{:?}", result);
|
||||
println!("{}", modmessage);
|
||||
}
|
||||
};
|
||||
}
|
||||
ServiceAccountOpt::Create(acopt) => {
|
||||
let client = acopt.copt.to_client().await;
|
||||
if let Err(e) = client
|
||||
.idm_service_account_create(
|
||||
acopt.aopts.account_id.as_str(),
|
||||
acopt.display_name.as_str(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
error!("Error -> {:?}", e)
|
||||
}
|
||||
}
|
||||
ServiceAccountOpt::Validity { commands } => match commands {
|
||||
AccountValidity::Show(ano) => {
|
||||
let client = ano.copt.to_client().await;
|
||||
|
||||
println!("user: {}", ano.aopts.account_id.as_str());
|
||||
let ex = match client
|
||||
.idm_service_account_get_attr(
|
||||
ano.aopts.account_id.as_str(),
|
||||
"account_expire",
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!("Error -> {:?}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let vf = match client
|
||||
.idm_service_account_get_attr(
|
||||
ano.aopts.account_id.as_str(),
|
||||
"account_valid_from",
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!("Error -> {:?}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(t) = vf {
|
||||
// Convert the time to local timezone.
|
||||
let t = OffsetDateTime::parse(&t[0], time::Format::Rfc3339)
|
||||
.map(|odt| {
|
||||
odt.to_offset(
|
||||
time::UtcOffset::try_current_local_offset()
|
||||
.unwrap_or(time::UtcOffset::UTC),
|
||||
)
|
||||
.format(time::Format::Rfc3339)
|
||||
})
|
||||
.unwrap_or_else(|_| "invalid timestamp".to_string());
|
||||
|
||||
println!("valid after: {}", t);
|
||||
} else {
|
||||
println!("valid after: any time");
|
||||
}
|
||||
|
||||
if let Some(t) = ex {
|
||||
let t = OffsetDateTime::parse(&t[0], time::Format::Rfc3339)
|
||||
.map(|odt| {
|
||||
odt.to_offset(
|
||||
time::UtcOffset::try_current_local_offset()
|
||||
.unwrap_or(time::UtcOffset::UTC),
|
||||
)
|
||||
.format(time::Format::Rfc3339)
|
||||
})
|
||||
.unwrap_or_else(|_| "invalid timestamp".to_string());
|
||||
println!("expire: {}", t);
|
||||
} else {
|
||||
println!("expire: never");
|
||||
}
|
||||
}
|
||||
AccountValidity::ExpireAt(ano) => {
|
||||
let client = ano.copt.to_client().await;
|
||||
if matches!(ano.datetime.as_str(), "never" | "clear") {
|
||||
// Unset the value
|
||||
match client
|
||||
.idm_service_account_purge_attr(
|
||||
ano.aopts.account_id.as_str(),
|
||||
"account_expire",
|
||||
)
|
||||
.await
|
||||
{
|
||||
Err(e) => error!("Error -> {:?}", e),
|
||||
_ => println!("Success"),
|
||||
}
|
||||
} else {
|
||||
if let Err(e) =
|
||||
OffsetDateTime::parse(ano.datetime.as_str(), time::Format::Rfc3339)
|
||||
{
|
||||
error!("Error -> {:?}", e);
|
||||
return;
|
||||
}
|
||||
|
||||
match client
|
||||
.idm_service_account_set_attr(
|
||||
ano.aopts.account_id.as_str(),
|
||||
"account_expire",
|
||||
&[ano.datetime.as_str()],
|
||||
)
|
||||
.await
|
||||
{
|
||||
Err(e) => error!("Error -> {:?}", e),
|
||||
_ => println!("Success"),
|
||||
}
|
||||
}
|
||||
}
|
||||
AccountValidity::BeginFrom(ano) => {
|
||||
let client = ano.copt.to_client().await;
|
||||
if matches!(ano.datetime.as_str(), "any" | "clear" | "whenever") {
|
||||
// Unset the value
|
||||
match client
|
||||
.idm_service_account_purge_attr(
|
||||
ano.aopts.account_id.as_str(),
|
||||
"account_valid_from",
|
||||
)
|
||||
.await
|
||||
{
|
||||
Err(e) => error!("Error -> {:?}", e),
|
||||
_ => println!("Success"),
|
||||
}
|
||||
} else {
|
||||
// Attempt to parse and set
|
||||
if let Err(e) =
|
||||
OffsetDateTime::parse(ano.datetime.as_str(), time::Format::Rfc3339)
|
||||
{
|
||||
error!("Error -> {:?}", e);
|
||||
return;
|
||||
}
|
||||
|
||||
match client
|
||||
.idm_service_account_set_attr(
|
||||
ano.aopts.account_id.as_str(),
|
||||
"account_valid_from",
|
||||
&[ano.datetime.as_str()],
|
||||
)
|
||||
.await
|
||||
{
|
||||
Err(e) => error!("Error -> {:?}", e),
|
||||
_ => println!("Success"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}, // end ServiceAccountOpt::Validity
|
||||
ServiceAccountOpt::IntoPerson(aopt) => {
|
||||
let client = aopt.copt.to_client().await;
|
||||
match client
|
||||
.idm_service_account_into_person(aopt.aopts.account_id.as_str())
|
||||
.await
|
||||
{
|
||||
Ok(()) => println!("Success"),
|
||||
Err(e) => error!("Error -> {:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,15 +9,17 @@ pub struct Named {
|
|||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct DebugOpt {
|
||||
/// Enable debbuging of the kanidm tool
|
||||
#[clap(short, long, env = "KANIDM_DEBUG")]
|
||||
pub debug: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct CommonOpt {
|
||||
// TODO: this should probably be a flag, or renamed to log level if it's a level
|
||||
/// Enable debbuging of the kanidm tool
|
||||
#[clap(short, long, env = "KANIDM_DEBUG")]
|
||||
pub debug: bool,
|
||||
/// The URL of the kanidm instance
|
||||
#[clap(short = 'H', long = "url", env = "KANIDM_URL")]
|
||||
pub addr: Option<String>,
|
||||
/// User which will initiate requests
|
||||
|
@ -47,32 +49,45 @@ pub struct GroupPosixOpt {
|
|||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum GroupPosix {
|
||||
/// Show details of a specific posix group
|
||||
#[clap(name = "show")]
|
||||
Show(Named),
|
||||
/// Setup posix group properties, or alter them
|
||||
#[clap(name = "set")]
|
||||
Set(GroupPosixOpt),
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum GroupOpt {
|
||||
/// List all groups
|
||||
#[clap(name = "list")]
|
||||
List(CommonOpt),
|
||||
/// View a specific group
|
||||
#[clap(name = "get")]
|
||||
Get(Named),
|
||||
/// Create a new group
|
||||
#[clap(name = "create")]
|
||||
Create(Named),
|
||||
/// Delete a group
|
||||
#[clap(name = "delete")]
|
||||
Delete(Named),
|
||||
/// List the members of a group
|
||||
#[clap(name = "list_members")]
|
||||
ListMembers(Named),
|
||||
/// Set the exact list of members that this group should contain, removing any not listed in the
|
||||
/// set operation.
|
||||
#[clap(name = "set_members")]
|
||||
SetMembers(GroupNamedMembers),
|
||||
/// Delete all members of a group.
|
||||
#[clap(name = "purge_members")]
|
||||
PurgeMembers(Named),
|
||||
/// Add new members to a group
|
||||
#[clap(name = "add_members")]
|
||||
AddMembers(GroupNamedMembers),
|
||||
/// Remove the named members from this group
|
||||
#[clap(name = "remove_members")]
|
||||
RemoveMembers(GroupNamedMembers),
|
||||
/// Manage posix extensions for this group allowing groups to be used on unix/linux systems
|
||||
#[clap(name = "posix")]
|
||||
Posix {
|
||||
#[clap(subcommand)]
|
||||
|
@ -161,6 +176,9 @@ pub struct AccountCreateOpt {
|
|||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum AccountCredential {
|
||||
/// Show the status of this accounts credentials.
|
||||
#[clap(name = "status")]
|
||||
Status(AccountNamedOpt),
|
||||
/// Interactively update/change the credentials for an account
|
||||
#[clap(name = "update")]
|
||||
Update(AccountNamedOpt),
|
||||
|
@ -200,7 +218,7 @@ pub struct AccountPosixOpt {
|
|||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum AccountPosix {
|
||||
pub enum PersonPosix {
|
||||
#[clap(name = "show")]
|
||||
Show(AccountNamedOpt),
|
||||
#[clap(name = "set")]
|
||||
|
@ -209,24 +227,28 @@ pub enum AccountPosix {
|
|||
SetPassword(AccountNamedOpt),
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct AccountPersonOpt {
|
||||
#[clap(flatten)]
|
||||
aopts: AccountCommonOpt,
|
||||
#[clap(long, short, help="Set the mail address, can be set multiple times for multiple addresses.")]
|
||||
mail: Option<Vec<String>>,
|
||||
#[clap(long, short, help="Set the legal name for the person.")]
|
||||
legalname: Option<String>,
|
||||
#[clap(flatten)]
|
||||
copt: CommonOpt,
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum ServiceAccountPosix {
|
||||
#[clap(name = "show")]
|
||||
Show(AccountNamedOpt),
|
||||
#[clap(name = "set")]
|
||||
Set(AccountPosixOpt),
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum AccountPerson {
|
||||
#[clap(name = "extend")]
|
||||
Extend(AccountPersonOpt),
|
||||
#[clap(name = "set")]
|
||||
Set(AccountPersonOpt),
|
||||
#[derive(Debug, Args)]
|
||||
pub struct PersonUpdateOpt {
|
||||
#[clap(flatten)]
|
||||
aopts: AccountCommonOpt,
|
||||
#[clap(long, short, help="Set the legal name for the person.")]
|
||||
legalname: Option<String>,
|
||||
#[clap(long, short, help="Set the account name for the person.")]
|
||||
newname: Option<String>,
|
||||
#[clap(long, short='i', help="Set the display name for the person.")]
|
||||
displayname: Option<String>,
|
||||
#[clap(long, short, help="Set the mail address, can be set multiple times for multiple addresses. The first listed mail address is the 'primary'")]
|
||||
mail: Option<Vec<String>>,
|
||||
#[clap(flatten)]
|
||||
copt: CommonOpt,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
|
@ -241,49 +263,59 @@ pub enum AccountSsh {
|
|||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum AccountValidity {
|
||||
/// Show an accounts validity window
|
||||
#[clap(name = "show")]
|
||||
Show(AccountNamedOpt),
|
||||
/// Set an accounts expiry time
|
||||
#[clap(name = "expire_at")]
|
||||
ExpireAt(AccountNamedExpireDateTimeOpt),
|
||||
/// Set an account valid from time
|
||||
#[clap(name = "begin_from")]
|
||||
BeginFrom(AccountNamedValidDateTimeOpt),
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum AccountOpt {
|
||||
pub enum PersonOpt {
|
||||
/// Manage the credentials this person uses for authentication
|
||||
#[clap(name = "credential")]
|
||||
Credential {
|
||||
#[clap(subcommand)]
|
||||
commands: AccountCredential,
|
||||
},
|
||||
/// Manage radius access for this person
|
||||
#[clap(name = "radius")]
|
||||
Radius {
|
||||
#[clap(subcommand)]
|
||||
commands: AccountRadius,
|
||||
},
|
||||
/// Manage posix extensions for this person allowing access to unix/linux systems
|
||||
#[clap(name = "posix")]
|
||||
Posix {
|
||||
#[clap(subcommand)]
|
||||
commands: AccountPosix,
|
||||
},
|
||||
#[clap(name = "person")]
|
||||
Person {
|
||||
#[clap(subcommand)]
|
||||
commands: AccountPerson,
|
||||
commands: PersonPosix,
|
||||
},
|
||||
/// Manage ssh public key's associated to this person
|
||||
#[clap(name = "ssh")]
|
||||
Ssh {
|
||||
#[clap(subcommand)]
|
||||
commands: AccountSsh,
|
||||
},
|
||||
/// List all persons
|
||||
#[clap(name = "list")]
|
||||
List(CommonOpt),
|
||||
/// View a specific person
|
||||
#[clap(name = "get")]
|
||||
Get(AccountNamedOpt),
|
||||
/// Update a specific person's attributes
|
||||
#[clap(name = "update")]
|
||||
Update(PersonUpdateOpt),
|
||||
/// Create a new person's account
|
||||
#[clap(name = "create")]
|
||||
Create(AccountCreateOpt),
|
||||
/// Delete a person's account
|
||||
#[clap(name = "delete")]
|
||||
Delete(AccountNamedOpt),
|
||||
/// Manage a person's account validity, such as expiry time (account lock/unlock)
|
||||
#[clap(name = "validity")]
|
||||
Validity {
|
||||
#[clap(subcommand)]
|
||||
|
@ -291,6 +323,80 @@ pub enum AccountOpt {
|
|||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum ServiceAccountCredential {
|
||||
/// Show the status of this accounts credentials.
|
||||
#[clap(name = "status")]
|
||||
Status(AccountNamedOpt),
|
||||
/// Reset and generate a new service account password. This password can NOT
|
||||
/// be used with the LDAP interface.
|
||||
#[clap(name = "generate-pw")]
|
||||
GeneratePw(AccountNamedOpt),
|
||||
// Future - add a token creator / remover.
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct ServiceAccountUpdateOpt {
|
||||
#[clap(flatten)]
|
||||
aopts: AccountCommonOpt,
|
||||
#[clap(long, short, help="Set the account name for the service account.")]
|
||||
newname: Option<String>,
|
||||
#[clap(long, short='i', help="Set the display name for the service account.")]
|
||||
displayname: Option<String>,
|
||||
#[clap(long, short, help="Set the mail address, can be set multiple times for multiple addresses. The first listed mail address is the 'primary'")]
|
||||
mail: Option<Vec<String>>,
|
||||
#[clap(flatten)]
|
||||
copt: CommonOpt,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum ServiceAccountOpt {
|
||||
/// Manage generated passwords or access tokens for this service account.
|
||||
#[clap(name = "credential")]
|
||||
Credential {
|
||||
#[clap(subcommand)]
|
||||
commands: ServiceAccountCredential,
|
||||
},
|
||||
/// Manage posix extensions for this service account allowing access to unix/linux systems
|
||||
#[clap(name = "posix")]
|
||||
Posix {
|
||||
#[clap(subcommand)]
|
||||
commands: ServiceAccountPosix,
|
||||
},
|
||||
/// Manage ssh public key's associated to this person
|
||||
#[clap(name = "ssh")]
|
||||
Ssh {
|
||||
#[clap(subcommand)]
|
||||
commands: AccountSsh,
|
||||
},
|
||||
/// List all service accounts
|
||||
#[clap(name = "list")]
|
||||
List(CommonOpt),
|
||||
/// View a specific service account
|
||||
#[clap(name = "get")]
|
||||
Get(AccountNamedOpt),
|
||||
/// Create a new service account
|
||||
#[clap(name = "create")]
|
||||
Create(AccountCreateOpt),
|
||||
/// Update a specific service account's attributes
|
||||
#[clap(name = "update")]
|
||||
Update(ServiceAccountUpdateOpt),
|
||||
/// Delete a service account
|
||||
#[clap(name = "delete")]
|
||||
Delete(AccountNamedOpt),
|
||||
/// Manage a service account validity, such as expiry time (account lock/unlock)
|
||||
#[clap(name = "validity")]
|
||||
Validity {
|
||||
#[clap(subcommand)]
|
||||
commands: AccountValidity,
|
||||
},
|
||||
/// Convert a service account into a person. This is used during the alpha.9
|
||||
/// to alpha.10 migration to "fix up" accounts that were not previously marked
|
||||
/// as persons.
|
||||
#[clap(name = "into-person")]
|
||||
IntoPerson(AccountNamedOpt),
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum RecycleOpt {
|
||||
#[clap(name = "list")]
|
||||
|
@ -376,9 +482,6 @@ pub enum RawOpt {
|
|||
pub enum SelfOpt {
|
||||
/// Show the current authenticated user's identity
|
||||
Whoami(CommonOpt),
|
||||
#[clap(name = "set_password")]
|
||||
/// Set the current user's password
|
||||
SetPassword(CommonOpt),
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
|
@ -530,28 +633,34 @@ pub enum KanidmClientOpt {
|
|||
#[clap(subcommand)]
|
||||
commands: SelfOpt,
|
||||
},
|
||||
/// Account operations
|
||||
Account {
|
||||
/// Actions to manage and view person (user) accounts
|
||||
Person {
|
||||
#[clap(subcommand)]
|
||||
commands: AccountOpt,
|
||||
commands: PersonOpt
|
||||
},
|
||||
/// Group operations
|
||||
/// Actions to manage groups
|
||||
Group {
|
||||
#[clap(subcommand)]
|
||||
commands: GroupOpt,
|
||||
},
|
||||
/// Actions to manage and view service accounts
|
||||
#[clap(name = "service-account")]
|
||||
ServiceAccount {
|
||||
#[clap(subcommand)]
|
||||
commands: ServiceAccountOpt,
|
||||
},
|
||||
/// System configuration operations
|
||||
System {
|
||||
#[clap(subcommand)]
|
||||
commands: SystemOpt,
|
||||
},
|
||||
#[clap(name = "recycle_bin")]
|
||||
#[clap(name = "recycle-bin")]
|
||||
/// Recycle Bin operations
|
||||
Recycle {
|
||||
#[clap(subcommand)]
|
||||
commands: RecycleOpt,
|
||||
},
|
||||
/// Unsafe - low level, raw database operations.
|
||||
/// Unsafe - low level, raw database queries and operations.
|
||||
Raw {
|
||||
#[clap(subcommand)]
|
||||
commands: RawOpt,
|
||||
|
|
|
@ -139,24 +139,24 @@ async fn test_fixture(rsclient: KanidmClient) {
|
|||
|
||||
// Create a new account
|
||||
rsclient
|
||||
.idm_account_create("testaccount1", "Posix Demo Account")
|
||||
.idm_person_account_create("testaccount1", "Posix Demo Account")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Extend the account with posix attrs.
|
||||
rsclient
|
||||
.idm_account_unix_extend("testaccount1", Some(20000), None)
|
||||
.idm_person_account_unix_extend("testaccount1", Some(20000), None)
|
||||
.await
|
||||
.unwrap();
|
||||
// Assign an ssh public key.
|
||||
rsclient
|
||||
.idm_account_post_ssh_pubkey("testaccount1", "tk",
|
||||
.idm_person_account_post_ssh_pubkey("testaccount1", "tk",
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAeGW1P6Pc2rPq0XqbRaDKBcXZUPRklo0L1EyR30CwoP william@amethyst")
|
||||
.await
|
||||
.unwrap();
|
||||
// Set a posix password
|
||||
rsclient
|
||||
.idm_account_unix_cred_put("testaccount1", TESTACCOUNT1_PASSWORD_A)
|
||||
.idm_person_account_unix_cred_put("testaccount1", TESTACCOUNT1_PASSWORD_A)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -370,7 +370,7 @@ async fn test_cache_account_delete() {
|
|||
.await
|
||||
.expect("failed to auth as admin");
|
||||
adminclient
|
||||
.idm_account_delete("testaccount1")
|
||||
.idm_person_account_delete("testaccount1")
|
||||
.await
|
||||
.expect("failed to delete");
|
||||
|
||||
|
@ -420,7 +420,7 @@ async fn test_cache_account_password() {
|
|||
.await
|
||||
.expect("failed to auth as admin");
|
||||
adminclient
|
||||
.idm_account_unix_cred_put("testaccount1", TESTACCOUNT1_PASSWORD_B)
|
||||
.idm_person_account_unix_cred_put("testaccount1", TESTACCOUNT1_PASSWORD_B)
|
||||
.await
|
||||
.expect("Failed to change password");
|
||||
|
||||
|
@ -570,7 +570,7 @@ async fn test_cache_account_expiry() {
|
|||
.await
|
||||
.expect("failed to auth as admin");
|
||||
adminclient
|
||||
.idm_account_set_attr("testaccount1", "account_expire", &[ACCOUNT_EXPIRE])
|
||||
.idm_person_account_set_attr("testaccount1", "account_expire", &[ACCOUNT_EXPIRE])
|
||||
.await
|
||||
.unwrap();
|
||||
// auth will fail
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
# This script based on the developer readme and allows you to run a test server.
|
||||
|
||||
if [ -z "$KANI_TMP" ]; then
|
||||
KANI_TMP=/tmp/kanidm
|
||||
fi
|
||||
|
||||
CONFIG_FILE="../../examples/insecure_server.toml"
|
||||
|
||||
|
@ -10,11 +13,11 @@ if [ ! -f "${CONFIG_FILE}" ]; then
|
|||
echo "Couldn't find configuration file at ${CONFIG_FILE}, please ensure you're running this script from its base directory (${SCRIPT_DIR})."
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "/tmp/kanidm/chain.pem" ]; then
|
||||
if [ ! -f "${KANI_TMP}/chain.pem" ]; then
|
||||
echo "Couldn't find certificate at /tmp/kanidm/chain.pem, quitting"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "/tmp/kanidm/key.pem" ]; then
|
||||
if [ ! -f "${KANI_TMP}/key.pem" ]; then
|
||||
echo "Couldn't find key file at /tmp/kanidm/key.pem, quitting"
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
@ -367,7 +367,7 @@ impl AccessControlProfile {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AccessEffectivePermission {
|
||||
// I don't think we need this? The ident is implied by the requestor.
|
||||
// ident: Uuid,
|
||||
|
@ -804,7 +804,7 @@ pub trait AccessControlsTransaction<'a> {
|
|||
// Find the acps that relate to the caller, and compile their related
|
||||
// target filters.
|
||||
let related_acp: Vec<(&AccessControlModify, _)> =
|
||||
self.modify_related_acp(&rec_entry, &me.ident);
|
||||
self.modify_related_acp(rec_entry, &me.ident);
|
||||
|
||||
related_acp.iter().for_each(|racp| {
|
||||
trace!("Related acs -> {:?}", racp.0.acp.name);
|
||||
|
@ -1277,7 +1277,7 @@ pub trait AccessControlsTransaction<'a> {
|
|||
|
||||
let modify_class: BTreeSet<AttrString> = modify_scoped_acp
|
||||
.iter()
|
||||
.flat_map(|acp| acp.classes.iter().map(|v| v.clone()))
|
||||
.flat_map(|acp| acp.classes.iter().cloned())
|
||||
.collect();
|
||||
|
||||
AccessEffectivePermission {
|
||||
|
|
|
@ -181,7 +181,7 @@ impl QueryServerReadV1 {
|
|||
msg: OnlineBackupEvent,
|
||||
outpath: &str,
|
||||
versions: usize,
|
||||
) {
|
||||
) -> Result<(), OperationError> {
|
||||
trace!(eventid = ?msg.eventid, "Begin online backup event");
|
||||
|
||||
let now: DateTime<Utc> = Utc::now();
|
||||
|
@ -194,46 +194,50 @@ impl QueryServerReadV1 {
|
|||
"Online backup file {} already exists, will not owerwrite it.",
|
||||
dest_file
|
||||
);
|
||||
return Err(OperationError::InvalidState);
|
||||
}
|
||||
false => {
|
||||
let idms_prox_read = self.idms.proxy_read_async().await;
|
||||
spanned!("actors::v1_read::handle<OnlineBackupEvent>", {
|
||||
let res = idms_prox_read.qs_read.get_be_txn().backup(&dest_file);
|
||||
|
||||
match &res {
|
||||
Ok(()) => {
|
||||
idms_prox_read
|
||||
.qs_read
|
||||
.get_be_txn()
|
||||
.backup(&dest_file)
|
||||
.map(|()| {
|
||||
info!("Online backup created {} successfully", dest_file);
|
||||
}
|
||||
Err(e) => {
|
||||
})
|
||||
.map_err(|e| {
|
||||
error!("Online backup failed to create {}: {:?}", dest_file, e);
|
||||
}
|
||||
}
|
||||
|
||||
admin_info!(?res, "online backup result");
|
||||
OperationError::InvalidState
|
||||
})?;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// cleanup of maximum backup versions to keep
|
||||
let mut backup_file_list: Vec<PathBuf> = Vec::new();
|
||||
// pattern to find automatically generated backup files
|
||||
let re = match Regex::new(r"^backup-\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z\.json$") {
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
eprintln!(
|
||||
let re = Regex::new(r"^backup-\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z\.json$").map_err(
|
||||
|error| {
|
||||
error!(
|
||||
"Failed to parse regexp for online backup files: {:?}",
|
||||
error
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
OperationError::InvalidState
|
||||
},
|
||||
)?;
|
||||
|
||||
// cleanup of maximum backup versions to keep
|
||||
let mut backup_file_list: Vec<PathBuf> = Vec::new();
|
||||
// get a list of backup files
|
||||
match fs::read_dir(outpath) {
|
||||
Ok(rd) => {
|
||||
for entry in rd {
|
||||
// get PathBuf
|
||||
let pb = entry.unwrap().path();
|
||||
let pb = entry
|
||||
.map_err(|e| {
|
||||
error!(?e, "Pathbuf access");
|
||||
OperationError::InvalidState
|
||||
})?
|
||||
.path();
|
||||
|
||||
// skip everything that is not a file
|
||||
if !pb.is_file() {
|
||||
|
@ -241,7 +245,10 @@ impl QueryServerReadV1 {
|
|||
}
|
||||
|
||||
// get the /some/dir/<file_name> of the file
|
||||
let file_name = pb.file_name().unwrap().to_str().unwrap();
|
||||
let file_name = pb.file_name().and_then(|f| f.to_str()).ok_or_else(|| {
|
||||
error!("filename is invalid");
|
||||
OperationError::InvalidState
|
||||
})?;
|
||||
// check for a online backup file
|
||||
if re.is_match(file_name) {
|
||||
backup_file_list.push(pb.clone());
|
||||
|
@ -250,6 +257,7 @@ impl QueryServerReadV1 {
|
|||
}
|
||||
Err(e) => {
|
||||
error!("Online backup cleanup error read dir {}: {}", outpath, e);
|
||||
return Err(OperationError::InvalidState);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -287,6 +295,8 @@ impl QueryServerReadV1 {
|
|||
} else {
|
||||
debug!("Online backup cleanup had no files to remove");
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
|
@ -1113,7 +1123,7 @@ impl QueryServerReadV1 {
|
|||
e
|
||||
}),
|
||||
CURequest::PasskeyFinish(label, rpkc) => idms_cred_update
|
||||
.credential_passkey_finish(&session_token, ct, label, rpkc)
|
||||
.credential_passkey_finish(&session_token, ct, label, &rpkc)
|
||||
.map_err(|e| {
|
||||
admin_error!(
|
||||
err = ?e,
|
||||
|
|
|
@ -3,8 +3,6 @@ use std::sync::Arc;
|
|||
use std::time::Duration;
|
||||
use tracing::{info, instrument, span, trace, Level};
|
||||
|
||||
use crate::idm::event::GenerateBackupCodeEvent;
|
||||
use crate::idm::event::RemoveBackupCodeEvent;
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::idm::credupdatesession::{
|
||||
|
@ -17,8 +15,7 @@ use crate::event::{
|
|||
ReviveRecycledEvent,
|
||||
};
|
||||
use crate::idm::event::{
|
||||
AcceptSha1TotpEvent, GeneratePasswordEvent, GenerateTotpEvent, PasswordChangeEvent,
|
||||
RegenerateRadiusSecretEvent, RemoveTotpEvent, UnixPasswordChangeEvent, VerifyTotpEvent,
|
||||
GeneratePasswordEvent, RegenerateRadiusSecretEvent, UnixPasswordChangeEvent,
|
||||
};
|
||||
use crate::modify::{Modify, ModifyInvalid, ModifyList};
|
||||
use crate::value::{PartialValue, Value};
|
||||
|
@ -33,8 +30,8 @@ use kanidm_proto::v1::Entry as ProtoEntry;
|
|||
use kanidm_proto::v1::Modify as ProtoModify;
|
||||
use kanidm_proto::v1::ModifyList as ProtoModifyList;
|
||||
use kanidm_proto::v1::{
|
||||
AccountPersonSet, AccountUnixExtend, CUIntentToken, CUSessionToken, CUStatus, CreateRequest,
|
||||
DeleteRequest, GroupUnixExtend, ModifyRequest, SetCredentialRequest, SetCredentialResponse,
|
||||
AccountUnixExtend, CUIntentToken, CUSessionToken, CUStatus, CreateRequest, DeleteRequest,
|
||||
GroupUnixExtend, ModifyRequest,
|
||||
};
|
||||
|
||||
use uuid::Uuid;
|
||||
|
@ -424,28 +421,21 @@ impl QueryServerWriteV1 {
|
|||
res
|
||||
}
|
||||
|
||||
// === IDM native types for modifications
|
||||
#[instrument(
|
||||
level = "info",
|
||||
name = "credential_set",
|
||||
skip(self, uat, uuid_or_name, sac, eventid)
|
||||
name = "service_account_credential_generate",
|
||||
skip(self, uat, uuid_or_name, eventid)
|
||||
fields(uuid = ?eventid)
|
||||
)]
|
||||
pub async fn handle_credentialset(
|
||||
pub async fn handle_service_account_credential_generate(
|
||||
&self,
|
||||
uat: Option<String>,
|
||||
uuid_or_name: String,
|
||||
sac: SetCredentialRequest,
|
||||
eventid: Uuid,
|
||||
) -> Result<SetCredentialResponse, OperationError> {
|
||||
) -> Result<String, 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<InternalCredentialSetMessage>", {
|
||||
// Trigger a session clean *before* we take any auth steps.
|
||||
// It's important to do this before to ensure that timeouts on
|
||||
// the session are enforced.
|
||||
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))
|
||||
|
@ -467,204 +457,16 @@ impl QueryServerWriteV1 {
|
|||
e
|
||||
})?;
|
||||
|
||||
// What type of auth set did we recieve?
|
||||
match sac {
|
||||
SetCredentialRequest::Password(cleartext) => {
|
||||
let pce = PasswordChangeEvent::from_parts(
|
||||
// &idms_prox_write.qs_write,
|
||||
ident,
|
||||
target_uuid,
|
||||
cleartext,
|
||||
)
|
||||
.map_err(|e| {
|
||||
admin_error!(
|
||||
err = ?e,
|
||||
"Failed to begin internal_credential_set_message",
|
||||
);
|
||||
e
|
||||
})?;
|
||||
idms_prox_write
|
||||
.set_account_password(&pce)
|
||||
.and_then(|_| idms_prox_write.commit())
|
||||
.map(|_| SetCredentialResponse::Success)
|
||||
}
|
||||
SetCredentialRequest::GeneratePassword => {
|
||||
let gpe = GeneratePasswordEvent::from_parts(
|
||||
// &idms_prox_write.qs_write,
|
||||
ident,
|
||||
target_uuid,
|
||||
)
|
||||
.map_err(|e| {
|
||||
admin_error!(
|
||||
err = ?e,
|
||||
"Failed to begin internal_credential_set_message",
|
||||
);
|
||||
e
|
||||
})?;
|
||||
idms_prox_write
|
||||
.generate_account_password(&gpe)
|
||||
.and_then(|r| idms_prox_write.commit().map(|_| r))
|
||||
.map(SetCredentialResponse::Token)
|
||||
}
|
||||
SetCredentialRequest::TotpGenerate => {
|
||||
let gte = GenerateTotpEvent::from_parts(
|
||||
// &idms_prox_write.qs_write,
|
||||
ident,
|
||||
target_uuid,
|
||||
)
|
||||
.map_err(|e| {
|
||||
admin_error!(
|
||||
err = ?e,
|
||||
"Failed to begin internal_credential_set_message",
|
||||
);
|
||||
e
|
||||
})?;
|
||||
idms_prox_write
|
||||
.generate_account_totp(>e, ct)
|
||||
.and_then(|r| idms_prox_write.commit().map(|_| r))
|
||||
}
|
||||
SetCredentialRequest::TotpVerify(uuid, chal) => {
|
||||
let vte = VerifyTotpEvent::from_parts(
|
||||
// &idms_prox_write.qs_write,
|
||||
ident,
|
||||
target_uuid,
|
||||
uuid,
|
||||
chal,
|
||||
)
|
||||
.map_err(|e| {
|
||||
admin_error!(
|
||||
err = ?e,
|
||||
"Failed to begin internal_credential_set_message",
|
||||
);
|
||||
e
|
||||
})?;
|
||||
idms_prox_write
|
||||
.verify_account_totp(&vte, ct)
|
||||
.and_then(|r| idms_prox_write.commit().map(|_| r))
|
||||
}
|
||||
SetCredentialRequest::TotpAcceptSha1(uuid) => {
|
||||
let aste =
|
||||
AcceptSha1TotpEvent::from_parts(ident, target_uuid, uuid).map_err(|e| {
|
||||
admin_error!(
|
||||
err = ?e,
|
||||
"Failed to begin internal_credential_set_message",
|
||||
);
|
||||
e
|
||||
})?;
|
||||
idms_prox_write
|
||||
.accept_account_sha1_totp(&aste)
|
||||
.and_then(|r| idms_prox_write.commit().map(|_| r))
|
||||
}
|
||||
SetCredentialRequest::TotpRemove => {
|
||||
let rte = RemoveTotpEvent::from_parts(
|
||||
// &idms_prox_write.qs_write,
|
||||
ident,
|
||||
target_uuid,
|
||||
)
|
||||
.map_err(|e| {
|
||||
admin_error!(
|
||||
err = ?e,
|
||||
"Failed to begin internal_credential_set_message",
|
||||
);
|
||||
e
|
||||
})?;
|
||||
idms_prox_write
|
||||
.remove_account_totp(&rte)
|
||||
.and_then(|r| idms_prox_write.commit().map(|_| r))
|
||||
}
|
||||
/*
|
||||
SetCredentialRequest::SecurityKeyBegin(label) => {
|
||||
let wre = WebauthnInitRegisterEvent::from_parts(
|
||||
// &idms_prox_write.qs_write,
|
||||
ident,
|
||||
target_uuid,
|
||||
label,
|
||||
)
|
||||
.map_err(|e| {
|
||||
admin_error!(
|
||||
err = ?e,
|
||||
"Failed to begin internal_credential_set_message",
|
||||
);
|
||||
e
|
||||
})?;
|
||||
idms_prox_write
|
||||
.reg_account_webauthn_init(&wre, ct)
|
||||
.and_then(|r| idms_prox_write.commit().map(|_| r))
|
||||
}
|
||||
SetCredentialRequest::SecurityKeyRegister(uuid, rpkc) => {
|
||||
let wre = WebauthnDoRegisterEvent::from_parts(
|
||||
// &idms_prox_write.qs_write,
|
||||
ident,
|
||||
target_uuid,
|
||||
uuid,
|
||||
rpkc,
|
||||
)
|
||||
.map_err(|e| {
|
||||
admin_error!(
|
||||
err = ?e,
|
||||
"Failed to begin internal_credential_set_message",
|
||||
);
|
||||
e
|
||||
})?;
|
||||
idms_prox_write
|
||||
.reg_account_webauthn_complete(&wre)
|
||||
.and_then(|r| idms_prox_write.commit().map(|_| r))
|
||||
}
|
||||
SetCredentialRequest::SecurityKeyRemove(label) => {
|
||||
let rwe = RemoveWebauthnEvent::from_parts(
|
||||
// &idms_prox_write.qs_write,
|
||||
ident,
|
||||
target_uuid,
|
||||
label,
|
||||
)
|
||||
.map_err(|e| {
|
||||
admin_error!(
|
||||
err = ?e,
|
||||
"Failed to begin internal_credential_set_message",
|
||||
);
|
||||
e
|
||||
})?;
|
||||
idms_prox_write
|
||||
.remove_account_webauthn(&rwe)
|
||||
.and_then(|r| idms_prox_write.commit().map(|_| r))
|
||||
}
|
||||
*/
|
||||
SetCredentialRequest::BackupCodeGenerate => {
|
||||
let gbe = GenerateBackupCodeEvent::from_parts(
|
||||
// &idms_prox_write.qs_write,
|
||||
ident,
|
||||
target_uuid,
|
||||
)
|
||||
.map_err(|e| {
|
||||
admin_error!(
|
||||
err = ?e,
|
||||
"Failed to begin internal_credential_set_message",
|
||||
);
|
||||
e
|
||||
})?;
|
||||
idms_prox_write
|
||||
.generate_backup_code(&gbe)
|
||||
.and_then(|r| idms_prox_write.commit().map(|_| r))
|
||||
.map(SetCredentialResponse::BackupCodes)
|
||||
}
|
||||
SetCredentialRequest::BackupCodeRemove => {
|
||||
let rbe = RemoveBackupCodeEvent::from_parts(
|
||||
// &idms_prox_write.qs_write,
|
||||
ident,
|
||||
target_uuid,
|
||||
)
|
||||
.map_err(|e| {
|
||||
admin_error!(
|
||||
err = ?e,
|
||||
"Failed to begin internal_credential_set_message",
|
||||
);
|
||||
e
|
||||
})?;
|
||||
idms_prox_write
|
||||
.remove_backup_code(&rbe)
|
||||
.and_then(|r| idms_prox_write.commit().map(|_| r))
|
||||
}
|
||||
}
|
||||
let gpe = GeneratePasswordEvent::from_parts(ident, target_uuid).map_err(|e| {
|
||||
admin_error!(
|
||||
err = ?e,
|
||||
"Failed to begin handle_service_account_credential_generate",
|
||||
);
|
||||
e
|
||||
})?;
|
||||
idms_prox_write
|
||||
.generate_account_password(&gpe)
|
||||
.and_then(|r| idms_prox_write.commit().map(|_| r))
|
||||
});
|
||||
res
|
||||
}
|
||||
|
@ -833,7 +635,7 @@ impl QueryServerWriteV1 {
|
|||
};
|
||||
|
||||
idms_prox_write
|
||||
.commit_credential_update(session_token, ct)
|
||||
.commit_credential_update(&session_token, ct)
|
||||
.and_then(|tok| idms_prox_write.commit().map(|_| tok))
|
||||
.map_err(|e| {
|
||||
admin_error!(
|
||||
|
@ -865,7 +667,7 @@ impl QueryServerWriteV1 {
|
|||
};
|
||||
|
||||
idms_prox_write
|
||||
.cancel_credential_update(session_token, ct)
|
||||
.cancel_credential_update(&session_token, ct)
|
||||
.and_then(|tok| idms_prox_write.commit().map(|_| tok))
|
||||
.map_err(|e| {
|
||||
admin_error!(
|
||||
|
@ -878,6 +680,7 @@ impl QueryServerWriteV1 {
|
|||
res
|
||||
}
|
||||
|
||||
/*
|
||||
#[instrument(
|
||||
level = "info",
|
||||
name = "idm_account_set_password",
|
||||
|
@ -918,6 +721,44 @@ impl QueryServerWriteV1 {
|
|||
});
|
||||
res
|
||||
}
|
||||
*/
|
||||
|
||||
#[instrument(
|
||||
level = "info",
|
||||
name = "handle_service_account_into_person",
|
||||
skip(self, uat, uuid_or_name, eventid)
|
||||
fields(uuid = ?eventid)
|
||||
)]
|
||||
pub async fn handle_service_account_into_person(
|
||||
&self,
|
||||
uat: Option<String>,
|
||||
uuid_or_name: String,
|
||||
eventid: Uuid,
|
||||
) -> Result<(), OperationError> {
|
||||
let ct = duration_from_epoch_now();
|
||||
let idms_prox_write = self.idms.proxy_write_async(ct).await;
|
||||
let res = spanned!("actors::v1_write::handle<IdmServiceAccountIntoPerson>", {
|
||||
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 target_uuid = 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
|
||||
})?;
|
||||
|
||||
idms_prox_write
|
||||
.service_account_into_person(&ident, target_uuid)
|
||||
.and_then(|_| idms_prox_write.commit())
|
||||
});
|
||||
res
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
level = "info",
|
||||
|
@ -936,8 +777,6 @@ impl QueryServerWriteV1 {
|
|||
let res = spanned!(
|
||||
"actors::v1_write::handle<InternalRegenerateRadiusMessage>",
|
||||
{
|
||||
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))
|
||||
|
@ -989,9 +828,9 @@ impl QueryServerWriteV1 {
|
|||
filter: Filter<FilterInvalid>,
|
||||
eventid: Uuid,
|
||||
) -> Result<(), OperationError> {
|
||||
let idms_prox_write = self.idms.proxy_write_async(duration_from_epoch_now()).await;
|
||||
let res = spanned!("actors::v1_write::handle<PurgeAttributeMessage>", {
|
||||
let ct = duration_from_epoch_now();
|
||||
let ct = duration_from_epoch_now();
|
||||
let idms_prox_write = self.idms.proxy_write_async(ct).await;
|
||||
spanned!("actors::v1_write::handle<PurgeAttributeMessage>", {
|
||||
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))
|
||||
|
@ -1027,8 +866,7 @@ impl QueryServerWriteV1 {
|
|||
.qs_write
|
||||
.modify(&mdf)
|
||||
.and_then(|_| idms_prox_write.commit().map(|_| ()))
|
||||
});
|
||||
res
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
|
@ -1047,7 +885,7 @@ impl QueryServerWriteV1 {
|
|||
eventid: Uuid,
|
||||
) -> Result<(), OperationError> {
|
||||
let idms_prox_write = self.idms.proxy_write_async(duration_from_epoch_now()).await;
|
||||
let res = spanned!("actors::v1_write::handle<RemoveAttributeValuesMessage>", {
|
||||
spanned!("actors::v1_write::handle<RemoveAttributeValuesMessage>", {
|
||||
let ct = duration_from_epoch_now();
|
||||
let ident = idms_prox_write
|
||||
.validate_and_parse_uat(uat.as_deref(), ct)
|
||||
|
@ -1091,8 +929,7 @@ impl QueryServerWriteV1 {
|
|||
.qs_write
|
||||
.modify(&mdf)
|
||||
.and_then(|_| idms_prox_write.commit().map(|_| ()))
|
||||
});
|
||||
res
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
|
@ -1118,10 +955,8 @@ impl QueryServerWriteV1 {
|
|||
.map(|v| ProtoModify::Present(attr.clone(), v))
|
||||
.collect(),
|
||||
);
|
||||
let res = self
|
||||
.modify_from_parts(uat, &uuid_or_name, &proto_ml, filter)
|
||||
.await;
|
||||
res
|
||||
self.modify_from_parts(uat, &uuid_or_name, &proto_ml, filter)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
|
@ -1150,10 +985,8 @@ impl QueryServerWriteV1 {
|
|||
)
|
||||
.collect(),
|
||||
);
|
||||
let res = self
|
||||
.modify_from_parts(uat, &uuid_or_name, &proto_ml, filter)
|
||||
.await;
|
||||
res
|
||||
self.modify_from_parts(uat, &uuid_or_name, &proto_ml, filter)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
|
@ -1175,121 +1008,8 @@ impl QueryServerWriteV1 {
|
|||
// than relying on the proto ones.
|
||||
let ml = ModifyList::new_append("ssh_publickey", Value::new_sshkey(tag, key));
|
||||
|
||||
let res = self
|
||||
.modify_from_internal_parts(uat, &uuid_or_name, &ml, filter)
|
||||
.await;
|
||||
res
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
level = "info",
|
||||
name = "idm_account_person_extend",
|
||||
skip(self, uat, uuid_or_name, eventid)
|
||||
fields(uuid = ?eventid)
|
||||
)]
|
||||
pub async fn handle_idmaccountpersonextend(
|
||||
&self,
|
||||
uat: Option<String>,
|
||||
uuid_or_name: String,
|
||||
px: AccountPersonSet,
|
||||
eventid: Uuid,
|
||||
) -> Result<(), OperationError> {
|
||||
let AccountPersonSet { mail, legalname } = px;
|
||||
|
||||
let mut mods: Vec<_> = Vec::with_capacity(4 + mail.as_ref().map(|v| v.len()).unwrap_or(0));
|
||||
mods.push(Modify::Present("class".into(), Value::new_class("person")));
|
||||
|
||||
if let Some(s) = legalname {
|
||||
mods.push(Modify::Purged("legalname".into()));
|
||||
mods.push(Modify::Present("legalname".into(), Value::new_utf8(s)));
|
||||
}
|
||||
|
||||
if let Some(mail) = mail {
|
||||
mods.push(Modify::Purged("mail".into()));
|
||||
|
||||
let mut miter = mail.into_iter();
|
||||
if let Some(m_primary) = miter.next() {
|
||||
let v =
|
||||
Value::new_email_address_primary_s(m_primary.as_str()).ok_or_else(|| {
|
||||
OperationError::InvalidAttribute(format!(
|
||||
"Invalid mail address {}",
|
||||
m_primary
|
||||
))
|
||||
})?;
|
||||
mods.push(Modify::Present("mail".into(), v));
|
||||
}
|
||||
|
||||
for m in miter {
|
||||
let v = Value::new_email_address_s(m.as_str()).ok_or_else(|| {
|
||||
OperationError::InvalidAttribute(format!("Invalid mail address {}", m))
|
||||
})?;
|
||||
mods.push(Modify::Present("mail".into(), v));
|
||||
}
|
||||
}
|
||||
|
||||
let ml = ModifyList::new_list(mods);
|
||||
|
||||
let filter = filter_all!(f_eq("class", PartialValue::new_class("account")));
|
||||
|
||||
let res = self
|
||||
.modify_from_internal_parts(uat, &uuid_or_name, &ml, filter)
|
||||
.await;
|
||||
res
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
level = "info",
|
||||
name = "idm_account_person_set",
|
||||
skip(self, uat, uuid_or_name, eventid)
|
||||
fields(uuid = ?eventid)
|
||||
)]
|
||||
pub async fn handle_idmaccountpersonset(
|
||||
&self,
|
||||
uat: Option<String>,
|
||||
uuid_or_name: String,
|
||||
px: AccountPersonSet,
|
||||
eventid: Uuid,
|
||||
) -> Result<(), OperationError> {
|
||||
let AccountPersonSet { mail, legalname } = px;
|
||||
|
||||
let mut mods: Vec<_> = Vec::with_capacity(3 + mail.as_ref().map(|v| v.len()).unwrap_or(0));
|
||||
|
||||
if let Some(s) = legalname {
|
||||
mods.push(Modify::Purged("legalname".into()));
|
||||
mods.push(Modify::Present("legalname".into(), Value::new_utf8(s)));
|
||||
}
|
||||
|
||||
if let Some(mail) = mail {
|
||||
mods.push(Modify::Purged("mail".into()));
|
||||
|
||||
let mut miter = mail.into_iter();
|
||||
if let Some(m_primary) = miter.next() {
|
||||
let v =
|
||||
Value::new_email_address_primary_s(m_primary.as_str()).ok_or_else(|| {
|
||||
OperationError::InvalidAttribute(format!(
|
||||
"Invalid mail address {}",
|
||||
m_primary
|
||||
))
|
||||
})?;
|
||||
mods.push(Modify::Present("mail".into(), v));
|
||||
}
|
||||
|
||||
for m in miter {
|
||||
let v = Value::new_email_address_s(m.as_str()).ok_or_else(|| {
|
||||
OperationError::InvalidAttribute(format!("Invalid mail address {}", m))
|
||||
})?;
|
||||
mods.push(Modify::Present("mail".into(), v));
|
||||
}
|
||||
}
|
||||
|
||||
let ml = ModifyList::new_list(mods);
|
||||
|
||||
let filter = filter_all!(f_eq("class", PartialValue::new_class("account")));
|
||||
|
||||
let res = self
|
||||
.modify_from_internal_parts(uat, &uuid_or_name, &ml, filter)
|
||||
.await;
|
||||
res
|
||||
self.modify_from_internal_parts(uat, &uuid_or_name, &ml, filter)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
|
@ -1333,10 +1053,8 @@ impl QueryServerWriteV1 {
|
|||
|
||||
let filter = filter_all!(f_eq("class", PartialValue::new_class("account")));
|
||||
|
||||
let res = self
|
||||
.modify_from_internal_parts(uat, &uuid_or_name, &ml, filter)
|
||||
.await;
|
||||
res
|
||||
self.modify_from_internal_parts(uat, &uuid_or_name, &ml, filter)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
|
@ -1368,10 +1086,8 @@ impl QueryServerWriteV1 {
|
|||
|
||||
let filter = filter_all!(f_eq("class", PartialValue::new_class("group")));
|
||||
|
||||
let res = self
|
||||
.modify_from_internal_parts(uat, &uuid_or_name, &ml, filter)
|
||||
.await;
|
||||
res
|
||||
self.modify_from_internal_parts(uat, &uuid_or_name, &ml, filter)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
|
@ -1390,8 +1106,6 @@ impl QueryServerWriteV1 {
|
|||
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<IdmAccountUnixSetCredMessage>", {
|
||||
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))
|
||||
|
|
|
@ -83,7 +83,7 @@ fn from_vec_dbval1(attr_val: Vec<DbValueV1>) -> Result<DbValueSetV2, OperationEr
|
|||
}
|
||||
})
|
||||
.collect();
|
||||
vs.map(|vs| DbValueSetV2::Iutf8(vs))
|
||||
vs.map(DbValueSetV2::Iutf8)
|
||||
}
|
||||
Some(DbValueV1::Iname(_)) => {
|
||||
let vs: Result<Vec<_>, _> = viter
|
||||
|
@ -95,7 +95,7 @@ fn from_vec_dbval1(attr_val: Vec<DbValueV1>) -> Result<DbValueSetV2, OperationEr
|
|||
}
|
||||
})
|
||||
.collect();
|
||||
vs.map(|vs| DbValueSetV2::Iname(vs))
|
||||
vs.map(DbValueSetV2::Iname)
|
||||
}
|
||||
Some(DbValueV1::Uuid(_)) => {
|
||||
let vs: Result<Vec<_>, _> = viter
|
||||
|
@ -107,7 +107,7 @@ fn from_vec_dbval1(attr_val: Vec<DbValueV1>) -> Result<DbValueSetV2, OperationEr
|
|||
}
|
||||
})
|
||||
.collect();
|
||||
vs.map(|vs| DbValueSetV2::Uuid(vs))
|
||||
vs.map(DbValueSetV2::Uuid)
|
||||
}
|
||||
Some(DbValueV1::Bool(_)) => {
|
||||
let vs: Result<Vec<_>, _> = viter
|
||||
|
@ -119,7 +119,7 @@ fn from_vec_dbval1(attr_val: Vec<DbValueV1>) -> Result<DbValueSetV2, OperationEr
|
|||
}
|
||||
})
|
||||
.collect();
|
||||
vs.map(|vs| DbValueSetV2::Bool(vs))
|
||||
vs.map(DbValueSetV2::Bool)
|
||||
}
|
||||
Some(DbValueV1::SyntaxType(_)) => {
|
||||
let vs: Result<Vec<_>, _> = viter
|
||||
|
@ -131,7 +131,7 @@ fn from_vec_dbval1(attr_val: Vec<DbValueV1>) -> Result<DbValueSetV2, OperationEr
|
|||
}
|
||||
})
|
||||
.collect();
|
||||
vs.map(|vs| DbValueSetV2::SyntaxType(vs))
|
||||
vs.map(DbValueSetV2::SyntaxType)
|
||||
}
|
||||
Some(DbValueV1::IndexType(_)) => {
|
||||
let vs: Result<Vec<_>, _> = viter
|
||||
|
@ -143,7 +143,7 @@ fn from_vec_dbval1(attr_val: Vec<DbValueV1>) -> Result<DbValueSetV2, OperationEr
|
|||
}
|
||||
})
|
||||
.collect();
|
||||
vs.map(|vs| DbValueSetV2::IndexType(vs))
|
||||
vs.map(DbValueSetV2::IndexType)
|
||||
}
|
||||
Some(DbValueV1::Reference(_)) => {
|
||||
let vs: Result<Vec<_>, _> = viter
|
||||
|
@ -155,7 +155,7 @@ fn from_vec_dbval1(attr_val: Vec<DbValueV1>) -> Result<DbValueSetV2, OperationEr
|
|||
}
|
||||
})
|
||||
.collect();
|
||||
vs.map(|vs| DbValueSetV2::Reference(vs))
|
||||
vs.map(DbValueSetV2::Reference)
|
||||
}
|
||||
Some(DbValueV1::JsonFilter(_)) => {
|
||||
let vs: Result<Vec<_>, _> = viter
|
||||
|
@ -167,7 +167,7 @@ fn from_vec_dbval1(attr_val: Vec<DbValueV1>) -> Result<DbValueSetV2, OperationEr
|
|||
}
|
||||
})
|
||||
.collect();
|
||||
vs.map(|vs| DbValueSetV2::JsonFilter(vs))
|
||||
vs.map(DbValueSetV2::JsonFilter)
|
||||
}
|
||||
Some(DbValueV1::Credential(_)) => {
|
||||
let vs: Result<Vec<_>, _> = viter
|
||||
|
@ -179,7 +179,7 @@ fn from_vec_dbval1(attr_val: Vec<DbValueV1>) -> Result<DbValueSetV2, OperationEr
|
|||
}
|
||||
})
|
||||
.collect();
|
||||
vs.map(|vs| DbValueSetV2::Credential(vs))
|
||||
vs.map(DbValueSetV2::Credential)
|
||||
}
|
||||
Some(DbValueV1::SecretValue(_)) => {
|
||||
let vs: Result<Vec<_>, _> = viter
|
||||
|
@ -191,7 +191,7 @@ fn from_vec_dbval1(attr_val: Vec<DbValueV1>) -> Result<DbValueSetV2, OperationEr
|
|||
}
|
||||
})
|
||||
.collect();
|
||||
vs.map(|vs| DbValueSetV2::SecretValue(vs))
|
||||
vs.map(DbValueSetV2::SecretValue)
|
||||
}
|
||||
Some(DbValueV1::SshKey(_)) => {
|
||||
let vs: Result<Vec<_>, _> = viter
|
||||
|
@ -203,7 +203,7 @@ fn from_vec_dbval1(attr_val: Vec<DbValueV1>) -> Result<DbValueSetV2, OperationEr
|
|||
}
|
||||
})
|
||||
.collect();
|
||||
vs.map(|vs| DbValueSetV2::SshKey(vs))
|
||||
vs.map(DbValueSetV2::SshKey)
|
||||
}
|
||||
Some(DbValueV1::Spn(_, _)) => {
|
||||
let vs: Result<Vec<_>, _> = viter
|
||||
|
@ -215,7 +215,7 @@ fn from_vec_dbval1(attr_val: Vec<DbValueV1>) -> Result<DbValueSetV2, OperationEr
|
|||
}
|
||||
})
|
||||
.collect();
|
||||
vs.map(|vs| DbValueSetV2::Spn(vs))
|
||||
vs.map(DbValueSetV2::Spn)
|
||||
}
|
||||
Some(DbValueV1::Uint32(_)) => {
|
||||
let vs: Result<Vec<_>, _> = viter
|
||||
|
@ -227,7 +227,7 @@ fn from_vec_dbval1(attr_val: Vec<DbValueV1>) -> Result<DbValueSetV2, OperationEr
|
|||
}
|
||||
})
|
||||
.collect();
|
||||
vs.map(|vs| DbValueSetV2::Uint32(vs))
|
||||
vs.map(DbValueSetV2::Uint32)
|
||||
}
|
||||
Some(DbValueV1::Cid(_)) => {
|
||||
let vs: Result<Vec<_>, _> = viter
|
||||
|
@ -239,7 +239,7 @@ fn from_vec_dbval1(attr_val: Vec<DbValueV1>) -> Result<DbValueSetV2, OperationEr
|
|||
}
|
||||
})
|
||||
.collect();
|
||||
vs.map(|vs| DbValueSetV2::Cid(vs))
|
||||
vs.map(DbValueSetV2::Cid)
|
||||
}
|
||||
Some(DbValueV1::NsUniqueId(_)) => {
|
||||
let vs: Result<Vec<_>, _> = viter
|
||||
|
@ -251,7 +251,7 @@ fn from_vec_dbval1(attr_val: Vec<DbValueV1>) -> Result<DbValueSetV2, OperationEr
|
|||
}
|
||||
})
|
||||
.collect();
|
||||
vs.map(|vs| DbValueSetV2::NsUniqueId(vs))
|
||||
vs.map(DbValueSetV2::NsUniqueId)
|
||||
}
|
||||
Some(DbValueV1::DateTime(_)) => {
|
||||
let vs: Result<Vec<_>, _> = viter
|
||||
|
@ -263,7 +263,7 @@ fn from_vec_dbval1(attr_val: Vec<DbValueV1>) -> Result<DbValueSetV2, OperationEr
|
|||
}
|
||||
})
|
||||
.collect();
|
||||
vs.map(|vs| DbValueSetV2::DateTime(vs))
|
||||
vs.map(DbValueSetV2::DateTime)
|
||||
}
|
||||
Some(DbValueV1::EmailAddress(_)) => {
|
||||
let mut primary = None;
|
||||
|
@ -309,7 +309,7 @@ fn from_vec_dbval1(attr_val: Vec<DbValueV1>) -> Result<DbValueSetV2, OperationEr
|
|||
}
|
||||
})
|
||||
.collect();
|
||||
vs.map(|vs| DbValueSetV2::Address(vs))
|
||||
vs.map(DbValueSetV2::Address)
|
||||
}
|
||||
Some(DbValueV1::Url(_)) => {
|
||||
let vs: Result<Vec<_>, _> = viter
|
||||
|
@ -321,7 +321,7 @@ fn from_vec_dbval1(attr_val: Vec<DbValueV1>) -> Result<DbValueSetV2, OperationEr
|
|||
}
|
||||
})
|
||||
.collect();
|
||||
vs.map(|vs| DbValueSetV2::Url(vs))
|
||||
vs.map(DbValueSetV2::Url)
|
||||
}
|
||||
Some(DbValueV1::OauthScope(_)) => {
|
||||
let vs: Result<Vec<_>, _> = viter
|
||||
|
@ -333,7 +333,7 @@ fn from_vec_dbval1(attr_val: Vec<DbValueV1>) -> Result<DbValueSetV2, OperationEr
|
|||
}
|
||||
})
|
||||
.collect();
|
||||
vs.map(|vs| DbValueSetV2::OauthScope(vs))
|
||||
vs.map(DbValueSetV2::OauthScope)
|
||||
}
|
||||
Some(DbValueV1::OauthScopeMap(_)) => {
|
||||
let vs: Result<Vec<_>, _> = viter
|
||||
|
@ -345,7 +345,7 @@ fn from_vec_dbval1(attr_val: Vec<DbValueV1>) -> Result<DbValueSetV2, OperationEr
|
|||
}
|
||||
})
|
||||
.collect();
|
||||
vs.map(|vs| DbValueSetV2::OauthScopeMap(vs))
|
||||
vs.map(DbValueSetV2::OauthScopeMap)
|
||||
}
|
||||
Some(DbValueV1::PrivateBinary(_)) => {
|
||||
let vs: Result<Vec<_>, _> = viter
|
||||
|
@ -357,7 +357,7 @@ fn from_vec_dbval1(attr_val: Vec<DbValueV1>) -> Result<DbValueSetV2, OperationEr
|
|||
}
|
||||
})
|
||||
.collect();
|
||||
vs.map(|vs| DbValueSetV2::PrivateBinary(vs))
|
||||
vs.map(DbValueSetV2::PrivateBinary)
|
||||
}
|
||||
Some(DbValueV1::PublicBinary(_, _)) => {
|
||||
let vs: Result<Vec<_>, _> = viter
|
||||
|
@ -369,7 +369,7 @@ fn from_vec_dbval1(attr_val: Vec<DbValueV1>) -> Result<DbValueSetV2, OperationEr
|
|||
}
|
||||
})
|
||||
.collect();
|
||||
vs.map(|vs| DbValueSetV2::PublicBinary(vs))
|
||||
vs.map(DbValueSetV2::PublicBinary)
|
||||
}
|
||||
Some(DbValueV1::RestrictedString(_)) => {
|
||||
let vs: Result<Vec<_>, _> = viter
|
||||
|
@ -381,7 +381,7 @@ fn from_vec_dbval1(attr_val: Vec<DbValueV1>) -> Result<DbValueSetV2, OperationEr
|
|||
}
|
||||
})
|
||||
.collect();
|
||||
vs.map(|vs| DbValueSetV2::RestrictedString(vs))
|
||||
vs.map(DbValueSetV2::RestrictedString)
|
||||
}
|
||||
Some(DbValueV1::IntentToken { u: _, s: _ }) => {
|
||||
let vs: Result<Vec<_>, _> = viter
|
||||
|
@ -393,14 +393,14 @@ fn from_vec_dbval1(attr_val: Vec<DbValueV1>) -> Result<DbValueSetV2, OperationEr
|
|||
}
|
||||
})
|
||||
.collect();
|
||||
vs.map(|vs| DbValueSetV2::IntentToken(vs))
|
||||
vs.map(DbValueSetV2::IntentToken)
|
||||
}
|
||||
// Neither of these should exist yet.
|
||||
Some(DbValueV1::TrustedDeviceEnrollment { u: _ })
|
||||
| Some(DbValueV1::AuthSession { u: _ })
|
||||
| None => {
|
||||
// Shiiiiii
|
||||
assert!(false);
|
||||
debug_assert!(false);
|
||||
Err(OperationError::InvalidState)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -325,12 +325,12 @@ pub trait IdlArcSqliteTransaction {
|
|||
|
||||
fn get_identry_raw(&self, idl: &IdList) -> Result<Vec<IdRawEntry>, OperationError>;
|
||||
|
||||
fn exists_idx(&mut self, attr: &str, itype: &IndexType) -> Result<bool, OperationError>;
|
||||
fn exists_idx(&mut self, attr: &str, itype: IndexType) -> Result<bool, OperationError>;
|
||||
|
||||
fn get_idl(
|
||||
&mut self,
|
||||
attr: &str,
|
||||
itype: &IndexType,
|
||||
itype: IndexType,
|
||||
idx_key: &str,
|
||||
) -> Result<Option<IDLBitRange>, OperationError>;
|
||||
|
||||
|
@ -374,14 +374,14 @@ impl<'a> IdlArcSqliteTransaction for IdlArcSqliteReadTransaction<'a> {
|
|||
get_identry_raw!(self, idl)
|
||||
}
|
||||
|
||||
fn exists_idx(&mut self, attr: &str, itype: &IndexType) -> Result<bool, OperationError> {
|
||||
fn exists_idx(&mut self, attr: &str, itype: IndexType) -> Result<bool, OperationError> {
|
||||
exists_idx!(self, attr, itype)
|
||||
}
|
||||
|
||||
fn get_idl(
|
||||
&mut self,
|
||||
attr: &str,
|
||||
itype: &IndexType,
|
||||
itype: IndexType,
|
||||
idx_key: &str,
|
||||
) -> Result<Option<IDLBitRange>, OperationError> {
|
||||
get_idl!(self, attr, itype, idx_key)
|
||||
|
@ -455,14 +455,14 @@ impl<'a> IdlArcSqliteTransaction for IdlArcSqliteWriteTransaction<'a> {
|
|||
get_identry_raw!(self, idl)
|
||||
}
|
||||
|
||||
fn exists_idx(&mut self, attr: &str, itype: &IndexType) -> Result<bool, OperationError> {
|
||||
fn exists_idx(&mut self, attr: &str, itype: IndexType) -> Result<bool, OperationError> {
|
||||
exists_idx!(self, attr, itype)
|
||||
}
|
||||
|
||||
fn get_idl(
|
||||
&mut self,
|
||||
attr: &str,
|
||||
itype: &IndexType,
|
||||
itype: IndexType,
|
||||
idx_key: &str,
|
||||
) -> Result<Option<IDLBitRange>, OperationError> {
|
||||
get_idl!(self, attr, itype, idx_key)
|
||||
|
@ -557,7 +557,7 @@ impl<'a> IdlArcSqliteWriteTransaction<'a> {
|
|||
spanned!("be::idl_arc_sqlite::commit<idl>", {
|
||||
idl_cache.iter_mut_mark_clean().try_for_each(|(k, v)| {
|
||||
match v {
|
||||
Some(idl) => db.write_idl(k.a.as_str(), &k.i, k.k.as_str(), idl),
|
||||
Some(idl) => db.write_idl(k.a.as_str(), k.i, k.k.as_str(), idl),
|
||||
#[allow(clippy::unreachable)]
|
||||
None => {
|
||||
// Due to how we remove items, we always write an empty idl
|
||||
|
@ -678,14 +678,14 @@ impl<'a> IdlArcSqliteWriteTransaction<'a> {
|
|||
pub fn write_idl(
|
||||
&mut self,
|
||||
attr: &str,
|
||||
itype: &IndexType,
|
||||
itype: IndexType,
|
||||
idx_key: &str,
|
||||
idl: &IDLBitRange,
|
||||
) -> Result<(), OperationError> {
|
||||
spanned!("be::idl_arc_sqlite::write_idl", {
|
||||
let cache_key = IdlCacheKey {
|
||||
a: attr.into(),
|
||||
i: itype.clone(),
|
||||
i: itype,
|
||||
k: idx_key.into(),
|
||||
};
|
||||
// On idl == 0 the db will remove this, and synthesise an empty IdList on a miss
|
||||
|
@ -1028,7 +1028,7 @@ impl<'a> IdlArcSqliteWriteTransaction<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn create_idx(&self, attr: &str, itype: &IndexType) -> Result<(), OperationError> {
|
||||
pub fn create_idx(&self, attr: &str, itype: IndexType) -> Result<(), OperationError> {
|
||||
// We don't need to affect this, so pass it down.
|
||||
self.db.create_idx(attr, itype)
|
||||
}
|
||||
|
|
|
@ -209,7 +209,7 @@ pub trait IdlSqliteTransaction {
|
|||
}
|
||||
}
|
||||
|
||||
fn exists_idx(&self, attr: &str, itype: &IndexType) -> Result<bool, OperationError> {
|
||||
fn exists_idx(&self, attr: &str, itype: IndexType) -> Result<bool, OperationError> {
|
||||
let tname = format!("idx_{}_{}", itype.as_idx_str(), attr);
|
||||
self.exists_table(&tname)
|
||||
}
|
||||
|
@ -217,7 +217,7 @@ pub trait IdlSqliteTransaction {
|
|||
fn get_idl(
|
||||
&self,
|
||||
attr: &str,
|
||||
itype: &IndexType,
|
||||
itype: IndexType,
|
||||
idx_key: &str,
|
||||
) -> Result<Option<IDLBitRange>, OperationError> {
|
||||
spanned!("be::idl_sqlite::get_idl", {
|
||||
|
@ -766,7 +766,7 @@ impl IdlSqliteWriteTransaction {
|
|||
pub fn write_idl(
|
||||
&self,
|
||||
attr: &str,
|
||||
itype: &IndexType,
|
||||
itype: IndexType,
|
||||
idx_key: &str,
|
||||
idl: &IDLBitRange,
|
||||
) -> Result<(), OperationError> {
|
||||
|
@ -934,7 +934,7 @@ impl IdlSqliteWriteTransaction {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn create_idx(&self, attr: &str, itype: &IndexType) -> Result<(), OperationError> {
|
||||
pub fn create_idx(&self, attr: &str, itype: IndexType) -> Result<(), OperationError> {
|
||||
// Is there a better way than formatting this? I can't seem
|
||||
// to template into the str.
|
||||
//
|
||||
|
|
|
@ -37,7 +37,7 @@ impl<'a> IdxKeyRef<'a> {
|
|||
pub fn as_key(&self) -> IdxKey {
|
||||
IdxKey {
|
||||
attr: self.attr.into(),
|
||||
itype: self.itype.clone(),
|
||||
itype: *self.itype,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -94,7 +94,7 @@ pub struct IdlCacheKey {
|
|||
#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash)]
|
||||
pub struct IdlCacheKeyRef<'a> {
|
||||
pub a: &'a str,
|
||||
pub i: &'a IndexType,
|
||||
pub i: IndexType,
|
||||
pub k: &'a str,
|
||||
}
|
||||
|
||||
|
@ -121,7 +121,7 @@ impl IdlCacheKeyToRef for IdlCacheKey {
|
|||
fn keyref(&self) -> IdlCacheKeyRef<'_> {
|
||||
IdlCacheKeyRef {
|
||||
a: self.a.as_str(),
|
||||
i: &self.i,
|
||||
i: self.i,
|
||||
k: self.k.as_str(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -98,6 +98,7 @@ impl BackendConfig {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn new_test() -> Self {
|
||||
BackendConfig {
|
||||
pool_size: 1,
|
||||
|
@ -188,7 +189,7 @@ pub trait BackendTransaction {
|
|||
// Get the idl for this
|
||||
match self
|
||||
.get_idlayer()
|
||||
.get_idl(attr, &IndexType::Equality, &idx_key)?
|
||||
.get_idl(attr, IndexType::Equality, &idx_key)?
|
||||
{
|
||||
Some(idl) => (
|
||||
IdList::Indexed(idl),
|
||||
|
@ -208,7 +209,7 @@ pub trait BackendTransaction {
|
|||
// Get the idl for this
|
||||
match self
|
||||
.get_idlayer()
|
||||
.get_idl(attr, &IndexType::SubString, &idx_key)?
|
||||
.get_idl(attr, IndexType::SubString, &idx_key)?
|
||||
{
|
||||
Some(idl) => (
|
||||
IdList::Indexed(idl),
|
||||
|
@ -224,11 +225,7 @@ pub trait BackendTransaction {
|
|||
FilterResolved::Pres(attr, idx) => {
|
||||
if idx.is_some() {
|
||||
// Get the idl for this
|
||||
match self.get_idlayer().get_idl(
|
||||
attr,
|
||||
&IndexType::Presence,
|
||||
&"_".to_string(),
|
||||
)? {
|
||||
match self.get_idlayer().get_idl(attr, IndexType::Presence, "_")? {
|
||||
Some(idl) => (IdList::Indexed(idl), FilterPlan::PresIndexed(attr.clone())),
|
||||
None => (IdList::AllIds, FilterPlan::PresCorrupt(attr.clone())),
|
||||
}
|
||||
|
@ -993,7 +990,7 @@ impl<'a> BackendWriteTransaction<'a> {
|
|||
// This auto compresses.
|
||||
let ruv_idl = IDLBitRange::from_iter(c_entries.iter().map(|e| e.get_id()));
|
||||
|
||||
self.get_ruv().insert_change(cid.clone(), ruv_idl)?;
|
||||
self.get_ruv().insert_change(cid, ruv_idl)?;
|
||||
|
||||
idlayer.write_identries(c_entries.iter())?;
|
||||
|
||||
|
@ -1036,7 +1033,7 @@ impl<'a> BackendWriteTransaction<'a> {
|
|||
// All good, lets update the RUV.
|
||||
// This auto compresses.
|
||||
let ruv_idl = IDLBitRange::from_iter(post_entries.iter().map(|e| e.get_id()));
|
||||
self.get_ruv().insert_change(cid.clone(), ruv_idl)?;
|
||||
self.get_ruv().insert_change(cid, ruv_idl)?;
|
||||
|
||||
// Now, given the list of id's, update them
|
||||
self.get_idlayer().write_identries(post_entries.iter())?;
|
||||
|
@ -1277,17 +1274,17 @@ impl<'a> BackendWriteTransaction<'a> {
|
|||
|
||||
let idxmeta = unsafe { &(*(&self.idxmeta.idxkeys as *const _)) };
|
||||
|
||||
let idx_diff = Entry::idx_diff(&(*idxmeta), pre, post);
|
||||
let idx_diff = Entry::idx_diff(idxmeta, pre, post);
|
||||
|
||||
idx_diff.iter()
|
||||
idx_diff.into_iter()
|
||||
.try_for_each(|act| {
|
||||
match act {
|
||||
Ok((attr, itype, idx_key)) => {
|
||||
trace!("Adding {:?} idx -> {:?}: {:?}", itype, attr, idx_key);
|
||||
match idlayer.get_idl(attr, itype, idx_key)? {
|
||||
match idlayer.get_idl(attr, itype, &idx_key)? {
|
||||
Some(mut idl) => {
|
||||
idl.insert_id(e_id);
|
||||
idlayer.write_idl(attr, itype, idx_key, &idl)
|
||||
idlayer.write_idl(attr, itype, &idx_key, &idl)
|
||||
}
|
||||
None => {
|
||||
admin_error!(
|
||||
|
@ -1300,10 +1297,10 @@ impl<'a> BackendWriteTransaction<'a> {
|
|||
}
|
||||
Err((attr, itype, idx_key)) => {
|
||||
trace!("Removing {:?} idx -> {:?}: {:?}", itype, attr, idx_key);
|
||||
match idlayer.get_idl(attr, itype, idx_key)? {
|
||||
match idlayer.get_idl(attr, itype, &idx_key)? {
|
||||
Some(mut idl) => {
|
||||
idl.remove_id(e_id);
|
||||
idlayer.write_idl(attr, itype, idx_key, &idl)
|
||||
idlayer.write_idl(attr, itype, &idx_key, &idl)
|
||||
}
|
||||
None => {
|
||||
admin_error!(
|
||||
|
@ -1338,7 +1335,7 @@ impl<'a> BackendWriteTransaction<'a> {
|
|||
if idx_table_set.contains(&tname) {
|
||||
None
|
||||
} else {
|
||||
Some((ikey.attr.clone(), ikey.itype.clone()))
|
||||
Some((ikey.attr.clone(), ikey.itype))
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
@ -1360,7 +1357,7 @@ impl<'a> BackendWriteTransaction<'a> {
|
|||
self.idxmeta
|
||||
.idxkeys
|
||||
.keys()
|
||||
.try_for_each(|ikey| idlayer.create_idx(&ikey.attr, &ikey.itype))
|
||||
.try_for_each(|ikey| idlayer.create_idx(&ikey.attr, ikey.itype))
|
||||
}
|
||||
|
||||
pub fn upgrade_reindex(&self, v: i64) -> Result<(), OperationError> {
|
||||
|
@ -1434,7 +1431,7 @@ impl<'a> BackendWriteTransaction<'a> {
|
|||
pub fn load_test_idl(
|
||||
&self,
|
||||
attr: &String,
|
||||
itype: &IndexType,
|
||||
itype: IndexType,
|
||||
idx_key: &String,
|
||||
) -> Result<Option<IDLBitRange>, OperationError> {
|
||||
self.get_idlayer().get_idl(attr, itype, idx_key)
|
||||
|
@ -1872,7 +1869,7 @@ mod tests {
|
|||
macro_rules! idl_state {
|
||||
($be:expr, $attr:expr, $itype:expr, $idx_key:expr, $expect:expr) => {{
|
||||
let t_idl = $be
|
||||
.load_test_idl(&$attr.to_string(), &$itype, &$idx_key.to_string())
|
||||
.load_test_idl(&$attr.to_string(), $itype, &$idx_key.to_string())
|
||||
.expect("IdList Load failed");
|
||||
let t = $expect.map(|v: Vec<u64>| IDLBitRange::from_iter(v));
|
||||
assert_eq!(t_idl, t);
|
||||
|
@ -2335,7 +2332,7 @@ mod tests {
|
|||
let uuid_p_idl = be
|
||||
.load_test_idl(
|
||||
&"not_indexed".to_string(),
|
||||
&IndexType::Presence,
|
||||
IndexType::Presence,
|
||||
&"_".to_string(),
|
||||
)
|
||||
.unwrap(); // unwrap the result
|
||||
|
|
|
@ -448,7 +448,7 @@ pub const JSON_IDM_ACP_ACCOUNT_WRITE_PRIV_V1: &str = r#"{
|
|||
],
|
||||
"name": ["idm_acp_account_write_priv"],
|
||||
"uuid": ["00000000-0000-0000-0000-ffffff000011"],
|
||||
"description": ["Builtin IDM Control for managing accounts."],
|
||||
"description": ["Builtin IDM Control for managing all accounts (both person and service)."],
|
||||
"acp_receiver": [
|
||||
"{\"eq\":[\"memberof\",\"00000000-0000-0000-0000-000000000006\"]}"
|
||||
],
|
||||
|
@ -495,7 +495,7 @@ pub const JSON_IDM_ACP_ACCOUNT_MANAGE_PRIV_V1: &str = r#"{
|
|||
"devicekeys"
|
||||
],
|
||||
"acp_create_class": [
|
||||
"object", "account"
|
||||
"object", "account", "service_account"
|
||||
]
|
||||
}
|
||||
}"#;
|
||||
|
@ -605,7 +605,7 @@ pub const JSON_IDM_ACP_HP_ACCOUNT_WRITE_PRIV_V1: &str = r#"{
|
|||
],
|
||||
"name": ["idm_acp_hp_account_write_priv"],
|
||||
"uuid": ["00000000-0000-0000-0000-ffffff000016"],
|
||||
"description": ["Builtin IDM Control for managing high privilege accounts."],
|
||||
"description": ["Builtin IDM Control for managing high privilege accounts (both person and service)."],
|
||||
"acp_receiver": [
|
||||
"{\"eq\":[\"memberof\",\"00000000-0000-0000-0000-000000000009\"]}"
|
||||
],
|
||||
|
@ -922,7 +922,7 @@ pub const JSON_IDM_ACP_HP_ACCOUNT_MANAGE_PRIV_V1: &str = r#"{
|
|||
"devicekeys"
|
||||
],
|
||||
"acp_create_class": [
|
||||
"object", "account"
|
||||
"object", "account", "service_account"
|
||||
]
|
||||
}
|
||||
}"#;
|
||||
|
@ -1224,3 +1224,33 @@ pub const JSON_IDM_HP_ACP_OAUTH2_MANAGE_PRIV_V1: &str = r#"{
|
|||
"acp_create_class": ["oauth2_resource_server", "oauth2_resource_server_basic", "object"]
|
||||
}
|
||||
}"#;
|
||||
|
||||
pub const JSON_IDM_HP_ACP_SERVICE_ACCOUNT_INTO_PERSON_MIGRATE_V1: &str = r#"{
|
||||
"attrs": {
|
||||
"class": [
|
||||
"object",
|
||||
"access_control_search",
|
||||
"access_control_profile",
|
||||
"access_control_modify"
|
||||
],
|
||||
"name": ["idm_hp_acp_service_account_into_person_migrate"],
|
||||
"uuid": ["00000000-0000-0000-0000-ffffff000042"],
|
||||
"description": ["Builtin IDM Control allowing service accounts to be migrated into persons"],
|
||||
"acp_receiver": [
|
||||
"{\"eq\":[\"memberof\",\"00000000-0000-0000-0000-000000000034\"]}"
|
||||
],
|
||||
"acp_targetscope": [
|
||||
"{\"and\": [{\"eq\": [\"class\",\"account\"]}, {\"andnot\": {\"or\": [{\"eq\": [\"class\", \"tombstone\"]}, {\"eq\": [\"class\", \"recycled\"]}]}}]}"
|
||||
],
|
||||
"acp_search_attr": [
|
||||
"class", "name", "uuid"
|
||||
],
|
||||
"acp_modify_removedattr": [
|
||||
"class"
|
||||
],
|
||||
"acp_modify_presentattr": [
|
||||
"class"
|
||||
],
|
||||
"acp_modify_class": ["service_account", "person"]
|
||||
}
|
||||
}"#;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
/// Builtin System Admin account.
|
||||
pub const JSON_ADMIN_V1: &str = r#"{
|
||||
"attrs": {
|
||||
"class": ["account", "memberof", "object"],
|
||||
"class": ["account", "service_account", "memberof", "object"],
|
||||
"name": ["admin"],
|
||||
"uuid": ["00000000-0000-0000-0000-000000000000"],
|
||||
"description": ["Builtin System Admin account."],
|
||||
|
@ -14,7 +14,7 @@ pub const JSON_ADMIN_V1: &str = r#"{
|
|||
/// Builtin IDM Admin account.
|
||||
pub const JSON_IDM_ADMIN_V1: &str = r#"{
|
||||
"attrs": {
|
||||
"class": ["account", "memberof", "object"],
|
||||
"class": ["account", "service_account", "memberof", "object"],
|
||||
"name": ["idm_admin"],
|
||||
"uuid": ["00000000-0000-0000-0000-000000000018"],
|
||||
"description": ["Builtin IDM Admin account."],
|
||||
|
@ -403,6 +403,18 @@ pub const JSON_IDM_HP_OAUTH2_MANAGE_PRIV_V1: &str = r#"{
|
|||
}
|
||||
}"#;
|
||||
|
||||
pub const JSON_IDM_HP_SERVICE_ACCOUNT_INTO_PERSON_MIGRATE_PRIV: &str = r#"{
|
||||
"attrs": {
|
||||
"class": ["group", "object"],
|
||||
"name": ["idm_hp_service_account_into_person_migrate_priv"],
|
||||
"uuid": ["00000000-0000-0000-0000-000000000034"],
|
||||
"description": ["Builtin IDM Group for allowing migrations of service accounts into persons"],
|
||||
"member": [
|
||||
"00000000-0000-0000-0000-000000000019"
|
||||
]
|
||||
}
|
||||
}"#;
|
||||
|
||||
/// This must be the last group to init to include the UUID of the other high priv groups.
|
||||
pub const JSON_IDM_HIGH_PRIVILEGE_V1: &str = r#"{
|
||||
"attrs": {
|
||||
|
@ -437,6 +449,7 @@ pub const JSON_IDM_HIGH_PRIVILEGE_V1: &str = r#"{
|
|||
"00000000-0000-0000-0000-000000000027",
|
||||
"00000000-0000-0000-0000-000000000031",
|
||||
"00000000-0000-0000-0000-000000000032",
|
||||
"00000000-0000-0000-0000-000000000034",
|
||||
"00000000-0000-0000-0000-000000001000"
|
||||
]
|
||||
}
|
||||
|
@ -447,7 +460,7 @@ pub const JSON_SYSTEM_INFO_V1: &str = r#"{
|
|||
"class": ["object", "system_info", "system"],
|
||||
"uuid": ["00000000-0000-0000-0000-ffffff000001"],
|
||||
"description": ["System (local) info and metadata object."],
|
||||
"version": ["6"]
|
||||
"version": ["7"]
|
||||
}
|
||||
}"#;
|
||||
|
||||
|
@ -464,7 +477,7 @@ pub const JSON_DOMAIN_INFO_V1: &str = r#"{
|
|||
// Anonymous should be the last object in the range here.
|
||||
pub const JSON_ANONYMOUS_V1: &str = r#"{
|
||||
"attrs": {
|
||||
"class": ["account", "object"],
|
||||
"class": ["account", "service_account", "object"],
|
||||
"name": ["anonymous"],
|
||||
"uuid": ["00000000-0000-0000-0000-ffffffffffff"],
|
||||
"description": ["Anonymous access account."],
|
||||
|
|
|
@ -1077,6 +1077,7 @@ pub const JSON_SCHEMA_CLASS_GROUP: &str = r#"
|
|||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
pub const JSON_SCHEMA_CLASS_ACCOUNT: &str = r#"
|
||||
{
|
||||
"attrs": {
|
||||
|
@ -1110,6 +1111,38 @@ pub const JSON_SCHEMA_CLASS_ACCOUNT: &str = r#"
|
|||
],
|
||||
"uuid": [
|
||||
"00000000-0000-0000-0000-ffff00000046"
|
||||
],
|
||||
"systemsupplements": [
|
||||
"person",
|
||||
"service_account"
|
||||
]
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
pub const JSON_SCHEMA_CLASS_SERVICE_ACCOUNT: &str = r#"
|
||||
{
|
||||
"attrs": {
|
||||
"class": [
|
||||
"object",
|
||||
"system",
|
||||
"classtype"
|
||||
],
|
||||
"description": [
|
||||
"Object representation of service account"
|
||||
],
|
||||
"classname": [
|
||||
"service_account"
|
||||
],
|
||||
"systemmay": [
|
||||
"mail",
|
||||
"primary_credential"
|
||||
],
|
||||
"uuid": [
|
||||
"00000000-0000-0000-0000-ffff00000106"
|
||||
],
|
||||
"systemexcludes": [
|
||||
"person"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -1172,6 +1205,9 @@ pub const JSON_SCHEMA_CLASS_POSIXGROUP: &str = r#"
|
|||
],
|
||||
"uuid": [
|
||||
"00000000-0000-0000-0000-ffff00000058"
|
||||
],
|
||||
"systemsupplements": [
|
||||
"group"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -1200,12 +1236,15 @@ pub const JSON_SCHEMA_CLASS_POSIXACCOUNT: &str = r#"
|
|||
],
|
||||
"uuid": [
|
||||
"00000000-0000-0000-0000-ffff00000057"
|
||||
],
|
||||
"systemsupplements": [
|
||||
"account"
|
||||
]
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
pub const JSON_SCHEMA_CLASS_SYSTEM_CONFIG: &str = &r#"
|
||||
pub const JSON_SCHEMA_CLASS_SYSTEM_CONFIG: &str = r#"
|
||||
{
|
||||
"attrs": {
|
||||
"class": [
|
||||
|
|
|
@ -42,9 +42,10 @@ pub const _UUID_IDM_RADIUS_SECRET_READ_PRIV_V1: Uuid =
|
|||
uuid!("00000000-0000-0000-0000-000000000032");
|
||||
pub const _UUID_IDM_RADIUS_SECRET_WRITE_PRIV_V1: Uuid =
|
||||
uuid!("00000000-0000-0000-0000-000000000031");
|
||||
|
||||
pub const _UUID_IDM_PEOPLE_SELF_WRITE_MAIL_PRIV: Uuid =
|
||||
uuid!("00000000-0000-0000-0000-000000000033");
|
||||
pub const _UUID_IDM_HP_SERVICE_ACCOUNT_INTO_PERSON_MIGRATE_PRIV: Uuid =
|
||||
uuid!("00000000-0000-0000-0000-000000000034");
|
||||
|
||||
//
|
||||
pub const _UUID_IDM_HIGH_PRIVILEGE: Uuid = uuid!("00000000-0000-0000-0000-000000001000");
|
||||
|
@ -174,6 +175,13 @@ pub const _UUID_SCHEMA_ATTR_DOMAIN_DISPLAY_NAME: Uuid =
|
|||
pub const _UUID_SCHEMA_ATTR_PASSKEYS: Uuid = uuid!("00000000-0000-0000-0000-ffff00000099");
|
||||
pub const _UUID_SCHEMA_ATTR_DEVICEKEYS: Uuid = uuid!("00000000-0000-0000-0000-ffff00000100");
|
||||
|
||||
pub const UUID_SCHEMA_ATTR_SYSTEMSUPPLEMENTS: Uuid = uuid!("00000000-0000-0000-0000-ffff00000101");
|
||||
pub const UUID_SCHEMA_ATTR_SUPPLEMENTS: Uuid = uuid!("00000000-0000-0000-0000-ffff00000102");
|
||||
pub const UUID_SCHEMA_ATTR_SYSTEMEXCLUDES: Uuid = uuid!("00000000-0000-0000-0000-ffff00000103");
|
||||
pub const UUID_SCHEMA_ATTR_EXCLUDES: Uuid = uuid!("00000000-0000-0000-0000-ffff00000104");
|
||||
pub const UUID_SCHEMA_ATTR_SCOPE: Uuid = uuid!("00000000-0000-0000-0000-ffff00000105");
|
||||
pub const UUID_SCHEMA_CLASS_SERVICE_ACCOUNT: Uuid = uuid!("00000000-0000-0000-0000-ffff00000106");
|
||||
|
||||
// System and domain infos
|
||||
// I'd like to strongly criticise william of the past for making poor choices about these allocations.
|
||||
pub const UUID_SYSTEM_INFO: Uuid = uuid!("00000000-0000-0000-0000-ffffff000001");
|
||||
|
@ -246,6 +254,8 @@ pub const _UUID_IDM_ACP_RADIUS_SECRET_WRITE_PRIV_V1: Uuid =
|
|||
uuid!("00000000-0000-0000-0000-ffffff000040");
|
||||
pub const _UUID_IDM_PEOPLE_SELF_ACP_WRITE_MAIL_V1: Uuid =
|
||||
uuid!("00000000-0000-0000-0000-ffffff000041");
|
||||
pub const _UUID_IDM_HP_ACP_SERVICE_ACCOUNT_INTO_PERSON_MIGRATE_V1: Uuid =
|
||||
uuid!("00000000-0000-0000-0000-ffffff000042");
|
||||
|
||||
// End of system ranges
|
||||
pub const UUID_DOES_NOT_EXIST: Uuid = uuid!("00000000-0000-0000-0000-fffffffffffe");
|
||||
|
|
|
@ -133,7 +133,7 @@ impl Password {
|
|||
let mut key: Vec<u8> = (0..PBKDF2_KEY_LEN).map(|_| 0).collect();
|
||||
|
||||
let start = Instant::now();
|
||||
let _ = pbkdf2_hmac(
|
||||
pbkdf2_hmac(
|
||||
input.as_slice(),
|
||||
salt.as_slice(),
|
||||
pbkdf2_cost,
|
||||
|
@ -215,7 +215,7 @@ impl Password {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct BackupCodes {
|
||||
code_set: HashSet<String>,
|
||||
}
|
||||
|
|
|
@ -14,14 +14,14 @@ use kanidm_proto::v1::TotpSecret as ProtoTotp;
|
|||
const SECRET_SIZE_BYTES: usize = 8;
|
||||
pub const TOTP_DEFAULT_STEP: u64 = 30;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum TotpError {
|
||||
OpenSSLError,
|
||||
HmacError,
|
||||
TimeError,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum TotpAlgo {
|
||||
Sha1,
|
||||
Sha256,
|
||||
|
@ -58,7 +58,7 @@ impl TotpAlgo {
|
|||
}
|
||||
|
||||
/// <https://tools.ietf.org/html/rfc6238> which relies on <https://tools.ietf.org/html/rfc4226>
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Totp {
|
||||
secret: Vec<u8>,
|
||||
pub(crate) step: u64,
|
||||
|
|
|
@ -663,30 +663,84 @@ impl<STATE> Entry<EntryInvalid, STATE> {
|
|||
// Do we have extensible?
|
||||
let extensible = ne.attribute_equality("class", &CLASS_EXTENSIBLE);
|
||||
|
||||
let entry_classes = ne.get_ava_set("class").ok_or(SchemaError::NoClassFound)?;
|
||||
let entry_classes = ne.get_ava_set("class").ok_or_else(|| {
|
||||
admin_debug!("Attribute 'class' missing from entry");
|
||||
SchemaError::NoClassFound
|
||||
})?;
|
||||
let mut invalid_classes = Vec::with_capacity(0);
|
||||
|
||||
let mut classes: Vec<&SchemaClass> = Vec::with_capacity(entry_classes.len());
|
||||
|
||||
match entry_classes.as_iutf8_iter() {
|
||||
Some(cls_iter) => cls_iter.for_each(|s| match schema_classes.get(s) {
|
||||
Some(x) => classes.push(x),
|
||||
None => {
|
||||
admin_debug!("invalid class: {:?}", s);
|
||||
invalid_classes.push(s.to_string())
|
||||
}
|
||||
}),
|
||||
None => {
|
||||
admin_debug!("corrupt class attribute in: {:?}", entry_classes);
|
||||
invalid_classes.push("corrupt class attribute".to_string())
|
||||
}
|
||||
// We need to keep the btreeset of entry classes here so we can check the
|
||||
// requires and excludes.
|
||||
let entry_classes = if let Some(ec) = entry_classes.as_iutf8_set() {
|
||||
ec.iter()
|
||||
.for_each(|s| match schema_classes.get(s.as_str()) {
|
||||
Some(x) => classes.push(x),
|
||||
None => {
|
||||
admin_debug!("invalid class: {:?}", s);
|
||||
invalid_classes.push(s.to_string())
|
||||
}
|
||||
});
|
||||
ec
|
||||
} else {
|
||||
admin_debug!("corrupt class attribute");
|
||||
return Err(SchemaError::NoClassFound);
|
||||
};
|
||||
|
||||
if !invalid_classes.is_empty() {
|
||||
// lrequest_error!("Class on entry not found in schema?");
|
||||
return Err(SchemaError::InvalidClass(invalid_classes));
|
||||
};
|
||||
|
||||
// Now determine the set of excludes and requires we have, and then
|
||||
// assert we don't violate them.
|
||||
|
||||
let supplements_classes: Vec<_> = classes
|
||||
.iter()
|
||||
.flat_map(|cls| cls.systemsupplements.iter().chain(cls.supplements.iter()))
|
||||
.collect();
|
||||
|
||||
// So long as one supplement is present we can continue.
|
||||
let valid_supplements = if supplements_classes.is_empty() {
|
||||
// No need to check.
|
||||
true
|
||||
} else {
|
||||
supplements_classes
|
||||
.iter()
|
||||
.any(|class| entry_classes.contains(class.as_str()))
|
||||
};
|
||||
|
||||
if !valid_supplements {
|
||||
admin_warn!(
|
||||
"Validation error, the following possible supplement classes are missing - {:?}",
|
||||
supplements_classes
|
||||
);
|
||||
let supplements_classes =
|
||||
supplements_classes.iter().map(|s| s.to_string()).collect();
|
||||
return Err(SchemaError::SupplementsNotSatisfied(supplements_classes));
|
||||
}
|
||||
|
||||
let excludes_classes: Vec<_> = classes
|
||||
.iter()
|
||||
.flat_map(|cls| cls.systemexcludes.iter().chain(cls.excludes.iter()))
|
||||
.collect();
|
||||
|
||||
let mut invalid_excludes = Vec::with_capacity(0);
|
||||
|
||||
excludes_classes.iter().for_each(|class| {
|
||||
if entry_classes.contains(class.as_str()) {
|
||||
invalid_excludes.push(class.to_string())
|
||||
}
|
||||
});
|
||||
|
||||
if !invalid_excludes.is_empty() {
|
||||
admin_warn!(
|
||||
"Validation error, the following excluded classes are present - {:?}",
|
||||
invalid_excludes
|
||||
);
|
||||
return Err(SchemaError::ExcludesNotSatisfied(invalid_excludes));
|
||||
}
|
||||
|
||||
// What this is really doing is taking a set of classes, and building an
|
||||
// "overall" class that describes this exact object for checking. IE we
|
||||
// build a super must/may set from the small class must/may sets.
|
||||
|
@ -998,7 +1052,7 @@ impl Entry<EntrySealed, EntryNew> {
|
|||
}
|
||||
|
||||
type IdxDiff<'a> =
|
||||
Vec<Result<(&'a AttrString, &'a IndexType, String), (&'a AttrString, &'a IndexType, String)>>;
|
||||
Vec<Result<(&'a AttrString, IndexType, String), (&'a AttrString, IndexType, String)>>;
|
||||
|
||||
impl<VALID> Entry<VALID, EntryCommitted> {
|
||||
/// If this entry has ever been commited to disk, retrieve it's database id number.
|
||||
|
@ -1238,11 +1292,11 @@ impl Entry<EntrySealed, EntryCommitted> {
|
|||
// We generate these keys out of the valueset now.
|
||||
vs.generate_idx_eq_keys()
|
||||
.into_iter()
|
||||
.map(|idx_key| Err((&ikey.attr, &ikey.itype, idx_key)))
|
||||
.map(|idx_key| Err((&ikey.attr, ikey.itype, idx_key)))
|
||||
.collect()
|
||||
}
|
||||
IndexType::Presence => {
|
||||
vec![Err((&ikey.attr, &ikey.itype, "_".to_string()))]
|
||||
vec![Err((&ikey.attr, ikey.itype, "_".to_string()))]
|
||||
}
|
||||
IndexType::SubString => Vec::new(),
|
||||
};
|
||||
|
@ -1264,10 +1318,10 @@ impl Entry<EntrySealed, EntryCommitted> {
|
|||
IndexType::Equality => vs
|
||||
.generate_idx_eq_keys()
|
||||
.into_iter()
|
||||
.map(|idx_key| Ok((&ikey.attr, &ikey.itype, idx_key)))
|
||||
.map(|idx_key| Ok((&ikey.attr, ikey.itype, idx_key)))
|
||||
.collect(),
|
||||
IndexType::Presence => {
|
||||
vec![Ok((&ikey.attr, &ikey.itype, "_".to_string()))]
|
||||
vec![Ok((&ikey.attr, ikey.itype, "_".to_string()))]
|
||||
}
|
||||
IndexType::SubString => Vec::new(),
|
||||
};
|
||||
|
@ -1301,11 +1355,11 @@ impl Entry<EntrySealed, EntryCommitted> {
|
|||
pre_vs
|
||||
.generate_idx_eq_keys()
|
||||
.into_iter()
|
||||
.map(|idx_key| Err((&ikey.attr, &ikey.itype, idx_key)))
|
||||
.map(|idx_key| Err((&ikey.attr, ikey.itype, idx_key)))
|
||||
.collect()
|
||||
}
|
||||
IndexType::Presence => {
|
||||
vec![Err((&ikey.attr, &ikey.itype, "_".to_string()))]
|
||||
vec![Err((&ikey.attr, ikey.itype, "_".to_string()))]
|
||||
}
|
||||
IndexType::SubString => Vec::new(),
|
||||
};
|
||||
|
@ -1320,11 +1374,11 @@ impl Entry<EntrySealed, EntryCommitted> {
|
|||
post_vs
|
||||
.generate_idx_eq_keys()
|
||||
.into_iter()
|
||||
.map(|idx_key| Ok((&ikey.attr, &ikey.itype, idx_key)))
|
||||
.map(|idx_key| Ok((&ikey.attr, ikey.itype, idx_key)))
|
||||
.collect()
|
||||
}
|
||||
IndexType::Presence => {
|
||||
vec![Ok((&ikey.attr, &ikey.itype, "_".to_string()))]
|
||||
vec![Ok((&ikey.attr, ikey.itype, "_".to_string()))]
|
||||
}
|
||||
IndexType::SubString => Vec::new(),
|
||||
};
|
||||
|
@ -1394,11 +1448,11 @@ impl Entry<EntrySealed, EntryCommitted> {
|
|||
IndexType::Equality => {
|
||||
removed_vs
|
||||
.into_iter()
|
||||
.map(|idx_key| Err((&ikey.attr, &ikey.itype, idx_key)))
|
||||
.map(|idx_key| Err((&ikey.attr, ikey.itype, idx_key)))
|
||||
.for_each(|v| diff.push(v));
|
||||
added_vs
|
||||
.into_iter()
|
||||
.map(|idx_key| Ok((&ikey.attr, &ikey.itype, idx_key)))
|
||||
.map(|idx_key| Ok((&ikey.attr, ikey.itype, idx_key)))
|
||||
.for_each(|v| diff.push(v));
|
||||
}
|
||||
IndexType::Presence => {
|
||||
|
@ -1456,7 +1510,7 @@ impl Entry<EntrySealed, EntryCommitted> {
|
|||
let cid = attrs
|
||||
.get("last_modified_cid")
|
||||
.and_then(|vs| vs.as_cid_set())
|
||||
.and_then(|set| set.iter().cloned().next())?;
|
||||
.and_then(|set| set.iter().next().cloned())?;
|
||||
|
||||
let eclog = EntryChangelog::new_without_schema(cid, attrs.clone());
|
||||
|
||||
|
@ -1784,6 +1838,11 @@ impl<VALID, STATE> Entry<VALID, STATE> {
|
|||
self.attrs.get(attr)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn get_ava_as_iutf8_iter(&self, attr: &str) -> Option<impl Iterator<Item = &str>> {
|
||||
self.attrs.get(attr).and_then(|vs| vs.as_iutf8_iter())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn get_ava_as_oauthscopes(&self, attr: &str) -> Option<impl Iterator<Item = &str>> {
|
||||
self.attrs.get(attr).and_then(|vs| vs.as_oauthscope_iter())
|
||||
|
@ -2611,7 +2670,7 @@ mod tests {
|
|||
del_r[0]
|
||||
== Err((
|
||||
&AttrString::from("userid"),
|
||||
&IndexType::Equality,
|
||||
IndexType::Equality,
|
||||
"william".to_string()
|
||||
))
|
||||
);
|
||||
|
@ -2619,7 +2678,7 @@ mod tests {
|
|||
del_r[1]
|
||||
== Err((
|
||||
&AttrString::from("userid"),
|
||||
&IndexType::Presence,
|
||||
IndexType::Presence,
|
||||
"_".to_string()
|
||||
))
|
||||
);
|
||||
|
@ -2632,7 +2691,7 @@ mod tests {
|
|||
add_r[0]
|
||||
== Ok((
|
||||
&AttrString::from("userid"),
|
||||
&IndexType::Equality,
|
||||
IndexType::Equality,
|
||||
"william".to_string()
|
||||
))
|
||||
);
|
||||
|
@ -2640,7 +2699,7 @@ mod tests {
|
|||
add_r[1]
|
||||
== Ok((
|
||||
&AttrString::from("userid"),
|
||||
&IndexType::Presence,
|
||||
IndexType::Presence,
|
||||
"_".to_string()
|
||||
))
|
||||
);
|
||||
|
@ -2657,7 +2716,7 @@ mod tests {
|
|||
add_a_r[0]
|
||||
== Ok((
|
||||
&AttrString::from("extra"),
|
||||
&IndexType::Equality,
|
||||
IndexType::Equality,
|
||||
"test".to_string()
|
||||
))
|
||||
);
|
||||
|
@ -2668,7 +2727,7 @@ mod tests {
|
|||
del_a_r[0]
|
||||
== Err((
|
||||
&AttrString::from("extra"),
|
||||
&IndexType::Equality,
|
||||
IndexType::Equality,
|
||||
"test".to_string()
|
||||
))
|
||||
);
|
||||
|
@ -2681,7 +2740,7 @@ mod tests {
|
|||
chg_r[1]
|
||||
== Err((
|
||||
&AttrString::from("userid"),
|
||||
&IndexType::Equality,
|
||||
IndexType::Equality,
|
||||
"william".to_string()
|
||||
))
|
||||
);
|
||||
|
@ -2690,7 +2749,7 @@ mod tests {
|
|||
chg_r[0]
|
||||
== Ok((
|
||||
&AttrString::from("userid"),
|
||||
&IndexType::Equality,
|
||||
IndexType::Equality,
|
||||
"claire".to_string()
|
||||
))
|
||||
);
|
||||
|
|
|
@ -162,7 +162,7 @@ pub enum FilterResolved {
|
|||
AndNot(Box<FilterResolved>, Option<NonZeroU8>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct FilterInvalid {
|
||||
inner: FilterComp,
|
||||
}
|
||||
|
@ -172,7 +172,7 @@ pub struct FilterValid {
|
|||
inner: FilterComp,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct FilterValidResolved {
|
||||
inner: FilterResolved,
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::credential::BackupCodes;
|
||||
use crate::entry::{Entry, EntryCommitted, EntryReduced, EntrySealed};
|
||||
use crate::prelude::*;
|
||||
use crate::schema::SchemaTransaction;
|
||||
|
||||
use kanidm_proto::v1::OperationError;
|
||||
use kanidm_proto::v1::{AuthType, UserAuthToken};
|
||||
|
@ -12,13 +12,13 @@ use webauthn_rs::prelude::Passkey as PasskeyV4;
|
|||
|
||||
use crate::constants::UUID_ANONYMOUS;
|
||||
use crate::credential::policy::CryptoPolicy;
|
||||
use crate::credential::totp::Totp;
|
||||
use crate::credential::{softlock::CredSoftLockPolicy, Credential};
|
||||
use crate::idm::group::Group;
|
||||
use crate::idm::server::IdmServerProxyWriteTransaction;
|
||||
use crate::modify::{ModifyInvalid, ModifyList};
|
||||
use crate::value::{IntentTokenState, PartialValue, Value};
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
use uuid::Uuid;
|
||||
|
@ -318,39 +318,6 @@ impl Account {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn gen_totp_mod(
|
||||
&self,
|
||||
token: Totp,
|
||||
) -> Result<ModifyList<ModifyInvalid>, OperationError> {
|
||||
match &self.primary {
|
||||
// Change the cred
|
||||
Some(primary) => {
|
||||
let ncred = primary.update_totp(token);
|
||||
let vcred = Value::new_credential("primary", ncred);
|
||||
Ok(ModifyList::new_purge_and_set("primary_credential", vcred))
|
||||
}
|
||||
None => {
|
||||
// No credential exists, we can't supplementy it.
|
||||
Err(OperationError::InvalidState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn gen_totp_remove_mod(&self) -> Result<ModifyList<ModifyInvalid>, OperationError> {
|
||||
match &self.primary {
|
||||
// Change the cred
|
||||
Some(primary) => {
|
||||
let ncred = primary.remove_totp();
|
||||
let vcred = Value::new_credential("primary", ncred);
|
||||
Ok(ModifyList::new_purge_and_set("primary_credential", vcred))
|
||||
}
|
||||
None => {
|
||||
// No credential exists, we can't remove what is not real.
|
||||
Err(OperationError::InvalidState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn gen_webauthn_counter_mod(
|
||||
&mut self,
|
||||
auth_result: &AuthenticationResult,
|
||||
|
@ -390,29 +357,6 @@ impl Account {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn gen_backup_code_mod(
|
||||
&self,
|
||||
backup_codes: BackupCodes,
|
||||
) -> Result<ModifyList<ModifyInvalid>, OperationError> {
|
||||
match &self.primary {
|
||||
// Change the cred
|
||||
Some(primary) => {
|
||||
let r_ncred = primary.update_backup_code(backup_codes);
|
||||
match r_ncred {
|
||||
Ok(ncred) => {
|
||||
let vcred = Value::new_credential("primary", ncred);
|
||||
Ok(ModifyList::new_purge_and_set("primary_credential", vcred))
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// No credential exists, we can't supplementy it.
|
||||
Err(OperationError::InvalidState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn invalidate_backup_code_mod(
|
||||
self,
|
||||
code_to_remove: &str,
|
||||
|
@ -436,28 +380,6 @@ impl Account {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn gen_backup_code_remove_mod(
|
||||
&self,
|
||||
) -> Result<ModifyList<ModifyInvalid>, OperationError> {
|
||||
match &self.primary {
|
||||
// Change the cred
|
||||
Some(primary) => {
|
||||
let r_ncred = primary.remove_backup_code();
|
||||
match r_ncred {
|
||||
Ok(ncred) => {
|
||||
let vcred = Value::new_credential("primary", ncred);
|
||||
Ok(ModifyList::new_purge_and_set("primary_credential", vcred))
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// No credential exists, we can't remove what is not real.
|
||||
Err(OperationError::InvalidState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn check_credential_pw(&self, cleartext: &str) -> Result<bool, OperationError> {
|
||||
self.primary
|
||||
.as_ref()
|
||||
|
@ -493,6 +415,7 @@ impl Account {
|
|||
|
||||
pub(crate) fn existing_credential_id_list(&self) -> Option<Vec<CredentialID>> {
|
||||
// TODO!!!
|
||||
// Used in registrations only for disallowing exsiting credentials.
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -501,6 +424,78 @@ impl Account {
|
|||
|
||||
// Need tests for conversion and the cred validations
|
||||
|
||||
impl<'a> IdmServerProxyWriteTransaction<'a> {
|
||||
pub fn service_account_into_person(
|
||||
&self,
|
||||
ident: &Identity,
|
||||
target_uuid: Uuid,
|
||||
) -> Result<(), OperationError> {
|
||||
let schema_ref = self.qs_write.get_schema();
|
||||
|
||||
// Get the entry.
|
||||
let account_entry = self
|
||||
.qs_write
|
||||
.internal_search_uuid(&target_uuid)
|
||||
.map_err(|e| {
|
||||
admin_error!("Failed to start service account into person -> {:?}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
// Copy the current classes
|
||||
let prev_classes: BTreeSet<_> = account_entry
|
||||
.get_ava_as_iutf8_iter("class")
|
||||
.ok_or_else(|| {
|
||||
admin_error!("Invalid entry, class attribute is not present or not iutf8");
|
||||
OperationError::InvalidAccountState("Missing attribute: class".to_string())
|
||||
})?
|
||||
.collect();
|
||||
|
||||
// Remove the service account class.
|
||||
// Add the person class.
|
||||
let mut new_classes = prev_classes.clone();
|
||||
new_classes.remove("service_account");
|
||||
new_classes.insert("person");
|
||||
|
||||
// diff the schema attrs, and remove the ones that are service_account only.
|
||||
let (_added, removed) = schema_ref
|
||||
.query_attrs_difference(&prev_classes, &new_classes)
|
||||
.map_err(|se| {
|
||||
admin_error!("While querying the schema, it reported that requested classes may not be present indicating a possible corruption");
|
||||
OperationError::SchemaViolation(
|
||||
se
|
||||
)
|
||||
})?;
|
||||
|
||||
// Now construct the modlist which:
|
||||
// removes service_account
|
||||
let mut modlist =
|
||||
ModifyList::new_remove("class", PartialValue::new_class("service_account"));
|
||||
// add person
|
||||
modlist.push_mod(Modify::Present("class".into(), Value::new_class("person")));
|
||||
// purge the other attrs that are SA only.
|
||||
removed
|
||||
.into_iter()
|
||||
.for_each(|attr| modlist.push_mod(Modify::Purged(attr.into())));
|
||||
// purge existing sessions
|
||||
|
||||
// Modify
|
||||
self.qs_write
|
||||
.impersonate_modify(
|
||||
// Filter as executed
|
||||
&filter!(f_eq("uuid", PartialValue::new_uuid(target_uuid))),
|
||||
// Filter as intended (acp)
|
||||
&filter_all!(f_eq("uuid", PartialValue::new_uuid(target_uuid))),
|
||||
&modlist,
|
||||
// Provide the entry to impersonate
|
||||
ident,
|
||||
)
|
||||
.map_err(|e| {
|
||||
admin_error!("Failed to migrate service account to person - {:?}", e);
|
||||
e
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::constants::JSON_ANONYMOUS_V1;
|
||||
|
|
|
@ -763,7 +763,7 @@ impl AuthSession {
|
|||
// Do we want to embed this? Or just give the URL? I think we embed
|
||||
// as we only need the client to be able to check it's not tampered, but
|
||||
// this isn't a root of trust.
|
||||
.sign_embed_public_jwk(&uat_jwt_signer)
|
||||
.sign_embed_public_jwk(uat_jwt_signer)
|
||||
.map(|jwts| jwts.to_string())
|
||||
.map_err(|e| {
|
||||
admin_error!(?e, "Failed to sign UserAuthToken to Jwt");
|
||||
|
|
|
@ -67,7 +67,7 @@ enum MfaRegState {
|
|||
TotpInit(Totp),
|
||||
TotpTryAgain(Totp),
|
||||
TotpInvalidSha1(Totp, Totp),
|
||||
Passkey(CreationChallengeResponse, PasskeyRegistration),
|
||||
Passkey(Box<CreationChallengeResponse>, PasskeyRegistration),
|
||||
}
|
||||
|
||||
impl fmt::Debug for MfaRegState {
|
||||
|
@ -208,7 +208,7 @@ impl From<&CredentialUpdateSession> for CredentialUpdateSessionStatus {
|
|||
),
|
||||
MfaRegState::TotpTryAgain(_) => MfaRegStateStatus::TotpTryAgain,
|
||||
MfaRegState::TotpInvalidSha1(_, _) => MfaRegStateStatus::TotpInvalidSha1,
|
||||
MfaRegState::Passkey(r, _) => MfaRegStateStatus::Passkey(r.clone()),
|
||||
MfaRegState::Passkey(r, _) => MfaRegStateStatus::Passkey(r.as_ref().clone()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -291,7 +291,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
.qs_write
|
||||
.get_accesscontrols()
|
||||
.effective_permission_check(
|
||||
&ident,
|
||||
ident,
|
||||
Some(btreeset![
|
||||
AttrString::from("primary_credential"),
|
||||
AttrString::from("passkeys"),
|
||||
|
@ -669,7 +669,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
// This shares some common paths between commit and cancel.
|
||||
fn credential_update_commit_common(
|
||||
&mut self,
|
||||
cust: CredentialUpdateSessionToken,
|
||||
cust: &CredentialUpdateSessionToken,
|
||||
ct: Duration,
|
||||
) -> Result<
|
||||
(
|
||||
|
@ -722,7 +722,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
|
||||
pub fn commit_credential_update(
|
||||
&mut self,
|
||||
cust: CredentialUpdateSessionToken,
|
||||
cust: &CredentialUpdateSessionToken,
|
||||
ct: Duration,
|
||||
) -> Result<(), OperationError> {
|
||||
let (mut modlist, session, session_token) =
|
||||
|
@ -818,7 +818,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
|
||||
pub fn cancel_credential_update(
|
||||
&mut self,
|
||||
cust: CredentialUpdateSessionToken,
|
||||
cust: &CredentialUpdateSessionToken,
|
||||
ct: Duration,
|
||||
) -> Result<(), OperationError> {
|
||||
let (mut modlist, session, session_token) =
|
||||
|
@ -1376,7 +1376,7 @@ impl<'a> IdmServerCredUpdateTransaction<'a> {
|
|||
OperationError::Webauthn
|
||||
})?;
|
||||
|
||||
session.mfaregstate = MfaRegState::Passkey(ccr, pk_reg);
|
||||
session.mfaregstate = MfaRegState::Passkey(Box::new(ccr), pk_reg);
|
||||
// Now that it's in the state, it'll be in the status when returned.
|
||||
Ok(session.deref().into())
|
||||
}
|
||||
|
@ -1386,7 +1386,7 @@ impl<'a> IdmServerCredUpdateTransaction<'a> {
|
|||
cust: &CredentialUpdateSessionToken,
|
||||
ct: Duration,
|
||||
label: String,
|
||||
reg: RegisterPublicKeyCredential,
|
||||
reg: &RegisterPublicKeyCredential,
|
||||
) -> Result<CredentialUpdateSessionStatus, OperationError> {
|
||||
let session_handle = self.get_current_session(cust, ct)?;
|
||||
let mut session = session_handle.try_lock().map_err(|_| {
|
||||
|
@ -1399,7 +1399,7 @@ impl<'a> IdmServerCredUpdateTransaction<'a> {
|
|||
MfaRegState::Passkey(_ccr, pk_reg) => {
|
||||
let passkey = self
|
||||
.webauthn
|
||||
.finish_passkey_registration(®, pk_reg)
|
||||
.finish_passkey_registration(reg, pk_reg)
|
||||
.map_err(|e| {
|
||||
error!(?e, "Unable to start passkey registration");
|
||||
OperationError::Webauthn
|
||||
|
@ -1506,6 +1506,7 @@ mod tests {
|
|||
let e1 = entry_init!(
|
||||
("class", Value::new_class("object")),
|
||||
("class", Value::new_class("account")),
|
||||
("class", Value::new_class("service_account")),
|
||||
("name", Value::new_iname("user_account_only")),
|
||||
("uuid", Value::new_uuid(testaccount_uuid)),
|
||||
("description", Value::new_utf8s("testaccount")),
|
||||
|
@ -1661,7 +1662,7 @@ mod tests {
|
|||
let mut idms_prox_write = idms.proxy_write(ct);
|
||||
|
||||
idms_prox_write
|
||||
.commit_credential_update(cust, ct)
|
||||
.commit_credential_update(&cust, ct)
|
||||
.expect("Failed to commit credential update.");
|
||||
|
||||
idms_prox_write.commit().expect("Failed to commit txn");
|
||||
|
@ -2243,6 +2244,7 @@ mod tests {
|
|||
|
||||
// There now should be a backup code invalidation present
|
||||
let da = idms_delayed.try_recv().expect("invalid");
|
||||
assert!(matches!(da, DelayedAction::BackupCodeRemoval(_)));
|
||||
let r = task::block_on(idms.delayed_action(ct, da));
|
||||
assert!(r.is_ok());
|
||||
|
||||
|
@ -2387,7 +2389,7 @@ mod tests {
|
|||
// Finish the registration
|
||||
let label = "softtoken".to_string();
|
||||
let c_status = cutxn
|
||||
.credential_passkey_finish(&cust, ct, label, passkey_resp)
|
||||
.credential_passkey_finish(&cust, ct, label, &passkey_resp)
|
||||
.expect("Failed to initiate passkey registration");
|
||||
|
||||
assert!(matches!(c_status.mfaregstate, MfaRegStateStatus::None));
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
use crate::prelude::*;
|
||||
use kanidm_proto::v1::OperationError;
|
||||
|
||||
pub struct PasswordChangeEvent {
|
||||
#[cfg(test)]
|
||||
pub(crate) struct PasswordChangeEvent {
|
||||
pub ident: Identity,
|
||||
pub target: Uuid,
|
||||
pub cleartext: String,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl PasswordChangeEvent {
|
||||
pub fn new_internal(target: &Uuid, cleartext: &str) -> Self {
|
||||
PasswordChangeEvent {
|
||||
|
@ -15,33 +17,6 @@ impl PasswordChangeEvent {
|
|||
cleartext: cleartext.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_idm_account_set_password(
|
||||
ident: Identity,
|
||||
cleartext: String,
|
||||
// qs: &QueryServerWriteTransaction,
|
||||
) -> Result<Self, OperationError> {
|
||||
let target = ident.get_uuid().ok_or(OperationError::InvalidState)?;
|
||||
|
||||
Ok(PasswordChangeEvent {
|
||||
ident,
|
||||
target,
|
||||
cleartext,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_parts(
|
||||
// qs: &QueryServerWriteTransaction,
|
||||
ident: Identity,
|
||||
target: Uuid,
|
||||
cleartext: String,
|
||||
) -> Result<Self, OperationError> {
|
||||
Ok(PasswordChangeEvent {
|
||||
ident,
|
||||
target,
|
||||
cleartext,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UnixPasswordChangeEvent {
|
||||
|
@ -90,51 +65,6 @@ impl GeneratePasswordEvent {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GenerateBackupCodeEvent {
|
||||
pub ident: Identity,
|
||||
pub target: Uuid,
|
||||
}
|
||||
|
||||
impl GenerateBackupCodeEvent {
|
||||
pub fn from_parts(
|
||||
// qs: &QueryServerWriteTransaction,
|
||||
ident: Identity,
|
||||
target: Uuid,
|
||||
) -> Result<Self, OperationError> {
|
||||
Ok(GenerateBackupCodeEvent { ident, target })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn new_internal(target: Uuid) -> Self {
|
||||
let ident = Identity::from_internal();
|
||||
|
||||
GenerateBackupCodeEvent { ident, target }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RemoveBackupCodeEvent {
|
||||
pub ident: Identity,
|
||||
pub target: Uuid,
|
||||
}
|
||||
|
||||
impl RemoveBackupCodeEvent {
|
||||
pub fn from_parts(
|
||||
// qs: &QueryServerWriteTransaction,
|
||||
ident: Identity,
|
||||
target: Uuid,
|
||||
) -> Result<Self, OperationError> {
|
||||
Ok(RemoveBackupCodeEvent { ident, target })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn new_internal(target: Uuid) -> Self {
|
||||
let ident = Identity::from_internal();
|
||||
|
||||
RemoveBackupCodeEvent { ident, target }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RegenerateRadiusSecretEvent {
|
||||
pub ident: Identity,
|
||||
|
@ -265,122 +195,6 @@ impl UnixUserAuthEvent {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GenerateTotpEvent {
|
||||
pub ident: Identity,
|
||||
pub target: Uuid,
|
||||
}
|
||||
|
||||
impl GenerateTotpEvent {
|
||||
pub fn from_parts(
|
||||
// qs: &QueryServerWriteTransaction,
|
||||
ident: Identity,
|
||||
target: Uuid,
|
||||
) -> Result<Self, OperationError> {
|
||||
Ok(GenerateTotpEvent { ident, target })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn new_internal(target: Uuid) -> Self {
|
||||
let ident = Identity::from_internal();
|
||||
|
||||
GenerateTotpEvent { ident, target }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VerifyTotpEvent {
|
||||
pub ident: Identity,
|
||||
pub target: Uuid,
|
||||
pub session: Uuid,
|
||||
pub chal: u32,
|
||||
}
|
||||
|
||||
impl VerifyTotpEvent {
|
||||
pub fn from_parts(
|
||||
// qs: &QueryServerWriteTransaction,
|
||||
ident: Identity,
|
||||
target: Uuid,
|
||||
session: Uuid,
|
||||
chal: u32,
|
||||
) -> Result<Self, OperationError> {
|
||||
Ok(VerifyTotpEvent {
|
||||
ident,
|
||||
target,
|
||||
session,
|
||||
chal,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn new_internal(target: Uuid, session: Uuid, chal: u32) -> Self {
|
||||
let ident = Identity::from_internal();
|
||||
|
||||
VerifyTotpEvent {
|
||||
ident,
|
||||
target,
|
||||
session,
|
||||
chal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AcceptSha1TotpEvent {
|
||||
pub ident: Identity,
|
||||
pub target: Uuid,
|
||||
pub session: Uuid,
|
||||
}
|
||||
|
||||
impl AcceptSha1TotpEvent {
|
||||
pub fn from_parts(
|
||||
// qs: &QueryServerWriteTransaction,
|
||||
ident: Identity,
|
||||
target: Uuid,
|
||||
session: Uuid,
|
||||
) -> Result<Self, OperationError> {
|
||||
Ok(AcceptSha1TotpEvent {
|
||||
ident,
|
||||
target,
|
||||
session,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn new_internal(target: Uuid, session: Uuid) -> Self {
|
||||
let ident = Identity::from_internal();
|
||||
|
||||
AcceptSha1TotpEvent {
|
||||
ident,
|
||||
target,
|
||||
session,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RemoveTotpEvent {
|
||||
pub ident: Identity,
|
||||
pub target: Uuid,
|
||||
}
|
||||
|
||||
impl RemoveTotpEvent {
|
||||
pub fn from_parts(
|
||||
// qs: &QueryServerWriteTransaction,
|
||||
ident: Identity,
|
||||
target: Uuid,
|
||||
) -> Result<Self, OperationError> {
|
||||
Ok(RemoveTotpEvent { ident, target })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn new_internal(target: Uuid) -> Self {
|
||||
let ident = Identity::from_internal();
|
||||
|
||||
RemoveTotpEvent { ident, target }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CredentialStatusEvent {
|
||||
pub ident: Identity,
|
||||
|
|
|
@ -1,153 +0,0 @@
|
|||
use crate::credential::totp::{Totp, TOTP_DEFAULT_STEP};
|
||||
use crate::identity::IdentityId;
|
||||
use crate::idm::account::Account;
|
||||
use kanidm_proto::v1::TotpSecret;
|
||||
use kanidm_proto::v1::{OperationError, SetCredentialResponse};
|
||||
use std::mem;
|
||||
use std::time::Duration;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub(crate) enum MfaRegCred {
|
||||
Totp(Totp),
|
||||
}
|
||||
|
||||
pub(crate) enum MfaRegNext {
|
||||
Success,
|
||||
TotpCheck(TotpSecret),
|
||||
TotpInvalidSha1,
|
||||
}
|
||||
|
||||
impl MfaRegNext {
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
pub fn to_proto(self, u: Uuid) -> SetCredentialResponse {
|
||||
match self {
|
||||
MfaRegNext::Success => SetCredentialResponse::Success,
|
||||
MfaRegNext::TotpCheck(secret) => SetCredentialResponse::TotpCheck(u, secret),
|
||||
MfaRegNext::TotpInvalidSha1 => SetCredentialResponse::TotpInvalidSha1(u),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum MfaRegState {
|
||||
TotpInit(Totp),
|
||||
TotpInvalidSha1(Totp),
|
||||
TotpDone,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct MfaRegSession {
|
||||
// The event origin, aka who is requesting the MFA reg (may not
|
||||
// be the same as account!!!)
|
||||
origin: IdentityId,
|
||||
// The account that the MFA will be registered to
|
||||
pub account: Account,
|
||||
// What state is the reg process in?
|
||||
state: MfaRegState,
|
||||
// Human-facing name of the Domain
|
||||
issuer: String,
|
||||
}
|
||||
|
||||
impl MfaRegSession {
|
||||
pub fn totp_new(
|
||||
origin: IdentityId,
|
||||
account: Account,
|
||||
issuer: String,
|
||||
) -> Result<(Self, MfaRegNext), OperationError> {
|
||||
// Based on the req, init our session, and the return the next step.
|
||||
// Store the ID of the event that start's the attempt
|
||||
let token = Totp::generate_secure(TOTP_DEFAULT_STEP);
|
||||
|
||||
let accountname = account.name.as_str();
|
||||
|
||||
let next = MfaRegNext::TotpCheck(token.to_proto(accountname, issuer.as_str()));
|
||||
|
||||
let state = MfaRegState::TotpInit(token);
|
||||
let s = MfaRegSession {
|
||||
origin,
|
||||
account,
|
||||
state,
|
||||
issuer,
|
||||
};
|
||||
Ok((s, next))
|
||||
}
|
||||
|
||||
pub fn totp_step(
|
||||
&mut self,
|
||||
origin: &IdentityId,
|
||||
target: &Uuid,
|
||||
chal: u32,
|
||||
ct: &Duration,
|
||||
) -> Result<(MfaRegNext, Option<MfaRegCred>), OperationError> {
|
||||
if &self.origin != origin || target != &self.account.uuid {
|
||||
// Verify that the same event source is the one continuing this attempt
|
||||
return Err(OperationError::InvalidRequestState);
|
||||
};
|
||||
|
||||
match &self.state {
|
||||
MfaRegState::TotpInit(token) => {
|
||||
if token.verify(chal, ct) {
|
||||
let mut nstate = MfaRegState::TotpDone;
|
||||
mem::swap(&mut self.state, &mut nstate);
|
||||
match nstate {
|
||||
MfaRegState::TotpInit(token) => {
|
||||
Ok((MfaRegNext::Success, Some(MfaRegCred::Totp(token))))
|
||||
}
|
||||
_ => Err(OperationError::InvalidState),
|
||||
}
|
||||
} else {
|
||||
// What if it's a broken authenticator app? Google authenticator
|
||||
// and authy both force sha1 and ignore the algo we send. So let's
|
||||
// check that just in case.
|
||||
|
||||
let token_sha1 = token.clone().downgrade_to_legacy();
|
||||
|
||||
if token_sha1.verify(chal, ct) {
|
||||
// Greeeaaaaaatttt. It's a broken app. Let's check the user
|
||||
// knows this is broken, before we proceed.
|
||||
let mut nstate = MfaRegState::TotpInvalidSha1(token_sha1);
|
||||
mem::swap(&mut self.state, &mut nstate);
|
||||
Ok((MfaRegNext::TotpInvalidSha1, None))
|
||||
} else {
|
||||
// Probably a bad code or typo then. Let them try again.
|
||||
let accountname = self.account.name.as_str();
|
||||
Ok((
|
||||
MfaRegNext::TotpCheck(
|
||||
token.to_proto(accountname, self.issuer.as_str()),
|
||||
),
|
||||
None,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => Err(OperationError::InvalidRequestState),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn totp_accept_sha1(
|
||||
&mut self,
|
||||
origin: &IdentityId,
|
||||
target: &Uuid,
|
||||
) -> Result<(MfaRegNext, Option<MfaRegCred>), OperationError> {
|
||||
if &self.origin != origin || target != &self.account.uuid {
|
||||
// Verify that the same event source is the one continuing this attempt
|
||||
return Err(OperationError::InvalidRequestState);
|
||||
};
|
||||
|
||||
match &self.state {
|
||||
MfaRegState::TotpInvalidSha1(_token) => {
|
||||
// They have accepted the token to be sha1, so lets yield that.
|
||||
// The token was mutated to sha1 in the previous step.
|
||||
let mut nstate = MfaRegState::TotpDone;
|
||||
mem::swap(&mut self.state, &mut nstate);
|
||||
match nstate {
|
||||
MfaRegState::TotpInvalidSha1(token) => {
|
||||
Ok((MfaRegNext::Success, Some(MfaRegCred::Totp(token))))
|
||||
}
|
||||
_ => Err(OperationError::InvalidState),
|
||||
}
|
||||
}
|
||||
_ => Err(OperationError::InvalidRequestState),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,7 +9,6 @@ pub(crate) mod credupdatesession;
|
|||
pub(crate) mod delayed;
|
||||
pub(crate) mod event;
|
||||
pub(crate) mod group;
|
||||
pub(crate) mod mfareg;
|
||||
pub mod oauth2;
|
||||
pub(crate) mod radius;
|
||||
pub mod server;
|
||||
|
|
|
@ -322,7 +322,7 @@ impl<'a> Oauth2ResourceServersWriteTransaction<'a> {
|
|||
let scope_maps = ent
|
||||
.get_ava_as_oauthscopemaps("oauth2_rs_scope_map")
|
||||
.cloned()
|
||||
.unwrap_or_else(BTreeMap::new);
|
||||
.unwrap_or_default();
|
||||
|
||||
trace!("implicit_scopes");
|
||||
let implicit_scopes = ent
|
||||
|
@ -385,7 +385,7 @@ impl<'a> Oauth2ResourceServersWriteTransaction<'a> {
|
|||
let scopes_supported: BTreeSet<String> = implicit_scopes
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(scope_maps.values().map(|bts| bts.iter()).flatten().cloned())
|
||||
.chain(scope_maps.values().flat_map(|bts| bts.iter()).cloned())
|
||||
.collect();
|
||||
let scopes_supported: Vec<_> = scopes_supported.into_iter().collect();
|
||||
|
||||
|
@ -589,7 +589,7 @@ impl Oauth2ResourceServersReadTransaction {
|
|||
uat: uat.clone(),
|
||||
code_challenge,
|
||||
redirect_uri: auth_req.redirect_uri.clone(),
|
||||
scopes: avail_scopes.clone(),
|
||||
scopes: avail_scopes,
|
||||
nonce: auth_req.nonce.clone(),
|
||||
};
|
||||
|
||||
|
@ -730,11 +730,14 @@ impl Oauth2ResourceServersReadTransaction {
|
|||
// Everything is DONE! Now submit that it's all happy and the user consented correctly.
|
||||
// this will let them bypass consent steps in the future.
|
||||
// Submit that we consented to the delayed action queue
|
||||
if let Err(_) = async_tx.send(DelayedAction::Oauth2ConsentGrant(Oauth2ConsentGrant {
|
||||
target_uuid: uat.uuid,
|
||||
oauth2_rs_uuid: o2rs.uuid,
|
||||
scopes: consent_req.scopes,
|
||||
})) {
|
||||
if async_tx
|
||||
.send(DelayedAction::Oauth2ConsentGrant(Oauth2ConsentGrant {
|
||||
target_uuid: uat.uuid,
|
||||
oauth2_rs_uuid: o2rs.uuid,
|
||||
scopes: consent_req.scopes,
|
||||
}))
|
||||
.is_err()
|
||||
{
|
||||
admin_warn!("unable to queue delayed oauth2 consent grant, continuing ... ");
|
||||
}
|
||||
|
||||
|
@ -862,7 +865,7 @@ impl Oauth2ResourceServersReadTransaction {
|
|||
})?;
|
||||
let mut hasher = sha::Sha256::new();
|
||||
hasher.update(code_verifier.as_bytes());
|
||||
let code_verifier_hash: Vec<u8> = hasher.finish().iter().copied().collect();
|
||||
let code_verifier_hash: Vec<u8> = hasher.finish().to_vec();
|
||||
|
||||
if code_challenge.0 != code_verifier_hash {
|
||||
security_info!(
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
use crate::credential::policy::CryptoPolicy;
|
||||
use crate::credential::softlock::CredSoftLock;
|
||||
use crate::credential::BackupCodes;
|
||||
use crate::event::{AuthEvent, AuthEventStep, AuthResult};
|
||||
use crate::identity::{IdentType, IdentUser, Limits};
|
||||
use crate::idm::account::Account;
|
||||
use crate::idm::authsession::AuthSession;
|
||||
use crate::idm::credupdatesession::CredentialUpdateSessionMutex;
|
||||
#[cfg(test)]
|
||||
use crate::idm::event::PasswordChangeEvent;
|
||||
use crate::idm::event::{
|
||||
AcceptSha1TotpEvent, CredentialStatusEvent, GeneratePasswordEvent, GenerateTotpEvent,
|
||||
LdapAuthEvent, PasswordChangeEvent, RadiusAuthTokenEvent, RegenerateRadiusSecretEvent,
|
||||
RemoveTotpEvent, UnixGroupTokenEvent, UnixPasswordChangeEvent, UnixUserAuthEvent,
|
||||
UnixUserTokenEvent, VerifyTotpEvent,
|
||||
CredentialStatusEvent, GeneratePasswordEvent, LdapAuthEvent, RadiusAuthTokenEvent,
|
||||
RegenerateRadiusSecretEvent, UnixGroupTokenEvent, UnixPasswordChangeEvent, UnixUserAuthEvent,
|
||||
UnixUserTokenEvent,
|
||||
};
|
||||
use crate::idm::mfareg::{MfaRegCred, MfaRegNext, MfaRegSession};
|
||||
use crate::idm::oauth2::{
|
||||
AccessTokenIntrospectRequest, AccessTokenIntrospectResponse, AccessTokenRequest,
|
||||
AccessTokenResponse, AuthorisationRequest, AuthorisePermitSuccess, AuthoriseResponse,
|
||||
|
@ -24,10 +23,7 @@ use crate::idm::unix::{UnixGroup, UnixUserAccount};
|
|||
use crate::idm::AuthState;
|
||||
use crate::ldap::LdapBoundToken;
|
||||
use crate::prelude::*;
|
||||
use crate::utils::{
|
||||
backup_code_from_random, 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};
|
||||
|
||||
use crate::actors::v1_write::QueryServerWriteV1;
|
||||
use crate::idm::delayed::{
|
||||
|
@ -37,8 +33,8 @@ use crate::idm::delayed::{
|
|||
|
||||
use hashbrown::HashSet;
|
||||
use kanidm_proto::v1::{
|
||||
AuthType, BackupCodesView, CredentialStatus, PasswordFeedback, RadiusAuthToken,
|
||||
SetCredentialResponse, UnixGroupToken, UnixUserToken, UserAuthToken,
|
||||
AuthType, BackupCodesView, CredentialStatus, PasswordFeedback, RadiusAuthToken, UnixGroupToken,
|
||||
UnixUserToken, UserAuthToken,
|
||||
};
|
||||
|
||||
use compact_jwt::{Jws, JwsSigner, JwsUnverified, JwsValidator};
|
||||
|
@ -51,7 +47,9 @@ use tokio::sync::Semaphore;
|
|||
|
||||
use async_std::task;
|
||||
|
||||
#[cfg(test)]
|
||||
use core::task::{Context, Poll};
|
||||
#[cfg(test)]
|
||||
use futures::task as futures_task;
|
||||
|
||||
use concread::{
|
||||
|
@ -71,7 +69,7 @@ use url::Url;
|
|||
use webauthn_rs::prelude::{Webauthn, WebauthnBuilder};
|
||||
|
||||
use super::delayed::BackupCodeRemoval;
|
||||
use super::event::{GenerateBackupCodeEvent, ReadBackupCodeEvent, RemoveBackupCodeEvent};
|
||||
use super::event::ReadBackupCodeEvent;
|
||||
|
||||
use tracing::trace;
|
||||
|
||||
|
@ -86,8 +84,7 @@ pub struct IdmServer {
|
|||
session_ticket: Semaphore,
|
||||
sessions: BptreeMap<Uuid, AuthSessionMutex>,
|
||||
softlocks: HashMap<Uuid, CredSoftLockMutex>,
|
||||
/// A set of in progress MFA registrations
|
||||
mfareg_sessions: BptreeMap<Uuid, MfaRegSession>,
|
||||
/// A set of in progress credential registrations
|
||||
cred_update_sessions: BptreeMap<Uuid, CredentialUpdateSessionMutex>,
|
||||
/// Reference to the query server.
|
||||
qs: QueryServer,
|
||||
|
@ -144,7 +141,6 @@ pub struct IdmServerProxyWriteTransaction<'a> {
|
|||
// qs operations to occur through this interface.
|
||||
pub qs_write: QueryServerWriteTransaction<'a>,
|
||||
/// Associate to an event origin ID, which has a TS and a UUID instead
|
||||
mfareg_sessions: BptreeMapWriteTxn<'a, Uuid, MfaRegSession>,
|
||||
pub(crate) cred_update_sessions: BptreeMapWriteTxn<'a, Uuid, CredentialUpdateSessionMutex>,
|
||||
pub(crate) sid: Sid,
|
||||
crypto_policy: &'a CryptoPolicy,
|
||||
|
@ -253,7 +249,6 @@ impl IdmServer {
|
|||
session_ticket: Semaphore::new(1),
|
||||
sessions: BptreeMap::new(),
|
||||
softlocks: HashMap::new(),
|
||||
mfareg_sessions: BptreeMap::new(),
|
||||
cred_update_sessions: BptreeMap::new(),
|
||||
qs,
|
||||
crypto_policy,
|
||||
|
@ -323,7 +318,6 @@ impl IdmServer {
|
|||
let qs_write = self.qs.write_async(ts).await;
|
||||
|
||||
IdmServerProxyWriteTransaction {
|
||||
mfareg_sessions: self.mfareg_sessions.write(),
|
||||
cred_update_sessions: self.cred_update_sessions.write(),
|
||||
qs_write,
|
||||
sid,
|
||||
|
@ -368,6 +362,7 @@ impl IdmServer {
|
|||
}
|
||||
|
||||
impl IdmServerDelayed {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn check_is_empty_or_panic(&mut self) {
|
||||
let waker = futures_task::noop_waker();
|
||||
let mut cx = Context::from_waker(&waker);
|
||||
|
@ -715,7 +710,7 @@ impl<'a> IdmServerAuthTransaction<'a> {
|
|||
let auth_session_ref = session_read
|
||||
// Why is the session missing?
|
||||
.get(&mech.sessionid)
|
||||
.map(|auth_session_ref| auth_session_ref.clone())
|
||||
.cloned()
|
||||
.ok_or_else(|| {
|
||||
admin_error!("Invalid Session State (no present session uuid)");
|
||||
OperationError::InvalidSessionState
|
||||
|
@ -744,7 +739,7 @@ impl<'a> IdmServerAuthTransaction<'a> {
|
|||
None => true,
|
||||
};
|
||||
|
||||
let auth_result = if is_valid {
|
||||
if is_valid {
|
||||
auth_result
|
||||
} else {
|
||||
// Fail the session
|
||||
|
@ -757,9 +752,7 @@ impl<'a> IdmServerAuthTransaction<'a> {
|
|||
state: aus,
|
||||
delay,
|
||||
}
|
||||
});
|
||||
|
||||
auth_result
|
||||
})
|
||||
} // End AuthEventStep::Mech
|
||||
AuthEventStep::Cred(creds) => {
|
||||
// lperf_segment!("idm::server::auth<Creds>", || {
|
||||
|
@ -770,7 +763,7 @@ impl<'a> IdmServerAuthTransaction<'a> {
|
|||
let auth_session_ref = session_read
|
||||
// Why is the session missing?
|
||||
.get(&creds.sessionid)
|
||||
.map(|auth_session_ref| auth_session_ref.clone())
|
||||
.cloned()
|
||||
.ok_or_else(|| {
|
||||
admin_error!("Invalid Session State (no present session uuid)");
|
||||
OperationError::InvalidSessionState
|
||||
|
@ -781,7 +774,7 @@ impl<'a> IdmServerAuthTransaction<'a> {
|
|||
let maybe_slock_ref = match auth_session.get_credential_uuid()? {
|
||||
Some(cred_uuid) => {
|
||||
let softlock_read = self.softlocks.read();
|
||||
softlock_read.get(&cred_uuid).map(|s| s.clone())
|
||||
softlock_read.get(&cred_uuid).cloned()
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
@ -804,7 +797,7 @@ impl<'a> IdmServerAuthTransaction<'a> {
|
|||
true
|
||||
};
|
||||
|
||||
let r = if is_valid {
|
||||
if is_valid {
|
||||
// Process the credentials here as required.
|
||||
// Basically throw them at the auth_session and see what
|
||||
// falls out.
|
||||
|
@ -843,10 +836,7 @@ impl<'a> IdmServerAuthTransaction<'a> {
|
|||
state: aus,
|
||||
delay,
|
||||
}
|
||||
});
|
||||
// softlock_write.commit();
|
||||
// session_write.commit();
|
||||
r
|
||||
})
|
||||
} // End AuthEventStep::Cred
|
||||
}
|
||||
}
|
||||
|
@ -1020,7 +1010,7 @@ impl<'a> IdmServerAuthTransaction<'a> {
|
|||
None
|
||||
};
|
||||
|
||||
let res = if let Some(mut slock) = maybe_valid {
|
||||
if let Some(mut slock) = maybe_valid {
|
||||
if account
|
||||
.verify_unix_credential(lae.cleartext.as_str(), &self.async_tx, ct)?
|
||||
.is_some()
|
||||
|
@ -1067,8 +1057,7 @@ impl<'a> IdmServerAuthTransaction<'a> {
|
|||
// Account is slocked!
|
||||
security_info!("Account is softlocked.");
|
||||
Ok(None)
|
||||
};
|
||||
res
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1276,15 +1265,6 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
self.webauthn.get_origin()
|
||||
}
|
||||
|
||||
pub fn expire_mfareg_sessions(&mut self, ct: Duration) {
|
||||
// ct is current time - sub the timeout. and then split.
|
||||
let expire = ct - Duration::from_secs(MFAREG_SESSION_TIMEOUT);
|
||||
let split_at = uuid_from_duration(expire, self.sid);
|
||||
// Removes older sessions in place.
|
||||
self.mfareg_sessions.split_off_lt(&split_at);
|
||||
// expired will now be dropped, and can't be used by future sessions.
|
||||
}
|
||||
|
||||
fn check_password_quality(
|
||||
&mut self,
|
||||
cleartext: &str,
|
||||
|
@ -1368,7 +1348,8 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn set_account_password(
|
||||
#[cfg(test)]
|
||||
pub(crate) fn set_account_password(
|
||||
&mut self,
|
||||
pce: &PasswordChangeEvent,
|
||||
) -> Result<(), OperationError> {
|
||||
|
@ -1588,8 +1569,9 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
/*
|
||||
/// Generate a new set of backup code and remove the old ones.
|
||||
pub fn generate_backup_code(
|
||||
pub(crate) fn generate_backup_code(
|
||||
&mut self,
|
||||
gbe: &GenerateBackupCodeEvent,
|
||||
) -> Result<Vec<String>, OperationError> {
|
||||
|
@ -1626,7 +1608,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn remove_backup_code(
|
||||
pub(crate) fn remove_backup_code(
|
||||
&mut self,
|
||||
rte: &RemoveBackupCodeEvent,
|
||||
) -> Result<SetCredentialResponse, OperationError> {
|
||||
|
@ -1653,6 +1635,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
})
|
||||
.map(|_| SetCredentialResponse::Success)
|
||||
}
|
||||
*/
|
||||
|
||||
pub fn regenerate_radius_secret(
|
||||
&mut self,
|
||||
|
@ -1691,284 +1674,6 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
.map(|_| cleartext)
|
||||
}
|
||||
|
||||
/*
|
||||
pub fn reg_account_webauthn_init(
|
||||
&mut self,
|
||||
wre: &WebauthnInitRegisterEvent,
|
||||
ct: Duration,
|
||||
) -> Result<SetCredentialResponse, OperationError> {
|
||||
let account = self.target_to_account(&wre.target)?;
|
||||
let sessionid = uuid_from_duration(ct, self.sid);
|
||||
|
||||
let origin = (&wre.ident.origin).into();
|
||||
let label = wre.label.clone();
|
||||
|
||||
let issuer = self.qs_write.get_domain_display_name().to_string();
|
||||
|
||||
let (session, mfa_reg_next) =
|
||||
MfaRegSession::webauthn_new(origin, account, label, self.webauthn, issuer)?;
|
||||
|
||||
let next = mfa_reg_next.to_proto(sessionid);
|
||||
|
||||
// Add session to tree
|
||||
self.mfareg_sessions.insert(sessionid, session);
|
||||
trace!(?sessionid, "Started mfa reg session for webauthn");
|
||||
Ok(next)
|
||||
}
|
||||
|
||||
pub fn reg_account_webauthn_complete(
|
||||
&mut self,
|
||||
wre: &WebauthnDoRegisterEvent,
|
||||
) -> Result<SetCredentialResponse, OperationError> {
|
||||
let sessionid = wre.session;
|
||||
let origin = (&wre.ident.origin).into();
|
||||
let webauthn = self.webauthn;
|
||||
|
||||
// Regardless of the outcome, we purge this session, so we get it
|
||||
// from the tree instead of a mut pointer.
|
||||
let mut session = self
|
||||
.mfareg_sessions
|
||||
.remove(&sessionid)
|
||||
.ok_or(OperationError::InvalidState)
|
||||
.map_err(|e| {
|
||||
admin_error!("Failed to register webauthn -> {:?}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
let (next, wan_cred) = session
|
||||
.webauthn_step(&origin, &wre.target, &wre.chal, webauthn)
|
||||
.map_err(|e| {
|
||||
admin_error!("Failed to register webauthn -> {:?}", e);
|
||||
OperationError::Webauthn
|
||||
})?;
|
||||
|
||||
if let (MfaRegNext::Success, Some(MfaRegCred::Webauthn(label, cred))) = (&next, wan_cred) {
|
||||
// Persist the credential
|
||||
let modlist = session
|
||||
.account
|
||||
.gen_securitykey_mod(label, cred)
|
||||
.map_err(|e| {
|
||||
admin_error!("Failed to gen webauthn mod {:?}", e);
|
||||
e
|
||||
})?;
|
||||
// Perform the mod
|
||||
self.qs_write
|
||||
.impersonate_modify(
|
||||
// Filter as executed
|
||||
&filter!(f_eq("uuid", PartialValue::new_uuid(session.account.uuid))),
|
||||
// Filter as intended (acp)
|
||||
&filter_all!(f_eq("uuid", PartialValue::new_uuid(session.account.uuid))),
|
||||
&modlist,
|
||||
&wre.ident,
|
||||
)
|
||||
.map_err(|e| {
|
||||
admin_error!("reg_account_webauthn_complete {:?}", e);
|
||||
e
|
||||
})?;
|
||||
}
|
||||
|
||||
let next = next.to_proto(sessionid);
|
||||
Ok(next)
|
||||
}
|
||||
|
||||
pub fn remove_account_webauthn(
|
||||
&mut self,
|
||||
rwe: &RemoveWebauthnEvent,
|
||||
) -> Result<SetCredentialResponse, OperationError> {
|
||||
trace!(
|
||||
"Attempting to remove webauthn {:?} -> {:?}",
|
||||
rwe.label,
|
||||
rwe.target
|
||||
);
|
||||
|
||||
let account = self.target_to_account(&rwe.target)?;
|
||||
let modlist = account
|
||||
.gen_securitykey_remove_mod(rwe.label.as_str())
|
||||
.map_err(|e| {
|
||||
admin_error!("Failed to gen webauthn remove mod {:?}", e);
|
||||
e
|
||||
})?;
|
||||
// Perform the mod
|
||||
self.qs_write
|
||||
.impersonate_modify(
|
||||
// Filter as executed
|
||||
&filter!(f_eq("uuid", PartialValue::new_uuid(account.uuid))),
|
||||
// Filter as intended (acp)
|
||||
&filter_all!(f_eq("uuid", PartialValue::new_uuid(account.uuid))),
|
||||
&modlist,
|
||||
&rwe.ident,
|
||||
)
|
||||
.map_err(|e| {
|
||||
admin_error!("remove_account_webauthn {:?}", e);
|
||||
e
|
||||
})
|
||||
.map(|_| SetCredentialResponse::Success)
|
||||
}
|
||||
*/
|
||||
|
||||
pub fn generate_account_totp(
|
||||
&mut self,
|
||||
gte: &GenerateTotpEvent,
|
||||
ct: Duration,
|
||||
) -> Result<SetCredentialResponse, OperationError> {
|
||||
let account = self.target_to_account(>e.target)?;
|
||||
let sessionid = uuid_from_duration(ct, self.sid);
|
||||
|
||||
let origin = (>e.ident.origin).into();
|
||||
|
||||
let issuer = self.qs_write.get_domain_display_name().to_string();
|
||||
|
||||
let (session, next) = MfaRegSession::totp_new(origin, account, issuer).map_err(|e| {
|
||||
admin_error!("Unable to start totp MfaRegSession {:?}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
let next = next.to_proto(sessionid);
|
||||
|
||||
// Add session to tree
|
||||
self.mfareg_sessions.insert(sessionid, session);
|
||||
trace!(?sessionid, "Started totp mfa reg session");
|
||||
Ok(next)
|
||||
}
|
||||
|
||||
pub fn verify_account_totp(
|
||||
&mut self,
|
||||
vte: &VerifyTotpEvent,
|
||||
ct: Duration,
|
||||
) -> Result<SetCredentialResponse, OperationError> {
|
||||
let sessionid = vte.session;
|
||||
let origin = (&vte.ident.origin).into();
|
||||
let chal = vte.chal;
|
||||
|
||||
trace!(?sessionid, "Attempting to find totp mfareg_session");
|
||||
|
||||
let (next, opt_cred) = self
|
||||
.mfareg_sessions
|
||||
.get_mut(&sessionid)
|
||||
.ok_or(OperationError::InvalidRequestState)
|
||||
.and_then(|session| session.totp_step(&origin, &vte.target, chal, &ct))
|
||||
.map_err(|e| {
|
||||
admin_error!("Failed to verify totp {:?}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
if let (MfaRegNext::Success, Some(MfaRegCred::Totp(token))) = (&next, opt_cred) {
|
||||
// Purge the session.
|
||||
let session = self
|
||||
.mfareg_sessions
|
||||
.remove(&sessionid)
|
||||
.ok_or(OperationError::InvalidState)
|
||||
.map_err(|e| {
|
||||
admin_error!("Session within totp reg transaction vanished!");
|
||||
e
|
||||
})?;
|
||||
// reg the token
|
||||
let modlist = session.account.gen_totp_mod(token).map_err(|e| {
|
||||
admin_error!("Failed to gen totp mod {:?}", e);
|
||||
e
|
||||
})?;
|
||||
// Perform the mod
|
||||
self.qs_write
|
||||
.impersonate_modify(
|
||||
// Filter as executed
|
||||
&filter!(f_eq("uuid", PartialValue::new_uuid(session.account.uuid))),
|
||||
// Filter as intended (acp)
|
||||
&filter_all!(f_eq("uuid", PartialValue::new_uuid(session.account.uuid))),
|
||||
&modlist,
|
||||
&vte.ident,
|
||||
)
|
||||
.map_err(|e| {
|
||||
admin_error!("verify_account_totp {:?}", e);
|
||||
e
|
||||
})?;
|
||||
};
|
||||
|
||||
let next = next.to_proto(sessionid);
|
||||
Ok(next)
|
||||
}
|
||||
|
||||
pub fn accept_account_sha1_totp(
|
||||
&mut self,
|
||||
aste: &AcceptSha1TotpEvent,
|
||||
) -> Result<SetCredentialResponse, OperationError> {
|
||||
let sessionid = aste.session;
|
||||
let origin = (&aste.ident.origin).into();
|
||||
|
||||
trace!(?sessionid, "Attempting to find mfareg_session");
|
||||
|
||||
let (next, opt_cred) = self
|
||||
.mfareg_sessions
|
||||
.get_mut(&sessionid)
|
||||
.ok_or(OperationError::InvalidRequestState)
|
||||
.and_then(|session| session.totp_accept_sha1(&origin, &aste.target))
|
||||
.map_err(|e| {
|
||||
admin_error!("Failed to accept SHA1 TOTP {:?}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
if let (MfaRegNext::Success, Some(MfaRegCred::Totp(token))) = (&next, opt_cred) {
|
||||
// Purge the session.
|
||||
let session = self
|
||||
.mfareg_sessions
|
||||
.remove(&sessionid)
|
||||
.ok_or(OperationError::InvalidState)
|
||||
.map_err(|e| {
|
||||
admin_error!("Session within transaction vanished!");
|
||||
e
|
||||
})?;
|
||||
// reg the token
|
||||
let modlist = session.account.gen_totp_mod(token).map_err(|e| {
|
||||
admin_error!("Failed to gen TOTP mod {:?}", e);
|
||||
e
|
||||
})?;
|
||||
// Perform the mod
|
||||
self.qs_write
|
||||
.impersonate_modify(
|
||||
// Filter as executed
|
||||
&filter!(f_eq("uuid", PartialValue::new_uuid(session.account.uuid))),
|
||||
// Filter as intended (acp)
|
||||
&filter_all!(f_eq("uuid", PartialValue::new_uuid(session.account.uuid))),
|
||||
&modlist,
|
||||
&aste.ident,
|
||||
)
|
||||
.map_err(|e| {
|
||||
admin_error!("accept_account_sha1_totp {:?}", e);
|
||||
e
|
||||
})?;
|
||||
};
|
||||
|
||||
let next = next.to_proto(sessionid);
|
||||
Ok(next)
|
||||
}
|
||||
|
||||
pub fn remove_account_totp(
|
||||
&mut self,
|
||||
rte: &RemoveTotpEvent,
|
||||
) -> Result<SetCredentialResponse, OperationError> {
|
||||
trace!(target = ?rte.target, "Attempting to remove totp");
|
||||
|
||||
let account = self.target_to_account(&rte.target)?;
|
||||
let modlist = account.gen_totp_remove_mod().map_err(|e| {
|
||||
admin_error!("Failed to gen totp remove mod {:?}", e);
|
||||
e
|
||||
})?;
|
||||
// Perform the mod
|
||||
self.qs_write
|
||||
.impersonate_modify(
|
||||
// Filter as executed
|
||||
&filter!(f_eq("uuid", PartialValue::new_uuid(account.uuid))),
|
||||
// Filter as intended (acp)
|
||||
&filter_all!(f_eq("uuid", PartialValue::new_uuid(account.uuid))),
|
||||
&modlist,
|
||||
&rte.ident,
|
||||
)
|
||||
.map_err(|e| {
|
||||
admin_error!("remove_account_totp {:?}", e);
|
||||
e
|
||||
})
|
||||
.map(|_| SetCredentialResponse::Success)
|
||||
}
|
||||
|
||||
// -- delayed action processing --
|
||||
fn process_pwupgrade(&mut self, pwu: &PasswordUpgrade) -> Result<(), OperationError> {
|
||||
// get the account
|
||||
|
@ -2161,7 +1866,6 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
self.uat_jwt_validator.commit();
|
||||
self.token_enc_key.commit();
|
||||
self.pw_badlist_cache.commit();
|
||||
self.mfareg_sessions.commit();
|
||||
self.cred_update_sessions.commit();
|
||||
trace!("cred_update_session.commit");
|
||||
self.qs_write.commit()
|
||||
|
@ -2184,20 +1888,16 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::credential::policy::CryptoPolicy;
|
||||
use crate::credential::totp::Totp;
|
||||
use crate::credential::{Credential, Password};
|
||||
use crate::event::{AuthEvent, AuthResult, CreateEvent, ModifyEvent};
|
||||
use crate::idm::delayed::{BackupCodeRemoval, DelayedAction};
|
||||
use crate::idm::event::{
|
||||
AcceptSha1TotpEvent, GenerateBackupCodeEvent, GenerateTotpEvent, PasswordChangeEvent,
|
||||
RadiusAuthTokenEvent, RegenerateRadiusSecretEvent, RemoveTotpEvent, UnixGroupTokenEvent,
|
||||
UnixPasswordChangeEvent, UnixUserAuthEvent, UnixUserTokenEvent, VerifyTotpEvent,
|
||||
PasswordChangeEvent, RadiusAuthTokenEvent, RegenerateRadiusSecretEvent,
|
||||
UnixGroupTokenEvent, UnixPasswordChangeEvent, UnixUserAuthEvent, UnixUserTokenEvent,
|
||||
};
|
||||
use crate::idm::AuthState;
|
||||
use crate::modify::{Modify, ModifyList};
|
||||
use crate::prelude::*;
|
||||
use kanidm_proto::v1::OperationError;
|
||||
use kanidm_proto::v1::SetCredentialResponse;
|
||||
use kanidm_proto::v1::{AuthAllowed, AuthMech, AuthType};
|
||||
|
||||
use crate::idm::server::{IdmServer, IdmServerTransaction};
|
||||
|
@ -2931,194 +2631,6 @@ mod tests {
|
|||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_idm_totp_registration() {
|
||||
run_idm_test!(
|
||||
|_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed| {
|
||||
let ct = duration_from_epoch_now();
|
||||
let expire = Duration::from_secs(ct.as_secs() + MFAREG_SESSION_TIMEOUT + 2);
|
||||
let mut idms_prox_write = idms.proxy_write(ct.clone());
|
||||
|
||||
// verify with no session (fail)
|
||||
let vte1 = VerifyTotpEvent::new_internal(UUID_ADMIN.clone(), Uuid::new_v4(), 0);
|
||||
|
||||
match idms_prox_write.verify_account_totp(&vte1, ct.clone()) {
|
||||
Err(e) => {
|
||||
assert!(e == OperationError::InvalidRequestState);
|
||||
}
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
// reg, expire session, attempt verify (fail)
|
||||
let gte1 = GenerateTotpEvent::new_internal(UUID_ADMIN.clone());
|
||||
|
||||
let res = idms_prox_write
|
||||
.generate_account_totp(>e1, ct.clone())
|
||||
.unwrap();
|
||||
let sesid = match res {
|
||||
SetCredentialResponse::TotpCheck(id, _) => id,
|
||||
_ => panic!("invalid state!"),
|
||||
};
|
||||
idms_prox_write.expire_mfareg_sessions(expire.clone());
|
||||
|
||||
let vte2 = VerifyTotpEvent::new_internal(UUID_ADMIN.clone(), sesid, 0);
|
||||
|
||||
match idms_prox_write.verify_account_totp(&vte1, ct.clone()) {
|
||||
Err(e) => {
|
||||
assert!(e == OperationError::InvalidRequestState);
|
||||
}
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
// == Test TOTP on account with no password (fail)
|
||||
let res = idms_prox_write
|
||||
.generate_account_totp(>e1, ct.clone())
|
||||
.unwrap();
|
||||
let (sesid, tok) = match res {
|
||||
SetCredentialResponse::TotpCheck(id, tok) => (id, tok),
|
||||
_ => panic!("invalid state!"),
|
||||
};
|
||||
// get the correct otp
|
||||
let r_tok: Totp = tok.into();
|
||||
let chal = r_tok
|
||||
.do_totp_duration_from_epoch(&ct)
|
||||
.expect("Failed to do totp?");
|
||||
// attempt the verify
|
||||
let vte3 = VerifyTotpEvent::new_internal(UUID_ADMIN.clone(), sesid, chal);
|
||||
|
||||
match idms_prox_write.verify_account_totp(&vte3, ct.clone()) {
|
||||
Err(e) => assert!(e == OperationError::InvalidState),
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
// Expire the session to allow it to reset.
|
||||
idms_prox_write.expire_mfareg_sessions(expire.clone());
|
||||
|
||||
// Set a password.
|
||||
let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, TEST_PASSWORD);
|
||||
assert!(idms_prox_write.set_account_password(&pce).is_ok());
|
||||
|
||||
// == reg, but change the event source part way in the process (failure)
|
||||
let res = idms_prox_write
|
||||
.generate_account_totp(>e1, ct.clone())
|
||||
.unwrap();
|
||||
let (sesid, tok) = match res {
|
||||
SetCredentialResponse::TotpCheck(id, tok) => (id, tok),
|
||||
_ => panic!("invalid state!"),
|
||||
};
|
||||
// get the correct otp
|
||||
let r_tok: Totp = tok.into();
|
||||
let chal = r_tok
|
||||
.do_totp_duration_from_epoch(&ct)
|
||||
.expect("Failed to do totp?");
|
||||
// attempt the verify
|
||||
let vte3 = VerifyTotpEvent::new_internal(UUID_ANONYMOUS.clone(), sesid, chal);
|
||||
|
||||
match idms_prox_write.verify_account_totp(&vte3, ct.clone()) {
|
||||
Err(e) => assert!(e == OperationError::InvalidRequestState),
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
// == reg, verify w_ incorrect totp (fail)
|
||||
let res = idms_prox_write
|
||||
.generate_account_totp(>e1, ct.clone())
|
||||
.unwrap();
|
||||
let (_sesid, _tok) = match res {
|
||||
SetCredentialResponse::TotpCheck(id, tok) => (id, tok),
|
||||
_ => panic!("invalid state!"),
|
||||
};
|
||||
|
||||
// We can reuse the OTP/Vte2 from before, since we want the invalid otp.
|
||||
match idms_prox_write.verify_account_totp(&vte2, ct.clone()) {
|
||||
// On failure we get back another attempt to setup the token.
|
||||
Ok(SetCredentialResponse::TotpCheck(_id, _tok)) => {}
|
||||
_ => panic!(),
|
||||
};
|
||||
idms_prox_write.expire_mfareg_sessions(expire.clone());
|
||||
|
||||
// Turn the pts into an otp
|
||||
// == reg, verify w_ correct totp (success)
|
||||
let res = idms_prox_write
|
||||
.generate_account_totp(>e1, ct.clone())
|
||||
.unwrap();
|
||||
let (sesid, tok) = match res {
|
||||
SetCredentialResponse::TotpCheck(id, tok) => (id, tok),
|
||||
_ => panic!("invalid state!"),
|
||||
};
|
||||
// We can't reuse the OTP/Vte from before, since the token seed changes
|
||||
let r_tok: Totp = tok.into();
|
||||
let chal = r_tok
|
||||
.do_totp_duration_from_epoch(&ct)
|
||||
.expect("Failed to do totp?");
|
||||
// attempt the verify
|
||||
let vte3 = VerifyTotpEvent::new_internal(UUID_ADMIN.clone(), sesid, chal);
|
||||
|
||||
match idms_prox_write.verify_account_totp(&vte3, ct.clone()) {
|
||||
Ok(SetCredentialResponse::Success) => {}
|
||||
_ => panic!(),
|
||||
};
|
||||
idms_prox_write.expire_mfareg_sessions(expire.clone());
|
||||
|
||||
// Test removing the TOTP and then authing with password only.
|
||||
let rte = RemoveTotpEvent::new_internal(UUID_ADMIN.clone());
|
||||
idms_prox_write.remove_account_totp(&rte).unwrap();
|
||||
assert!(idms_prox_write.commit().is_ok());
|
||||
|
||||
check_admin_password(idms, TEST_PASSWORD);
|
||||
// All done!
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_idm_totp_sha1_registration() {
|
||||
run_idm_test!(
|
||||
|_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed| {
|
||||
let ct = duration_from_epoch_now();
|
||||
let mut idms_prox_write = idms.proxy_write(ct.clone());
|
||||
|
||||
let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, TEST_PASSWORD);
|
||||
assert!(idms_prox_write.set_account_password(&pce).is_ok());
|
||||
|
||||
// Start registering the TOTP
|
||||
let gte1 = GenerateTotpEvent::new_internal(UUID_ADMIN.clone());
|
||||
let res = idms_prox_write
|
||||
.generate_account_totp(>e1, ct.clone())
|
||||
.unwrap();
|
||||
let (sesid, tok) = match res {
|
||||
SetCredentialResponse::TotpCheck(id, tok) => (id, tok),
|
||||
_ => panic!("invalid state!"),
|
||||
};
|
||||
|
||||
let r_tok: Totp = tok.into();
|
||||
// Now, assert that the Totp is NOT sha1 (correct default behaviour).
|
||||
assert!(!r_tok.is_legacy_algo());
|
||||
// Mutate the tok to a legacy token.
|
||||
let legacy_tok = r_tok.downgrade_to_legacy();
|
||||
|
||||
let chal = legacy_tok
|
||||
.do_totp_duration_from_epoch(&ct)
|
||||
.expect("Failed to do totp?");
|
||||
// attempt the verify
|
||||
let vte3 = VerifyTotpEvent::new_internal(UUID_ADMIN.clone(), sesid, chal);
|
||||
|
||||
match idms_prox_write.verify_account_totp(&vte3, ct.clone()) {
|
||||
Ok(SetCredentialResponse::TotpInvalidSha1(_)) => {}
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
let aste = AcceptSha1TotpEvent::new_internal(UUID_ADMIN.clone(), sesid);
|
||||
|
||||
match idms_prox_write.accept_account_sha1_totp(&aste) {
|
||||
Ok(SetCredentialResponse::Success) => {}
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
// Done!
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_idm_simple_password_upgrade() {
|
||||
run_idm_test!(
|
||||
|
@ -3722,85 +3234,6 @@ mod tests {
|
|||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_idm_backup_code_removal_delayed_action() {
|
||||
run_idm_test!(
|
||||
|_qs: &QueryServer, idms: &IdmServer, idms_delayed: &mut IdmServerDelayed| {
|
||||
let ct = duration_from_epoch_now();
|
||||
let expire = ct - Duration::from_secs(AUTH_SESSION_TIMEOUT);
|
||||
let mut idms_prox_write = idms.proxy_write(ct.clone());
|
||||
|
||||
// let mut wa_softtok = WebauthnAuthenticator::new(SoftPasskey::new());
|
||||
|
||||
// The account must has primary credential + uses MFA before generating backup codes
|
||||
// Set a password.
|
||||
let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, TEST_PASSWORD);
|
||||
assert!(idms_prox_write.set_account_password(&pce).is_ok());
|
||||
// Generate TOTP
|
||||
let gte1 = GenerateTotpEvent::new_internal(UUID_ADMIN.clone());
|
||||
// == reg, verify w_ correct totp (success)
|
||||
let res = idms_prox_write
|
||||
.generate_account_totp(>e1, ct.clone())
|
||||
.unwrap();
|
||||
let (sesid, tok) = match res {
|
||||
SetCredentialResponse::TotpCheck(id, tok) => (id, tok),
|
||||
_ => panic!("invalid state!"),
|
||||
};
|
||||
let r_tok: Totp = tok.into();
|
||||
let chal = r_tok
|
||||
.do_totp_duration_from_epoch(&ct)
|
||||
.expect("Failed to do totp?");
|
||||
// attempt the verify
|
||||
let vte3 = VerifyTotpEvent::new_internal(UUID_ADMIN.clone(), sesid, chal);
|
||||
|
||||
match idms_prox_write.verify_account_totp(&vte3, ct.clone()) {
|
||||
Ok(SetCredentialResponse::Success) => {}
|
||||
_ => panic!(),
|
||||
};
|
||||
idms_prox_write.expire_mfareg_sessions(expire.clone());
|
||||
|
||||
// Generate backup codes
|
||||
let gbe = GenerateBackupCodeEvent::new_internal(UUID_ADMIN.clone());
|
||||
|
||||
let backup_codes_vec = idms_prox_write.generate_backup_code(&gbe).unwrap();
|
||||
let code_for_test = &backup_codes_vec[0];
|
||||
// Get the account now so we can peek at the registered credential.
|
||||
let account = idms_prox_write
|
||||
.target_to_account(&UUID_ADMIN)
|
||||
.expect("account must exist");
|
||||
|
||||
let cred = account.primary.expect("Must exist.");
|
||||
|
||||
let backup_codes_view = cred.get_backup_code_view().expect("must have view");
|
||||
assert!(backup_codes_view.backup_codes.contains(code_for_test));
|
||||
assert!(idms_prox_write.commit().is_ok());
|
||||
|
||||
// Assert the delayed action queue is empty
|
||||
idms_delayed.check_is_empty_or_panic();
|
||||
|
||||
// Generate a fake action to remove one backup code
|
||||
let da = DelayedAction::BackupCodeRemoval(BackupCodeRemoval {
|
||||
target_uuid: UUID_ADMIN.clone(),
|
||||
code_to_remove: code_for_test.to_string(),
|
||||
});
|
||||
let r = task::block_on(idms.delayed_action(duration_from_epoch_now(), da));
|
||||
assert!(Ok(true) == r);
|
||||
|
||||
// Check the removed backup code is no longer in the set
|
||||
let mut idms_prox_write = idms.proxy_write(ct.clone());
|
||||
|
||||
let account = idms_prox_write
|
||||
.target_to_account(&UUID_ADMIN)
|
||||
.expect("account must exist");
|
||||
|
||||
let cred = account.primary.expect("Must exist.");
|
||||
|
||||
let backup_codes_view = cred.get_backup_code_view().expect("must have view");
|
||||
assert!(!backup_codes_view.backup_codes.contains(code_for_test));
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_idm_jwt_uat_expiry() {
|
||||
run_idm_test!(
|
||||
|
@ -3980,4 +3413,39 @@ mod tests {
|
|||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_idm_service_account_to_person() {
|
||||
run_idm_test!(|_qs: &QueryServer,
|
||||
idms: &IdmServer,
|
||||
_idms_delayed: &mut IdmServerDelayed| {
|
||||
let ct = Duration::from_secs(TEST_CURRENT_TIME);
|
||||
let idms_prox_write = idms.proxy_write(ct.clone());
|
||||
|
||||
let ident = Identity::from_internal();
|
||||
let target_uuid = Uuid::new_v4();
|
||||
|
||||
// Create a service account
|
||||
let e = entry_init!(
|
||||
("class", Value::new_class("object")),
|
||||
("class", Value::new_class("account")),
|
||||
("class", Value::new_class("service_account")),
|
||||
("name", Value::new_iname("testaccount")),
|
||||
("uuid", Value::new_uuid(target_uuid)),
|
||||
("description", Value::new_utf8s("testaccount")),
|
||||
("displayname", Value::new_utf8s("Test Account"))
|
||||
);
|
||||
|
||||
let ce = CreateEvent::new_internal(vec![e]);
|
||||
let cr = idms_prox_write.qs_write.create(&ce);
|
||||
assert!(cr.is_ok());
|
||||
|
||||
// Do the migrate.
|
||||
assert!(idms_prox_write
|
||||
.service_account_into_person(&ident, target_uuid)
|
||||
.is_ok());
|
||||
|
||||
// Any checks?
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,13 +97,16 @@ impl IntervalActor {
|
|||
);
|
||||
|
||||
sleep(Duration::from_secs(wait_seconds)).await;
|
||||
server
|
||||
if let Err(e) = server
|
||||
.handle_online_backup(
|
||||
OnlineBackupEvent::new(),
|
||||
outpath.clone().as_str(),
|
||||
versions,
|
||||
)
|
||||
.await;
|
||||
.await
|
||||
{
|
||||
error!(?e, "An online backup error occured.");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -848,4 +848,34 @@ mod tests {
|
|||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ldap_token_privilege_granting() {
|
||||
run_idm_test!(
|
||||
|_qs: &QueryServer, idms: &IdmServer, _idms_delayed: &IdmServerDelayed| {
|
||||
// Configure the user account that will have the tokens issued.
|
||||
// Should be a SERVICE account.
|
||||
|
||||
// Should I finally do the class rules shit?
|
||||
|
||||
// Issue a token, make it purpose = ldap.
|
||||
|
||||
// assert the uat fails on non-ldap events.
|
||||
|
||||
// Setup a person with
|
||||
// Setup an access control for the service account to view mail attrs.
|
||||
|
||||
// Setup the ldap server
|
||||
let _ldaps = LdapServer::new(idms).expect("failed to start ldap");
|
||||
|
||||
// Bind with account pw, search and show attr isn't accessible.
|
||||
|
||||
// Bind using the token instead of the PW.
|
||||
|
||||
// Search and retrieve an attribute that's now accessible.
|
||||
|
||||
assert!(true);
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#[cfg(test)]
|
||||
macro_rules! setup_test {
|
||||
() => {{
|
||||
let _ = sketching::test_init();
|
||||
|
@ -125,6 +126,7 @@ macro_rules! entry_str_to_account {
|
|||
}};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
macro_rules! run_idm_test_inner {
|
||||
($test_fn:expr) => {{
|
||||
#[allow(unused_imports)]
|
||||
|
@ -166,6 +168,7 @@ macro_rules! run_idm_test {
|
|||
}};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn run_idm_test_no_logging<F>(mut test_fn: F)
|
||||
where
|
||||
F: FnMut(
|
||||
|
|
|
@ -173,7 +173,7 @@ mod tests {
|
|||
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["account"],
|
||||
"class": ["account", "service_account"],
|
||||
"name": ["testperson"],
|
||||
"uuid": ["83a0927f-3de1-45ec-bea0-2f7b997ef244"],
|
||||
"description": ["testperson"],
|
||||
|
|
|
@ -78,7 +78,7 @@ fn do_memberof(
|
|||
} else {
|
||||
// Means MO is empty, so we need to duplicate dmo to allow things to
|
||||
// proceed.
|
||||
mo = Some(dmo.clone());
|
||||
mo = Some(dmo);
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -91,14 +91,11 @@ impl Plugin for ReferentialIntegrity {
|
|||
let ref_types = schema.get_reference_types();
|
||||
|
||||
// Fast Path
|
||||
let mut vsiter = cand
|
||||
.iter()
|
||||
.map(|c| {
|
||||
ref_types
|
||||
.values()
|
||||
.filter_map(move |rtype| c.get_ava_set(&rtype.name))
|
||||
})
|
||||
.flatten();
|
||||
let mut vsiter = cand.iter().flat_map(|c| {
|
||||
ref_types
|
||||
.values()
|
||||
.filter_map(move |rtype| c.get_ava_set(&rtype.name))
|
||||
});
|
||||
|
||||
// Could check len first?
|
||||
let mut i = Vec::new();
|
||||
|
@ -183,11 +180,10 @@ impl Plugin for ReferentialIntegrity {
|
|||
// .iter()
|
||||
cand.iter()
|
||||
.map(|e| e.get_uuid())
|
||||
.map(|u| ref_types.values().map(move |r_type| {
|
||||
.flat_map(|u| ref_types.values().map(move |r_type| {
|
||||
// For everything that references the uuid's in the deleted set.
|
||||
f_eq(r_type.name.as_str(), PartialValue::new_refer(u))
|
||||
}))
|
||||
.flatten()
|
||||
.collect(),
|
||||
));
|
||||
|
||||
|
|
|
@ -203,7 +203,7 @@ mod tests {
|
|||
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["account"],
|
||||
"class": ["account", "service_account"],
|
||||
"name": ["testperson"],
|
||||
"description": ["testperson"],
|
||||
"displayname": ["testperson"]
|
||||
|
@ -230,7 +230,7 @@ mod tests {
|
|||
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["account"],
|
||||
"class": ["account", "service_account"],
|
||||
"name": ["testperson"],
|
||||
"description": ["testperson"],
|
||||
"displayname": ["testperson"]
|
||||
|
@ -257,7 +257,7 @@ mod tests {
|
|||
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["account"],
|
||||
"class": ["account", "service_account"],
|
||||
"spn": ["testperson@invalid_domain.com"],
|
||||
"name": ["testperson"],
|
||||
"description": ["testperson"],
|
||||
|
@ -284,7 +284,7 @@ mod tests {
|
|||
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["account"],
|
||||
"class": ["account", "service_account"],
|
||||
"name": ["testperson"],
|
||||
"description": ["testperson"],
|
||||
"displayname": ["testperson"]
|
||||
|
|
|
@ -70,8 +70,8 @@ impl fmt::Display for State {
|
|||
enum Transition {
|
||||
Create(Eattrs),
|
||||
ModifyPurge(AttrString),
|
||||
ModifyPresent(AttrString, Value),
|
||||
ModifyRemoved(AttrString, PartialValue),
|
||||
ModifyPresent(AttrString, Box<Value>),
|
||||
ModifyRemoved(AttrString, Box<PartialValue>),
|
||||
Recycle,
|
||||
Revive,
|
||||
Tombstone(Eattrs),
|
||||
|
@ -107,11 +107,11 @@ impl State {
|
|||
(State::Live(ref mut attrs), Transition::ModifyPresent(attr, value)) => {
|
||||
trace!("Live + ModifyPresent({}) -> Live", attr);
|
||||
if let Some(vs) = attrs.get_mut(attr) {
|
||||
let r = vs.insert_checked(value.clone());
|
||||
let r = vs.insert_checked(value.as_ref().clone());
|
||||
assert!(r.is_ok());
|
||||
// Reject if it fails?
|
||||
} else {
|
||||
let vs = valueset::from_value_iter(std::iter::once(value.clone()))
|
||||
let vs = valueset::from_value_iter(std::iter::once(value.as_ref().clone()))
|
||||
.expect("Unable to fail - not empty, and only one type!");
|
||||
attrs.insert(attr.clone(), vs);
|
||||
}
|
||||
|
@ -218,19 +218,13 @@ impl EntryChangelog {
|
|||
.map(|c| c.contains(&PVCLASS_TOMBSTONE as &PartialValue))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
(
|
||||
btreemap![(cid.clone(), State::Tombstone(attrs))],
|
||||
BTreeMap::new(),
|
||||
)
|
||||
(btreemap![(cid, State::Tombstone(attrs))], BTreeMap::new())
|
||||
} else if class
|
||||
.as_ref()
|
||||
.map(|c| c.contains(&PVCLASS_RECYCLED as &PartialValue))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
(
|
||||
btreemap![(cid.clone(), State::Recycled(attrs))],
|
||||
BTreeMap::new(),
|
||||
)
|
||||
(btreemap![(cid, State::Recycled(attrs))], BTreeMap::new())
|
||||
} else {
|
||||
(
|
||||
btreemap![(cid.clone(), State::NonExistent)],
|
||||
|
@ -262,7 +256,7 @@ impl EntryChangelog {
|
|||
|
||||
viter
|
||||
.into_iter()
|
||||
.map(|v| Transition::ModifyPresent(AttrString::from(attr), v))
|
||||
.map(|v| Transition::ModifyPresent(AttrString::from(attr), Box::new(v)))
|
||||
.for_each(|t| change.s.push(t));
|
||||
}
|
||||
|
||||
|
@ -282,7 +276,7 @@ impl EntryChangelog {
|
|||
|
||||
viter
|
||||
.into_iter()
|
||||
.map(|v| Transition::ModifyRemoved(AttrString::from(attr), v))
|
||||
.map(|v| Transition::ModifyRemoved(AttrString::from(attr), Box::new(v)))
|
||||
.for_each(|t| change.s.push(t));
|
||||
}
|
||||
|
||||
|
@ -510,7 +504,7 @@ impl EntryChangelog {
|
|||
|
||||
// insert_anchor will remove anything to the right, we also need to
|
||||
// remove everything to the left, so just clear.
|
||||
let _ = self.anchors.clear();
|
||||
self.anchors.clear();
|
||||
self.anchors.insert(cid.clone(), entry_state);
|
||||
|
||||
// And now split the CL.
|
||||
|
|
|
@ -3,6 +3,7 @@ use crate::repl::cid::Cid;
|
|||
use concread::bptree::{BptreeMap, BptreeMapReadTxn, BptreeMapWriteTxn};
|
||||
use idlset::v2::IDLBitRange;
|
||||
use kanidm_proto::v1::ConsistencyError;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BTreeMap;
|
||||
use std::ops::Bound::*;
|
||||
use std::sync::Arc;
|
||||
|
@ -60,7 +61,7 @@ pub trait ReplicationUpdateVectorTransaction {
|
|||
// We don't need the details of the change - only the cid of the
|
||||
// change that this entry was involved in.
|
||||
for cid in eclog.cid_iter() {
|
||||
if let Some(idl) = check_ruv.get_mut(&cid) {
|
||||
if let Some(idl) = check_ruv.get_mut(cid) {
|
||||
// We can't guarantee id order, so we have to do this properly.
|
||||
idl.insert_id(eid);
|
||||
} else {
|
||||
|
@ -86,26 +87,30 @@ pub trait ReplicationUpdateVectorTransaction {
|
|||
let mut snap_next = snap_iter.next();
|
||||
|
||||
while let (Some((ck, cv)), Some((sk, sv))) = (&check_next, &snap_next) {
|
||||
if ck == sk {
|
||||
if cv == sv {
|
||||
trace!("{:?} is consistent!", ck);
|
||||
} else {
|
||||
admin_warn!("{:?} is NOT consistent! IDL's differ", ck);
|
||||
match ck.cmp(sk) {
|
||||
Ordering::Equal => {
|
||||
if cv == sv {
|
||||
trace!("{:?} is consistent!", ck);
|
||||
} else {
|
||||
admin_warn!("{:?} is NOT consistent! IDL's differ", ck);
|
||||
debug_assert!(false);
|
||||
results.push(Err(ConsistencyError::RuvInconsistent(ck.to_string())));
|
||||
}
|
||||
check_next = check_iter.next();
|
||||
snap_next = snap_iter.next();
|
||||
}
|
||||
Ordering::Less => {
|
||||
admin_warn!("{:?} is NOT consistent! CID missing from RUV", ck);
|
||||
debug_assert!(false);
|
||||
results.push(Err(ConsistencyError::RuvInconsistent(ck.to_string())));
|
||||
check_next = check_iter.next();
|
||||
}
|
||||
Ordering::Greater => {
|
||||
admin_warn!("{:?} is NOT consistent! CID should not exist in RUV", sk);
|
||||
debug_assert!(false);
|
||||
results.push(Err(ConsistencyError::RuvInconsistent(sk.to_string())));
|
||||
snap_next = snap_iter.next();
|
||||
}
|
||||
check_next = check_iter.next();
|
||||
snap_next = snap_iter.next();
|
||||
} else if ck < sk {
|
||||
admin_warn!("{:?} is NOT consistent! CID missing from RUV", ck);
|
||||
debug_assert!(false);
|
||||
results.push(Err(ConsistencyError::RuvInconsistent(ck.to_string())));
|
||||
check_next = check_iter.next();
|
||||
} else {
|
||||
admin_warn!("{:?} is NOT consistent! CID should not exist in RUV", sk);
|
||||
debug_assert!(false);
|
||||
results.push(Err(ConsistencyError::RuvInconsistent(sk.to_string())));
|
||||
snap_next = snap_iter.next();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -159,7 +164,7 @@ impl<'a> ReplicationUpdateVectorWriteTransaction<'a> {
|
|||
// We don't need the details of the change - only the cid of the
|
||||
// change that this entry was involved in.
|
||||
for cid in eclog.cid_iter() {
|
||||
if let Some(idl) = rebuild_ruv.get_mut(&cid) {
|
||||
if let Some(idl) = rebuild_ruv.get_mut(cid) {
|
||||
// We can't guarantee id order, so we have to do this properly.
|
||||
idl.insert_id(eid);
|
||||
} else {
|
||||
|
@ -181,9 +186,9 @@ impl<'a> ReplicationUpdateVectorWriteTransaction<'a> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn insert_change(&mut self, cid: Cid, idl: IDLBitRange) -> Result<(), OperationError> {
|
||||
pub fn insert_change(&mut self, cid: &Cid, idl: IDLBitRange) -> Result<(), OperationError> {
|
||||
// Remember, in a transaction the changes can be updated multiple times.
|
||||
if let Some(ex_idl) = self.data.get_mut(&cid) {
|
||||
if let Some(ex_idl) = self.data.get_mut(cid) {
|
||||
// This ensures both sets have all the available ids.
|
||||
let idl = ex_idl as &_ | &idl;
|
||||
*ex_idl = idl;
|
||||
|
|
|
@ -23,6 +23,7 @@ use kanidm_proto::v1::{ConsistencyError, OperationError, SchemaError};
|
|||
use tracing::trace;
|
||||
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use std::collections::BTreeSet;
|
||||
use uuid::Uuid;
|
||||
|
||||
use concread::cowcell::*;
|
||||
|
@ -292,18 +293,27 @@ impl SchemaAttribute {
|
|||
///
|
||||
/// [`Entry`]: ../entry/index.html
|
||||
/// [`access`]: ../access/index.html
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct SchemaClass {
|
||||
// Is this used?
|
||||
// class: Vec<String>,
|
||||
pub name: AttrString,
|
||||
pub uuid: Uuid,
|
||||
pub description: String,
|
||||
// This allows modification of system types to be extended in custom ways
|
||||
/// This allows modification of system types to be extended in custom ways
|
||||
pub systemmay: Vec<AttrString>,
|
||||
pub may: Vec<AttrString>,
|
||||
pub systemmust: Vec<AttrString>,
|
||||
pub must: Vec<AttrString>,
|
||||
/// A list of classes that this extends. These are an "or", as at least one
|
||||
/// of the supplementing classes must also be present. Think of this as
|
||||
/// "inherits toward" or "provides". This is just as "strict" as requires but
|
||||
/// operates in the opposite direction allowing a tree structure.
|
||||
pub systemsupplements: Vec<AttrString>,
|
||||
pub supplements: Vec<AttrString>,
|
||||
/// A list of classes that can not co-exist with this item at the same time.
|
||||
pub systemexcludes: Vec<AttrString>,
|
||||
pub excludes: Vec<AttrString>,
|
||||
}
|
||||
|
||||
impl SchemaClass {
|
||||
|
@ -354,6 +364,23 @@ impl SchemaClass {
|
|||
.map(|i| i.map(AttrString::from).collect())
|
||||
.unwrap_or_else(Vec::new);
|
||||
|
||||
let systemsupplements = value
|
||||
.get_ava_iter_iutf8("systemsupplements")
|
||||
.map(|i| i.map(AttrString::from).collect())
|
||||
.unwrap_or_else(Vec::new);
|
||||
let supplements = value
|
||||
.get_ava_iter_iutf8("supplements")
|
||||
.map(|i| i.map(AttrString::from).collect())
|
||||
.unwrap_or_else(Vec::new);
|
||||
let systemexcludes = value
|
||||
.get_ava_iter_iutf8("systemexcludes")
|
||||
.map(|i| i.map(AttrString::from).collect())
|
||||
.unwrap_or_else(Vec::new);
|
||||
let excludes = value
|
||||
.get_ava_iter_iutf8("excludes")
|
||||
.map(|i| i.map(AttrString::from).collect())
|
||||
.unwrap_or_else(Vec::new);
|
||||
|
||||
Ok(SchemaClass {
|
||||
name,
|
||||
uuid,
|
||||
|
@ -362,8 +389,22 @@ impl SchemaClass {
|
|||
may,
|
||||
systemmust,
|
||||
must,
|
||||
systemsupplements,
|
||||
supplements,
|
||||
systemexcludes,
|
||||
excludes,
|
||||
})
|
||||
}
|
||||
|
||||
/// An iterator over the full set of attrs that may or must exist
|
||||
/// on this class.
|
||||
pub fn may_iter(&self) -> impl Iterator<Item = &AttrString> {
|
||||
self.systemmay
|
||||
.iter()
|
||||
.chain(self.may.iter())
|
||||
.chain(self.systemmust.iter())
|
||||
.chain(self.must.iter())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SchemaTransaction {
|
||||
|
@ -449,6 +490,59 @@ pub trait SchemaTransaction {
|
|||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn query_attrs_difference(
|
||||
&self,
|
||||
prev_class: &BTreeSet<&str>,
|
||||
new_class: &BTreeSet<&str>,
|
||||
) -> Result<(BTreeSet<&str>, BTreeSet<&str>), SchemaError> {
|
||||
let schema_classes = self.get_classes();
|
||||
|
||||
let mut invalid_classes = Vec::with_capacity(0);
|
||||
|
||||
let prev_attrs: BTreeSet<&str> = prev_class
|
||||
.iter()
|
||||
.filter_map(|cls| match schema_classes.get(*cls) {
|
||||
Some(x) => Some(x.may_iter()),
|
||||
None => {
|
||||
admin_debug!("invalid class: {:?}", cls);
|
||||
invalid_classes.push(cls.to_string());
|
||||
None
|
||||
}
|
||||
})
|
||||
// flatten all the inner iters.
|
||||
.flatten()
|
||||
.map(|s| s.as_str())
|
||||
.collect();
|
||||
|
||||
if !invalid_classes.is_empty() {
|
||||
return Err(SchemaError::InvalidClass(invalid_classes));
|
||||
};
|
||||
|
||||
let new_attrs: BTreeSet<&str> = new_class
|
||||
.iter()
|
||||
.filter_map(|cls| match schema_classes.get(*cls) {
|
||||
Some(x) => Some(x.may_iter()),
|
||||
None => {
|
||||
admin_debug!("invalid class: {:?}", cls);
|
||||
invalid_classes.push(cls.to_string());
|
||||
None
|
||||
}
|
||||
})
|
||||
// flatten all the inner iters.
|
||||
.flatten()
|
||||
.map(|s| s.as_str())
|
||||
.collect();
|
||||
|
||||
if !invalid_classes.is_empty() {
|
||||
return Err(SchemaError::InvalidClass(invalid_classes));
|
||||
};
|
||||
|
||||
let removed = prev_attrs.difference(&new_attrs).copied().collect();
|
||||
let added = new_attrs.difference(&prev_attrs).copied().collect();
|
||||
|
||||
Ok((added, removed))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SchemaWriteTransaction<'a> {
|
||||
|
@ -531,7 +625,7 @@ impl<'a> SchemaWriteTransaction<'a> {
|
|||
.flat_map(|a| {
|
||||
a.index.iter().map(move |itype: &IndexType| IdxKey {
|
||||
attr: a.name.clone(),
|
||||
itype: (*itype).clone(),
|
||||
itype: *itype,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
|
@ -774,6 +868,67 @@ impl<'a> SchemaWriteTransaction<'a> {
|
|||
syntax: SyntaxType::Utf8StringInsensitive,
|
||||
},
|
||||
);
|
||||
self.attributes.insert(
|
||||
AttrString::from("systemsupplements"),
|
||||
SchemaAttribute {
|
||||
name: AttrString::from("systemsupplements"),
|
||||
uuid: UUID_SCHEMA_ATTR_SYSTEMSUPPLEMENTS,
|
||||
description: String::from(
|
||||
"A set of classes that this type supplements too, where this class can't exist without their presence.",
|
||||
),
|
||||
multivalue: true,
|
||||
unique: false,
|
||||
phantom: false,
|
||||
index: vec![],
|
||||
syntax: SyntaxType::Utf8StringInsensitive,
|
||||
},
|
||||
);
|
||||
self.attributes.insert(
|
||||
AttrString::from("supplements"),
|
||||
SchemaAttribute {
|
||||
name: AttrString::from("supplements"),
|
||||
uuid: UUID_SCHEMA_ATTR_SUPPLEMENTS,
|
||||
description: String::from(
|
||||
"A set of user modifiable classes, where this determines that at least one other type must supplement this type",
|
||||
),
|
||||
multivalue: true,
|
||||
unique: false,
|
||||
phantom: false,
|
||||
index: vec![],
|
||||
syntax: SyntaxType::Utf8StringInsensitive,
|
||||
},
|
||||
);
|
||||
self.attributes.insert(
|
||||
AttrString::from("systemexcludes"),
|
||||
SchemaAttribute {
|
||||
name: AttrString::from("systemexcludes"),
|
||||
uuid: UUID_SCHEMA_ATTR_SYSTEMEXCLUDES,
|
||||
description: String::from(
|
||||
"A set of classes that are denied presence in connection to this class",
|
||||
),
|
||||
multivalue: true,
|
||||
unique: false,
|
||||
phantom: false,
|
||||
index: vec![],
|
||||
syntax: SyntaxType::Utf8StringInsensitive,
|
||||
},
|
||||
);
|
||||
self.attributes.insert(
|
||||
AttrString::from("excludes"),
|
||||
SchemaAttribute {
|
||||
name: AttrString::from("excludes"),
|
||||
uuid: UUID_SCHEMA_ATTR_EXCLUDES,
|
||||
description: String::from(
|
||||
"A set of user modifiable classes that are denied presence in connection to this class",
|
||||
),
|
||||
multivalue: true,
|
||||
unique: false,
|
||||
phantom: false,
|
||||
index: vec![],
|
||||
syntax: SyntaxType::Utf8StringInsensitive,
|
||||
},
|
||||
);
|
||||
|
||||
// SYSINFO attrs
|
||||
// ACP attributes.
|
||||
self.attributes.insert(
|
||||
|
@ -988,6 +1143,22 @@ impl<'a> SchemaWriteTransaction<'a> {
|
|||
syntax: SyntaxType::Utf8StringInsensitive,
|
||||
},
|
||||
);
|
||||
self.attributes.insert(
|
||||
AttrString::from("scope"),
|
||||
SchemaAttribute {
|
||||
name: AttrString::from("scope"),
|
||||
uuid: UUID_SCHEMA_ATTR_SCOPE,
|
||||
description: String::from(
|
||||
"The string identifier of a permission scope in a session",
|
||||
),
|
||||
multivalue: true,
|
||||
unique: false,
|
||||
phantom: true,
|
||||
index: vec![],
|
||||
syntax: SyntaxType::Utf8StringInsensitive,
|
||||
},
|
||||
);
|
||||
|
||||
self.attributes.insert(
|
||||
AttrString::from("password_import"),
|
||||
SchemaAttribute {
|
||||
|
@ -1142,7 +1313,6 @@ impl<'a> SchemaWriteTransaction<'a> {
|
|||
uuid: UUID_SCHEMA_CLASS_ATTRIBUTETYPE,
|
||||
description: String::from("Definition of a schema attribute"),
|
||||
systemmay: vec![AttrString::from("phantom"), AttrString::from("index")],
|
||||
may: vec![],
|
||||
systemmust: vec![
|
||||
AttrString::from("class"),
|
||||
AttrString::from("attributename"),
|
||||
|
@ -1151,7 +1321,8 @@ impl<'a> SchemaWriteTransaction<'a> {
|
|||
AttrString::from("syntax"),
|
||||
AttrString::from("description"),
|
||||
],
|
||||
must: vec![],
|
||||
systemexcludes: vec![AttrString::from("classtype")],
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
self.classes.insert(
|
||||
|
@ -1165,14 +1336,18 @@ impl<'a> SchemaWriteTransaction<'a> {
|
|||
AttrString::from("may"),
|
||||
AttrString::from("systemmust"),
|
||||
AttrString::from("must"),
|
||||
AttrString::from("systemsupplements"),
|
||||
AttrString::from("supplements"),
|
||||
AttrString::from("systemexcludes"),
|
||||
AttrString::from("excludes"),
|
||||
],
|
||||
may: vec![],
|
||||
systemmust: vec![
|
||||
AttrString::from("class"),
|
||||
AttrString::from("classname"),
|
||||
AttrString::from("description"),
|
||||
],
|
||||
must: vec![],
|
||||
systemexcludes: vec![AttrString::from("attributetype")],
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
self.classes.insert(
|
||||
|
@ -1184,13 +1359,12 @@ impl<'a> SchemaWriteTransaction<'a> {
|
|||
"A system created class that all objects must contain",
|
||||
),
|
||||
systemmay: vec![AttrString::from("description")],
|
||||
may: vec![],
|
||||
systemmust: vec![
|
||||
AttrString::from("class"),
|
||||
AttrString::from("uuid"),
|
||||
AttrString::from("last_modified_cid"),
|
||||
],
|
||||
must: vec![],
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
self.classes.insert(
|
||||
|
@ -1203,9 +1377,7 @@ impl<'a> SchemaWriteTransaction<'a> {
|
|||
AttrString::from("memberof"),
|
||||
AttrString::from("directmemberof")
|
||||
],
|
||||
may: vec![],
|
||||
systemmust: vec![],
|
||||
must: vec![],
|
||||
.. Default::default()
|
||||
},
|
||||
);
|
||||
self.classes.insert(
|
||||
|
@ -1216,10 +1388,7 @@ impl<'a> SchemaWriteTransaction<'a> {
|
|||
description: String::from(
|
||||
"A class type that has green hair and turns off all rules ...",
|
||||
),
|
||||
systemmay: vec![],
|
||||
may: vec![],
|
||||
systemmust: vec![],
|
||||
must: vec![],
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
/* These two classes are core to the entry lifecycle for recycling and tombstoning */
|
||||
|
@ -1229,10 +1398,7 @@ impl<'a> SchemaWriteTransaction<'a> {
|
|||
name: AttrString::from("recycled"),
|
||||
uuid: UUID_SCHEMA_CLASS_RECYCLED,
|
||||
description: String::from("An object that has been deleted, but still recoverable via the revive operation. Recycled objects are not modifiable, only revivable."),
|
||||
systemmay: vec![],
|
||||
may: vec![],
|
||||
systemmust: vec![],
|
||||
must: vec![],
|
||||
.. Default::default()
|
||||
},
|
||||
);
|
||||
self.classes.insert(
|
||||
|
@ -1241,13 +1407,11 @@ impl<'a> SchemaWriteTransaction<'a> {
|
|||
name: AttrString::from("tombstone"),
|
||||
uuid: UUID_SCHEMA_CLASS_TOMBSTONE,
|
||||
description: String::from("An object that is purged from the recycle bin. This is a system internal state. Tombstones have no attributes beside UUID."),
|
||||
systemmay: vec![],
|
||||
may: vec![],
|
||||
systemmust: vec![
|
||||
AttrString::from("class"),
|
||||
AttrString::from("uuid"),
|
||||
],
|
||||
must: vec![],
|
||||
.. Default::default()
|
||||
},
|
||||
);
|
||||
// sysinfo
|
||||
|
@ -1257,10 +1421,8 @@ impl<'a> SchemaWriteTransaction<'a> {
|
|||
name: AttrString::from("system_info"),
|
||||
uuid: UUID_SCHEMA_CLASS_SYSTEM_INFO,
|
||||
description: String::from("System metadata object class"),
|
||||
systemmay: vec![],
|
||||
may: vec![],
|
||||
systemmust: vec![AttrString::from("version")],
|
||||
must: vec![],
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
// ACP
|
||||
|
@ -1274,13 +1436,18 @@ impl<'a> SchemaWriteTransaction<'a> {
|
|||
AttrString::from("acp_enable"),
|
||||
AttrString::from("description"),
|
||||
],
|
||||
may: vec![],
|
||||
systemmust: vec![
|
||||
AttrString::from("acp_receiver"),
|
||||
AttrString::from("acp_targetscope"),
|
||||
AttrString::from("name"),
|
||||
],
|
||||
must: vec![],
|
||||
systemsupplements: vec![
|
||||
AttrString::from("access_control_search"),
|
||||
AttrString::from("access_control_delete"),
|
||||
AttrString::from("access_control_modify"),
|
||||
AttrString::from("access_control_create"),
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
self.classes.insert(
|
||||
|
@ -1289,10 +1456,8 @@ impl<'a> SchemaWriteTransaction<'a> {
|
|||
name: AttrString::from("access_control_search"),
|
||||
uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_SEARCH,
|
||||
description: String::from("System Access Control Search Class"),
|
||||
systemmay: vec![],
|
||||
may: vec![],
|
||||
systemmust: vec![AttrString::from("acp_search_attr")],
|
||||
must: vec![],
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
self.classes.insert(
|
||||
|
@ -1301,10 +1466,7 @@ impl<'a> SchemaWriteTransaction<'a> {
|
|||
name: AttrString::from("access_control_delete"),
|
||||
uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_DELETE,
|
||||
description: String::from("System Access Control DELETE Class"),
|
||||
systemmay: vec![],
|
||||
may: vec![],
|
||||
systemmust: vec![],
|
||||
must: vec![],
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
self.classes.insert(
|
||||
|
@ -1318,9 +1480,7 @@ impl<'a> SchemaWriteTransaction<'a> {
|
|||
AttrString::from("acp_modify_presentattr"),
|
||||
AttrString::from("acp_modify_class"),
|
||||
],
|
||||
may: vec![],
|
||||
systemmust: vec![],
|
||||
must: vec![],
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
self.classes.insert(
|
||||
|
@ -1333,9 +1493,7 @@ impl<'a> SchemaWriteTransaction<'a> {
|
|||
AttrString::from("acp_create_class"),
|
||||
AttrString::from("acp_create_attr"),
|
||||
],
|
||||
may: vec![],
|
||||
systemmust: vec![],
|
||||
must: vec![],
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
self.classes.insert(
|
||||
|
@ -1344,10 +1502,7 @@ impl<'a> SchemaWriteTransaction<'a> {
|
|||
name: AttrString::from("system"),
|
||||
uuid: UUID_SCHEMA_CLASS_SYSTEM,
|
||||
description: String::from("A class denoting that a type is system generated and protected. It has special internal behaviour."),
|
||||
systemmay: vec![],
|
||||
may: vec![],
|
||||
systemmust: vec![],
|
||||
must: vec![],
|
||||
.. Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -1435,6 +1590,7 @@ impl Schema {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn write_blocking(&self) -> SchemaWriteTransaction<'_> {
|
||||
self.write()
|
||||
}
|
||||
|
@ -2183,13 +2339,146 @@ mod tests {
|
|||
uuid: Uuid::new_v4(),
|
||||
description: String::from("test object"),
|
||||
systemmay: vec![AttrString::from("claim")],
|
||||
may: vec![],
|
||||
systemmust: vec![],
|
||||
must: vec![],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert!(schema.update_classes(vec![class]).is_ok());
|
||||
|
||||
assert!(schema.validate().len() == 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_schema_class_exclusion_requires() {
|
||||
let _ = sketching::test_init();
|
||||
|
||||
let schema_outer = Schema::new().expect("failed to create schema");
|
||||
let mut schema = schema_outer.write_blocking();
|
||||
|
||||
assert!(schema.validate().len() == 0);
|
||||
|
||||
// We setup some classes that have requires and excludes and check that they
|
||||
// are enforced correctly.
|
||||
let class_account = SchemaClass {
|
||||
name: AttrString::from("account"),
|
||||
uuid: Uuid::new_v4(),
|
||||
description: String::from("account object"),
|
||||
systemmust: vec![
|
||||
AttrString::from("class"),
|
||||
AttrString::from("uuid"),
|
||||
AttrString::from("last_modified_cid"),
|
||||
],
|
||||
systemsupplements: vec![AttrString::from("service"), AttrString::from("person")],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let class_person = SchemaClass {
|
||||
name: AttrString::from("person"),
|
||||
uuid: Uuid::new_v4(),
|
||||
description: String::from("person object"),
|
||||
systemmust: vec![
|
||||
AttrString::from("class"),
|
||||
AttrString::from("uuid"),
|
||||
AttrString::from("last_modified_cid"),
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let class_service = SchemaClass {
|
||||
name: AttrString::from("service"),
|
||||
uuid: Uuid::new_v4(),
|
||||
description: String::from("service object"),
|
||||
systemmust: vec![
|
||||
AttrString::from("class"),
|
||||
AttrString::from("uuid"),
|
||||
AttrString::from("last_modified_cid"),
|
||||
],
|
||||
excludes: vec![AttrString::from("person")],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert!(schema
|
||||
.update_classes(vec![class_account, class_person, class_service])
|
||||
.is_ok());
|
||||
|
||||
// Missing person or service account.
|
||||
let e_account = unsafe {
|
||||
entry_init!(
|
||||
("class", Value::new_class("account")),
|
||||
("uuid", Value::new_uuid(Uuid::new_v4()))
|
||||
)
|
||||
.into_invalid_new()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
e_account.validate(&schema),
|
||||
Err(SchemaError::SupplementsNotSatisfied(vec![
|
||||
"service".to_string(),
|
||||
"person".to_string(),
|
||||
]))
|
||||
);
|
||||
|
||||
// Service account missing account
|
||||
/*
|
||||
let e_service = unsafe { entry_init!(
|
||||
("class", Value::new_class("service")),
|
||||
("uuid", Value::new_uuid(Uuid::new_v4()))
|
||||
).into_invalid_new() };
|
||||
|
||||
assert_eq!(
|
||||
e_service.validate(&schema),
|
||||
Err(SchemaError::RequiresNotSatisfied(vec!["account".to_string()]))
|
||||
);
|
||||
*/
|
||||
|
||||
// Service can't have person
|
||||
let e_service_person = unsafe {
|
||||
entry_init!(
|
||||
("class", Value::new_class("service")),
|
||||
("class", Value::new_class("account")),
|
||||
("class", Value::new_class("person")),
|
||||
("uuid", Value::new_uuid(Uuid::new_v4()))
|
||||
)
|
||||
.into_invalid_new()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
e_service_person.validate(&schema),
|
||||
Err(SchemaError::ExcludesNotSatisfied(
|
||||
vec!["person".to_string()]
|
||||
))
|
||||
);
|
||||
|
||||
// These are valid configurations.
|
||||
let e_service_valid = unsafe {
|
||||
entry_init!(
|
||||
("class", Value::new_class("service")),
|
||||
("class", Value::new_class("account")),
|
||||
("uuid", Value::new_uuid(Uuid::new_v4()))
|
||||
)
|
||||
.into_invalid_new()
|
||||
};
|
||||
|
||||
assert!(e_service_valid.validate(&schema).is_ok());
|
||||
|
||||
let e_person_valid = unsafe {
|
||||
entry_init!(
|
||||
("class", Value::new_class("person")),
|
||||
("class", Value::new_class("account")),
|
||||
("uuid", Value::new_uuid(Uuid::new_v4()))
|
||||
)
|
||||
.into_invalid_new()
|
||||
};
|
||||
|
||||
assert!(e_person_valid.validate(&schema).is_ok());
|
||||
|
||||
let e_person_valid = unsafe {
|
||||
entry_init!(
|
||||
("class", Value::new_class("person")),
|
||||
("uuid", Value::new_uuid(Uuid::new_v4()))
|
||||
)
|
||||
.into_invalid_new()
|
||||
};
|
||||
|
||||
assert!(e_person_valid.validate(&schema).is_ok());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,6 +50,8 @@ lazy_static! {
|
|||
static ref PVCLASS_ACC: PartialValue = PartialValue::new_class("access_control_create");
|
||||
static ref PVCLASS_ACP: PartialValue = PartialValue::new_class("access_control_profile");
|
||||
static ref PVCLASS_OAUTH2_RS: PartialValue = PartialValue::new_class("oauth2_resource_server");
|
||||
static ref PVCLASS_ACCOUNT: PartialValue = PartialValue::new_class("account");
|
||||
static ref PVCLASS_PERSON: PartialValue = PartialValue::new_class("person");
|
||||
static ref PVUUID_DOMAIN_INFO: PartialValue = PartialValue::new_uuid(*UUID_DOMAIN_INFO);
|
||||
static ref PVACP_ENABLE_FALSE: PartialValue = PartialValue::new_bool(false);
|
||||
}
|
||||
|
@ -1169,6 +1171,10 @@ impl QueryServer {
|
|||
migrate_txn.migrate_5_to_6()?;
|
||||
}
|
||||
|
||||
if system_info_version < 7 {
|
||||
migrate_txn.migrate_6_to_7()?;
|
||||
}
|
||||
|
||||
migrate_txn.commit()?;
|
||||
// Migrations complete. Init idm will now set the version as needed.
|
||||
|
||||
|
@ -2275,6 +2281,23 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Migrate 6 to 7
|
||||
///
|
||||
/// Modify accounts that are not persons, to be service accounts so that the extension
|
||||
/// rules remain valid.
|
||||
pub fn migrate_6_to_7(&self) -> Result<(), OperationError> {
|
||||
spanned!("server::migrate_5_to_6", {
|
||||
admin_warn!("starting 6 to 7 migration.");
|
||||
let filter = filter!(f_and!([
|
||||
f_eq("class", (*PVCLASS_ACCOUNT).clone()),
|
||||
f_andnot(f_eq("class", (*PVCLASS_PERSON).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
|
||||
// is the "internal" version, where we define the event as being internal
|
||||
// only, allowing certain plugin by passes etc.
|
||||
|
@ -2580,6 +2603,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
|||
JSON_SCHEMA_CLASS_ORGPERSON,
|
||||
JSON_SCHEMA_CLASS_GROUP,
|
||||
JSON_SCHEMA_CLASS_ACCOUNT,
|
||||
JSON_SCHEMA_CLASS_SERVICE_ACCOUNT,
|
||||
JSON_SCHEMA_CLASS_DOMAIN_INFO,
|
||||
JSON_SCHEMA_CLASS_POSIXACCOUNT,
|
||||
JSON_SCHEMA_CLASS_POSIXGROUP,
|
||||
|
@ -2677,6 +2701,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
|||
JSON_IDM_ACP_MANAGE_PRIV_V1,
|
||||
JSON_DOMAIN_ADMINS,
|
||||
JSON_IDM_HP_OAUTH2_MANAGE_PRIV_V1,
|
||||
JSON_IDM_HP_SERVICE_ACCOUNT_INTO_PERSON_MIGRATE_PRIV,
|
||||
// All members must exist before we write HP
|
||||
JSON_IDM_HIGH_PRIVILEGE_V1,
|
||||
// Built in access controls.
|
||||
|
@ -2718,6 +2743,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
|||
JSON_IDM_HP_ACP_OAUTH2_MANAGE_PRIV_V1,
|
||||
JSON_IDM_ACP_RADIUS_SECRET_READ_PRIV_V1,
|
||||
JSON_IDM_ACP_RADIUS_SECRET_WRITE_PRIV_V1,
|
||||
JSON_IDM_HP_ACP_SERVICE_ACCOUNT_INTO_PERSON_MIGRATE_V1,
|
||||
];
|
||||
|
||||
let res: Result<(), _> = idm_entries
|
||||
|
|
|
@ -233,11 +233,10 @@ impl ValueSetEmailAddress {
|
|||
if let Some(primary) = primary {
|
||||
Some(Box::new(ValueSetEmailAddress { primary, set }))
|
||||
} else {
|
||||
if let Some(primary) = set.iter().cloned().take(1).next() {
|
||||
Some(Box::new(ValueSetEmailAddress { primary, set }))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
set.iter()
|
||||
.next()
|
||||
.cloned()
|
||||
.map(|primary| Box::new(ValueSetEmailAddress { primary, set }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -269,7 +268,7 @@ impl ValueSetT for ValueSetEmailAddress {
|
|||
let r = self.set.remove(a);
|
||||
if &self.primary == a {
|
||||
// if we can, inject another former address into primary.
|
||||
if let Some(n) = self.set.iter().take(1).cloned().next() {
|
||||
if let Some(n) = self.set.iter().next().cloned() {
|
||||
self.primary = n
|
||||
}
|
||||
}
|
||||
|
|
|
@ -99,11 +99,11 @@ impl ValueSetT for ValueSetBool {
|
|||
}
|
||||
|
||||
fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
|
||||
Box::new(self.set.iter().copied().map(|b| PartialValue::new_bool(b)))
|
||||
Box::new(self.set.iter().copied().map(PartialValue::new_bool))
|
||||
}
|
||||
|
||||
fn to_value_iter(&self) -> Box<dyn Iterator<Item = Value> + '_> {
|
||||
Box::new(self.set.iter().copied().map(|b| Value::new_bool(b)))
|
||||
Box::new(self.set.iter().copied().map(Value::new_bool))
|
||||
}
|
||||
|
||||
fn equal(&self, other: &ValueSet) -> bool {
|
||||
|
|
|
@ -125,11 +125,11 @@ impl ValueSetT for ValueSetCid {
|
|||
}
|
||||
|
||||
fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
|
||||
Box::new(self.set.iter().cloned().map(|u| PartialValue::new_cid(u)))
|
||||
Box::new(self.set.iter().cloned().map(PartialValue::new_cid))
|
||||
}
|
||||
|
||||
fn to_value_iter(&self) -> Box<dyn Iterator<Item = Value> + '_> {
|
||||
Box::new(self.set.iter().cloned().map(|u| Value::new_cid(u)))
|
||||
Box::new(self.set.iter().cloned().map(Value::new_cid))
|
||||
}
|
||||
|
||||
fn equal(&self, other: &ValueSet) -> bool {
|
||||
|
|
|
@ -32,7 +32,7 @@ impl ValueSetOauthScope {
|
|||
Ok(Box::new(ValueSetOauthScope { set }))
|
||||
}
|
||||
|
||||
pub fn from_iter<'a, T>(iter: T) -> Option<Box<Self>>
|
||||
pub fn from_iter<T>(iter: T) -> Option<Box<Self>>
|
||||
where
|
||||
T: IntoIterator<Item = String>,
|
||||
{
|
||||
|
@ -246,8 +246,7 @@ impl ValueSetT for ValueSetOauthScopeMap {
|
|||
fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
|
||||
self.map
|
||||
.values()
|
||||
.map(|set| set.iter())
|
||||
.flatten()
|
||||
.flat_map(|set| set.iter())
|
||||
.all(|s| OAUTHSCOPE_RE.is_match(s))
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ impl ValueSetRestricted {
|
|||
Ok(Box::new(ValueSetRestricted { set }))
|
||||
}
|
||||
|
||||
pub fn from_iter<'a, T>(iter: T) -> Option<Box<Self>>
|
||||
pub fn from_iter<T>(iter: T) -> Option<Box<Self>>
|
||||
where
|
||||
T: IntoIterator<Item = String>,
|
||||
{
|
||||
|
|
|
@ -102,16 +102,11 @@ impl ValueSetT for ValueSetUint32 {
|
|||
}
|
||||
|
||||
fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
|
||||
Box::new(
|
||||
self.set
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|b| PartialValue::new_uint32(b)),
|
||||
)
|
||||
Box::new(self.set.iter().copied().map(PartialValue::new_uint32))
|
||||
}
|
||||
|
||||
fn to_value_iter(&self) -> Box<dyn Iterator<Item = Value> + '_> {
|
||||
Box::new(self.set.iter().copied().map(|b| Value::new_uint32(b)))
|
||||
Box::new(self.set.iter().copied().map(Value::new_uint32))
|
||||
}
|
||||
|
||||
fn equal(&self, other: &ValueSet) -> bool {
|
||||
|
|
|
@ -105,11 +105,11 @@ impl ValueSetT for ValueSetUuid {
|
|||
}
|
||||
|
||||
fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
|
||||
Box::new(self.set.iter().copied().map(|u| PartialValue::new_uuid(u)))
|
||||
Box::new(self.set.iter().copied().map(PartialValue::new_uuid))
|
||||
}
|
||||
|
||||
fn to_value_iter(&self) -> Box<dyn Iterator<Item = Value> + '_> {
|
||||
Box::new(self.set.iter().copied().map(|u| Value::new_uuid(u)))
|
||||
Box::new(self.set.iter().copied().map(Value::new_uuid))
|
||||
}
|
||||
|
||||
fn equal(&self, other: &ValueSet) -> bool {
|
||||
|
@ -251,11 +251,11 @@ impl ValueSetT for ValueSetRefer {
|
|||
}
|
||||
|
||||
fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
|
||||
Box::new(self.set.iter().copied().map(|u| PartialValue::new_refer(u)))
|
||||
Box::new(self.set.iter().copied().map(PartialValue::new_refer))
|
||||
}
|
||||
|
||||
fn to_value_iter(&self) -> Box<dyn Iterator<Item = Value> + '_> {
|
||||
Box::new(self.set.iter().copied().map(|u| Value::new_refer(u)))
|
||||
Box::new(self.set.iter().copied().map(Value::new_refer))
|
||||
}
|
||||
|
||||
fn equal(&self, other: &ValueSet) -> bool {
|
||||
|
|
|
@ -440,16 +440,20 @@ pub fn create_https_server(
|
|||
let mut tserver_cacheable = tserver.at("");
|
||||
tserver_cacheable.with(CacheableMiddleware::default());
|
||||
|
||||
// We allow clients to cache the unix token for accounts and groups.
|
||||
let mut account_route_cacheable = tserver_cacheable.at("/v1/account");
|
||||
|
||||
account_route_cacheable
|
||||
.at("/:id/_unix/_token")
|
||||
.mapped_get(&mut routemap, account_get_id_unix_token);
|
||||
// We allow caching of the radius token.
|
||||
account_route_cacheable
|
||||
.at("/:id/_radius/_token")
|
||||
.mapped_get(&mut routemap, account_get_id_radius_token);
|
||||
// We allow clients to cache the unix token.
|
||||
account_route_cacheable
|
||||
|
||||
let mut group_route_cacheable = tserver_cacheable.at("/v1/group");
|
||||
group_route_cacheable
|
||||
.at("/:id/_unix/_token")
|
||||
.mapped_get(&mut routemap, account_get_id_unix_token);
|
||||
.mapped_get(&mut routemap, group_get_id_unix_token);
|
||||
|
||||
// ==== These routes can not be cached
|
||||
let mut appserver = tserver.at("");
|
||||
|
@ -573,9 +577,6 @@ pub fn create_https_server(
|
|||
.at("/_credential")
|
||||
.mapped_get(&mut routemap, do_nothing);
|
||||
|
||||
self_route
|
||||
.at("/_credential/primary/set_password")
|
||||
.mapped_post(&mut routemap, idm_account_set_password);
|
||||
self_route
|
||||
.at("/_credential/:cid/_lock")
|
||||
.mapped_get(&mut routemap, do_nothing);
|
||||
|
@ -603,87 +604,130 @@ pub fn create_https_server(
|
|||
.mapped_post(&mut routemap, person_post);
|
||||
person_route
|
||||
.at("/:id")
|
||||
.mapped_get(&mut routemap, person_id_get);
|
||||
|
||||
let mut account_route = appserver.at("/v1/account");
|
||||
account_route
|
||||
.at("/")
|
||||
.mapped_get(&mut routemap, account_get)
|
||||
.mapped_post(&mut routemap, account_post);
|
||||
account_route
|
||||
.at("/:id")
|
||||
.mapped_get(&mut routemap, account_id_get)
|
||||
.mapped_delete(&mut routemap, account_id_delete);
|
||||
account_route
|
||||
.mapped_get(&mut routemap, person_id_get)
|
||||
.mapped_patch(&mut routemap, account_id_patch)
|
||||
.mapped_delete(&mut routemap, person_account_id_delete);
|
||||
person_route
|
||||
.at("/:id/_attr/:attr")
|
||||
.mapped_get(&mut routemap, account_id_get_attr)
|
||||
.mapped_put(&mut routemap, account_id_put_attr)
|
||||
.mapped_post(&mut routemap, account_id_post_attr)
|
||||
.mapped_delete(&mut routemap, account_id_delete_attr);
|
||||
account_route
|
||||
.at("/:id/_person/_extend")
|
||||
.mapped_post(&mut routemap, account_post_id_person_extend);
|
||||
account_route
|
||||
.at("/:id/_person/_set")
|
||||
.mapped_post(&mut routemap, account_post_id_person_set);
|
||||
account_route
|
||||
|
||||
person_route
|
||||
.at("/:id/_lock")
|
||||
.mapped_get(&mut routemap, do_nothing);
|
||||
|
||||
account_route
|
||||
person_route
|
||||
.at("/:id/_credential")
|
||||
.mapped_get(&mut routemap, do_nothing);
|
||||
account_route
|
||||
person_route
|
||||
.at("/:id/_credential/_status")
|
||||
.mapped_get(&mut routemap, account_get_id_credential_status);
|
||||
account_route
|
||||
.at("/:id/_credential/primary")
|
||||
.mapped_put(&mut routemap, account_put_id_credential_primary);
|
||||
account_route
|
||||
person_route
|
||||
.at("/:id/_credential/:cid/_lock")
|
||||
.mapped_get(&mut routemap, do_nothing);
|
||||
account_route
|
||||
.at("/:id/_credential/:cid/backup_code")
|
||||
.mapped_get(&mut routemap, account_get_backup_code);
|
||||
// .mapped_post(&mut routemap, account_post_backup_code_regenerate) // use "/:id/_credential/primary" instead
|
||||
// .mapped_delete(&mut routemap, account_delete_backup_code); // same as above
|
||||
account_route
|
||||
person_route
|
||||
.at("/:id/_credential/_update")
|
||||
.mapped_get(&mut routemap, account_get_id_credential_update);
|
||||
account_route
|
||||
person_route
|
||||
.at("/:id/_credential/_update_intent")
|
||||
.mapped_get(&mut routemap, account_get_id_credential_update_intent);
|
||||
account_route
|
||||
person_route
|
||||
.at("/:id/_credential/_update_intent/:ttl")
|
||||
.mapped_get(&mut routemap, account_get_id_credential_update_intent);
|
||||
|
||||
account_route
|
||||
person_route
|
||||
.at("/:id/_ssh_pubkeys")
|
||||
.mapped_get(&mut routemap, account_get_id_ssh_pubkeys)
|
||||
.mapped_post(&mut routemap, account_post_id_ssh_pubkey);
|
||||
|
||||
account_route
|
||||
person_route
|
||||
.at("/:id/_ssh_pubkeys/:tag")
|
||||
.mapped_get(&mut routemap, account_get_id_ssh_pubkey_tag)
|
||||
.mapped_delete(&mut routemap, account_delete_id_ssh_pubkey_tag);
|
||||
|
||||
account_route
|
||||
person_route
|
||||
.at("/:id/_radius")
|
||||
.mapped_get(&mut routemap, account_get_id_radius)
|
||||
.mapped_post(&mut routemap, account_post_id_radius_regenerate)
|
||||
.mapped_delete(&mut routemap, account_delete_id_radius);
|
||||
|
||||
account_route
|
||||
person_route
|
||||
.at("/:id/_unix")
|
||||
.mapped_post(&mut routemap, account_post_id_unix);
|
||||
account_route
|
||||
.at("/:id/_unix/_auth")
|
||||
.mapped_post(&mut routemap, account_post_id_unix_auth);
|
||||
account_route
|
||||
person_route
|
||||
.at("/:id/_unix/_credential")
|
||||
.mapped_put(&mut routemap, account_put_id_unix_credential)
|
||||
.mapped_delete(&mut routemap, account_delete_id_unix_credential);
|
||||
|
||||
// Service accounts
|
||||
|
||||
let mut service_account_route = appserver.at("/v1/service_account");
|
||||
service_account_route
|
||||
.at("/")
|
||||
.mapped_get(&mut routemap, service_account_get)
|
||||
.mapped_post(&mut routemap, service_account_post);
|
||||
service_account_route
|
||||
.at("/:id")
|
||||
.mapped_get(&mut routemap, service_account_id_get)
|
||||
.mapped_patch(&mut routemap, account_id_patch)
|
||||
.mapped_delete(&mut routemap, service_account_id_delete);
|
||||
service_account_route
|
||||
.at("/:id/_attr/:attr")
|
||||
.mapped_get(&mut routemap, account_id_get_attr)
|
||||
.mapped_put(&mut routemap, account_id_put_attr)
|
||||
.mapped_post(&mut routemap, account_id_post_attr)
|
||||
.mapped_delete(&mut routemap, account_id_delete_attr);
|
||||
|
||||
service_account_route
|
||||
.at("/:id/_lock")
|
||||
.mapped_get(&mut routemap, do_nothing);
|
||||
|
||||
service_account_route
|
||||
.at("/:id/_into_person")
|
||||
.mapped_post(&mut routemap, service_account_into_person);
|
||||
|
||||
service_account_route
|
||||
.at("/:id/_credential")
|
||||
.mapped_get(&mut routemap, do_nothing);
|
||||
service_account_route
|
||||
.at("/:id/_credential/_generate")
|
||||
.mapped_get(&mut routemap, service_account_credential_generate);
|
||||
service_account_route
|
||||
.at("/:id/_credential/_status")
|
||||
.mapped_get(&mut routemap, account_get_id_credential_status);
|
||||
service_account_route
|
||||
.at("/:id/_credential/:cid/_lock")
|
||||
.mapped_get(&mut routemap, do_nothing);
|
||||
|
||||
service_account_route
|
||||
.at("/:id/_ssh_pubkeys")
|
||||
.mapped_get(&mut routemap, account_get_id_ssh_pubkeys)
|
||||
.mapped_post(&mut routemap, account_post_id_ssh_pubkey);
|
||||
service_account_route
|
||||
.at("/:id/_ssh_pubkeys/:tag")
|
||||
.mapped_get(&mut routemap, account_get_id_ssh_pubkey_tag)
|
||||
.mapped_delete(&mut routemap, account_delete_id_ssh_pubkey_tag);
|
||||
|
||||
service_account_route
|
||||
.at("/:id/_unix")
|
||||
.mapped_post(&mut routemap, account_post_id_unix);
|
||||
|
||||
// TODO: Apis for token management
|
||||
|
||||
// Shared account features only - mainly this is for unix-like
|
||||
// features.
|
||||
let mut account_route = appserver.at("/v1/account");
|
||||
account_route
|
||||
.at("/:id/_unix/_auth")
|
||||
.mapped_post(&mut routemap, account_post_id_unix_auth);
|
||||
account_route
|
||||
.at("/:id/_ssh_pubkeys")
|
||||
.mapped_get(&mut routemap, account_get_id_ssh_pubkeys);
|
||||
account_route
|
||||
.at("/:id/_ssh_pubkeys/:tag")
|
||||
.mapped_get(&mut routemap, account_get_id_ssh_pubkey_tag);
|
||||
|
||||
// Credential updates, don't require the account id.
|
||||
let mut cred_route = appserver.at("/v1/credential");
|
||||
cred_route
|
||||
.at("/_exchange_intent")
|
||||
|
@ -723,9 +767,6 @@ pub fn create_https_server(
|
|||
group_route
|
||||
.at("/:id/_unix")
|
||||
.mapped_post(&mut routemap, group_post_id_unix);
|
||||
group_route
|
||||
.at("/:id/_unix/_token")
|
||||
.mapped_get(&mut routemap, group_get_id_unix_token);
|
||||
|
||||
let mut domain_route = appserver.at("/v1/domain");
|
||||
domain_route.at("/").mapped_get(&mut routemap, domain_get);
|
||||
|
|
|
@ -6,9 +6,9 @@ use kanidm::status::StatusRequestEvent;
|
|||
|
||||
use kanidm_proto::v1::Entry as ProtoEntry;
|
||||
use kanidm_proto::v1::{
|
||||
AccountPersonSet, AccountUnixExtend, AuthRequest, AuthResponse, AuthState as ProtoAuthState,
|
||||
CUIntentToken, CURequest, CUSessionToken, CreateRequest, DeleteRequest, GroupUnixExtend,
|
||||
ModifyRequest, OperationError, SearchRequest, SetCredentialRequest, SingleStringRequest,
|
||||
AccountUnixExtend, AuthRequest, AuthResponse, AuthState as ProtoAuthState, CUIntentToken,
|
||||
CURequest, CUSessionToken, CreateRequest, DeleteRequest, GroupUnixExtend, ModifyRequest,
|
||||
OperationError, SearchRequest, SingleStringRequest,
|
||||
};
|
||||
|
||||
use super::{to_tide_response, AppState, RequestExtensions, RouteMap};
|
||||
|
@ -260,20 +260,6 @@ pub async fn json_rest_event_delete_attr(
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn json_rest_event_credential_put(mut req: tide::Request<AppState>) -> tide::Result {
|
||||
let uat = req.get_current_uat();
|
||||
let uuid_or_name = req.get_url_param("id")?;
|
||||
let sac: SetCredentialRequest = req.body_json().await?;
|
||||
|
||||
let (eventid, hvalue) = req.new_eventid();
|
||||
let res = req
|
||||
.state()
|
||||
.qe_w_ref
|
||||
.handle_credentialset(uat, uuid_or_name, sac, eventid)
|
||||
.await;
|
||||
to_tide_response(res, hvalue)
|
||||
}
|
||||
|
||||
// Okay, so a put normally needs
|
||||
// * filter of what we are working on (id + class)
|
||||
// * a Map<String, Vec<String>> that we turn into a modlist.
|
||||
|
@ -357,7 +343,11 @@ pub async fn person_get(req: tide::Request<AppState>) -> tide::Result {
|
|||
}
|
||||
|
||||
pub async fn person_post(req: tide::Request<AppState>) -> tide::Result {
|
||||
let classes = vec!["person".to_string(), "object".to_string()];
|
||||
let classes = vec![
|
||||
"person".to_string(),
|
||||
"account".to_string(),
|
||||
"object".to_string(),
|
||||
];
|
||||
json_rest_event_post(req, classes).await
|
||||
}
|
||||
|
||||
|
@ -366,23 +356,70 @@ pub async fn person_id_get(req: tide::Request<AppState>) -> tide::Result {
|
|||
json_rest_event_get_id(req, filter, None).await
|
||||
}
|
||||
|
||||
pub async fn person_account_id_delete(req: tide::Request<AppState>) -> tide::Result {
|
||||
let filter = filter_all!(f_eq("class", PartialValue::new_class("person")));
|
||||
json_rest_event_delete_id(req, filter).await
|
||||
}
|
||||
|
||||
// == account ==
|
||||
|
||||
pub async fn account_get(req: tide::Request<AppState>) -> tide::Result {
|
||||
let filter = filter_all!(f_eq("class", PartialValue::new_class("account")));
|
||||
pub async fn service_account_get(req: tide::Request<AppState>) -> tide::Result {
|
||||
let filter = filter_all!(f_eq("class", PartialValue::new_class("service_account")));
|
||||
json_rest_event_get(req, filter, None).await
|
||||
}
|
||||
|
||||
pub async fn account_post(req: tide::Request<AppState>) -> tide::Result {
|
||||
let classes = vec!["account".to_string(), "object".to_string()];
|
||||
pub async fn service_account_post(req: tide::Request<AppState>) -> tide::Result {
|
||||
let classes = vec![
|
||||
"service_account".to_string(),
|
||||
"account".to_string(),
|
||||
"object".to_string(),
|
||||
];
|
||||
json_rest_event_post(req, classes).await
|
||||
}
|
||||
|
||||
pub async fn account_id_get(req: tide::Request<AppState>) -> tide::Result {
|
||||
let filter = filter_all!(f_eq("class", PartialValue::new_class("account")));
|
||||
pub async fn service_account_id_get(req: tide::Request<AppState>) -> tide::Result {
|
||||
let filter = filter_all!(f_eq("class", PartialValue::new_class("service_account")));
|
||||
json_rest_event_get_id(req, filter, None).await
|
||||
}
|
||||
|
||||
pub async fn service_account_id_delete(req: tide::Request<AppState>) -> tide::Result {
|
||||
let filter = filter_all!(f_eq("class", PartialValue::new_class("service_account")));
|
||||
json_rest_event_delete_id(req, filter).await
|
||||
}
|
||||
|
||||
pub async fn service_account_credential_generate(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_w_ref
|
||||
.handle_service_account_credential_generate(uat, uuid_or_name, eventid)
|
||||
.await;
|
||||
to_tide_response(res, hvalue)
|
||||
}
|
||||
|
||||
// Due to how the migrations work in 6 -> 7, we can accidentally
|
||||
// mark "accounts" as service accounts when they are persons. This
|
||||
// allows migrating them to the person type due to it's similarities.
|
||||
//
|
||||
// In the future this will be REMOVED!
|
||||
pub async fn service_account_into_person(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_w_ref
|
||||
.handle_service_account_into_person(uat, uuid_or_name, eventid)
|
||||
.await;
|
||||
to_tide_response(res, hvalue)
|
||||
}
|
||||
|
||||
pub async fn account_id_get_attr(req: tide::Request<AppState>) -> tide::Result {
|
||||
let filter = filter_all!(f_eq("class", PartialValue::new_class("account")));
|
||||
json_rest_event_get_id_attr(req, filter).await
|
||||
|
@ -404,13 +441,24 @@ pub async fn account_id_put_attr(req: tide::Request<AppState>) -> tide::Result {
|
|||
json_rest_event_put_id_attr(req, filter).await
|
||||
}
|
||||
|
||||
pub async fn account_id_delete(req: tide::Request<AppState>) -> tide::Result {
|
||||
let filter = filter_all!(f_eq("class", PartialValue::new_class("account")));
|
||||
json_rest_event_delete_id(req, filter).await
|
||||
}
|
||||
pub async fn account_id_patch(mut req: tide::Request<AppState>) -> tide::Result {
|
||||
// Update a value / attrs
|
||||
let uat = req.get_current_uat();
|
||||
let id = req.get_url_param("id")?;
|
||||
|
||||
pub async fn account_put_id_credential_primary(req: tide::Request<AppState>) -> tide::Result {
|
||||
json_rest_event_credential_put(req).await
|
||||
let obj: ProtoEntry = req.body_json().await?;
|
||||
|
||||
let filter = filter_all!(f_eq("class", PartialValue::new_class("account")));
|
||||
let filter = Filter::join_parts_and(filter, filter_all!(f_id(id.as_str())));
|
||||
|
||||
let (eventid, hvalue) = req.new_eventid();
|
||||
|
||||
let res = req
|
||||
.state()
|
||||
.qe_w_ref
|
||||
.handle_internalpatch(uat, filter, obj, eventid)
|
||||
.await;
|
||||
to_tide_response(res, hvalue)
|
||||
}
|
||||
|
||||
pub async fn account_get_id_credential_update(req: tide::Request<AppState>) -> tide::Result {
|
||||
|
@ -526,20 +574,6 @@ pub async fn account_get_id_credential_status(req: tide::Request<AppState>) -> t
|
|||
to_tide_response(res, hvalue)
|
||||
}
|
||||
|
||||
pub async fn account_get_backup_code(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_idmbackupcodeview(uat, uuid_or_name, eventid)
|
||||
.await;
|
||||
to_tide_response(res, hvalue)
|
||||
}
|
||||
|
||||
// Return a vec of str
|
||||
pub async fn account_get_id_ssh_pubkeys(req: tide::Request<AppState>) -> tide::Result {
|
||||
let uat = req.get_current_uat();
|
||||
|
@ -654,32 +688,6 @@ pub async fn account_get_id_radius_token(req: tide::Request<AppState>) -> tide::
|
|||
to_tide_response(res, hvalue)
|
||||
}
|
||||
|
||||
pub async fn account_post_id_person_extend(mut req: tide::Request<AppState>) -> tide::Result {
|
||||
let uat = req.get_current_uat();
|
||||
let uuid_or_name = req.get_url_param("id")?;
|
||||
let obj: AccountPersonSet = req.body_json().await?;
|
||||
let (eventid, hvalue) = req.new_eventid();
|
||||
let res = req
|
||||
.state()
|
||||
.qe_w_ref
|
||||
.handle_idmaccountpersonextend(uat, uuid_or_name, obj, eventid)
|
||||
.await;
|
||||
to_tide_response(res, hvalue)
|
||||
}
|
||||
|
||||
pub async fn account_post_id_person_set(mut req: tide::Request<AppState>) -> tide::Result {
|
||||
let uat = req.get_current_uat();
|
||||
let uuid_or_name = req.get_url_param("id")?;
|
||||
let obj: AccountPersonSet = req.body_json().await?;
|
||||
let (eventid, hvalue) = req.new_eventid();
|
||||
let res = req
|
||||
.state()
|
||||
.qe_w_ref
|
||||
.handle_idmaccountpersonset(uat, uuid_or_name, obj, eventid)
|
||||
.await;
|
||||
to_tide_response(res, hvalue)
|
||||
}
|
||||
|
||||
pub async fn account_post_id_unix(mut req: tide::Request<AppState>) -> tide::Result {
|
||||
let uat = req.get_current_uat();
|
||||
let uuid_or_name = req.get_url_param("id")?;
|
||||
|
@ -1037,19 +1045,6 @@ pub async fn auth_valid(req: tide::Request<AppState>) -> tide::Result {
|
|||
to_tide_response(res, hvalue)
|
||||
}
|
||||
|
||||
pub async fn idm_account_set_password(mut req: tide::Request<AppState>) -> tide::Result {
|
||||
let uat = req.get_current_uat();
|
||||
let obj: SingleStringRequest = req.body_json().await?;
|
||||
let cleartext = obj.value;
|
||||
let (eventid, hvalue) = req.new_eventid();
|
||||
let res = req
|
||||
.state()
|
||||
.qe_w_ref
|
||||
.handle_idmaccountsetpassword(uat, cleartext, eventid)
|
||||
.await;
|
||||
to_tide_response(res, hvalue)
|
||||
}
|
||||
|
||||
// == Status
|
||||
|
||||
pub async fn status(req: tide::Request<AppState>) -> tide::Result {
|
||||
|
|
|
@ -57,7 +57,7 @@ static DEFAULT_NOT_HP_GROUP_NAMES: [&str; 2] =
|
|||
["idm_account_unix_extend_priv", "idm_group_unix_extend_priv"];
|
||||
|
||||
async fn create_user(rsclient: &KanidmClient, id: &str, group_name: &str) -> () {
|
||||
rsclient.idm_account_create(id, id).await.unwrap();
|
||||
rsclient.idm_person_account_create(id, id).await.unwrap();
|
||||
|
||||
// Create group and add to user to test read attr: member_of
|
||||
if rsclient.idm_group_get(&group_name).await.unwrap().is_none() {
|
||||
|
@ -81,13 +81,13 @@ async fn is_attr_writable(rsclient: &KanidmClient, id: &str, attr: &str) -> Opti
|
|||
),
|
||||
"primary_credential" => Some(
|
||||
rsclient
|
||||
.idm_account_primary_credential_set_password(id, "dsadjasiodqwjk12asdl")
|
||||
.idm_person_account_primary_credential_set_password(id, "dsadjasiodqwjk12asdl")
|
||||
.await
|
||||
.is_ok(),
|
||||
),
|
||||
"ssh_publickey" => Some(
|
||||
rsclient
|
||||
.idm_account_post_ssh_pubkey(
|
||||
.idm_person_account_post_ssh_pubkey(
|
||||
id,
|
||||
"k1",
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAeGW1P6Pc2rPq0XqbRaDKBcXZUPRklo0\
|
||||
|
@ -98,19 +98,19 @@ async fn is_attr_writable(rsclient: &KanidmClient, id: &str, attr: &str) -> Opti
|
|||
),
|
||||
"unix_password" => Some(
|
||||
rsclient
|
||||
.idm_account_unix_cred_put(id, "dsadjasiodqwjk12asdl")
|
||||
.idm_person_account_unix_cred_put(id, "dsadjasiodqwjk12asdl")
|
||||
.await
|
||||
.is_ok(),
|
||||
),
|
||||
"legalname" => Some(
|
||||
rsclient
|
||||
.idm_account_person_set(id, None, Some("test legal name".into()))
|
||||
.idm_person_account_set_attr(id, "legalname", &["test legal name"])
|
||||
.await
|
||||
.is_ok(),
|
||||
),
|
||||
"mail" => Some(
|
||||
rsclient
|
||||
.idm_account_person_set(id, Some(&[format!("{}@example.com", id)]), None)
|
||||
.idm_person_account_set_attr(id, "mail", &[&format!("{}@example.com", id)])
|
||||
.await
|
||||
.is_ok(),
|
||||
),
|
||||
|
@ -130,14 +130,15 @@ async fn is_attr_writable(rsclient: &KanidmClient, id: &str, attr: &str) -> Opti
|
|||
}
|
||||
}
|
||||
|
||||
async fn add_all_attrs(rsclient: &KanidmClient, id: &str, group_name: &str) {
|
||||
async fn add_all_attrs(
|
||||
rsclient: &KanidmClient,
|
||||
id: &str,
|
||||
group_name: &str,
|
||||
legalname: Option<&str>,
|
||||
) {
|
||||
// Extend with posix attrs to test read attr: gidnumber and loginshell
|
||||
rsclient
|
||||
.idm_group_add_members("idm_admins", &[ADMIN_TEST_USER])
|
||||
.await
|
||||
.unwrap();
|
||||
rsclient
|
||||
.idm_account_unix_extend(id, None, Some(&"/bin/sh"))
|
||||
.idm_person_account_unix_extend(id, None, Some(&"/bin/sh"))
|
||||
.await
|
||||
.unwrap();
|
||||
rsclient
|
||||
|
@ -145,16 +146,16 @@ async fn add_all_attrs(rsclient: &KanidmClient, id: &str, group_name: &str) {
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
// Extend with person to allow legalname
|
||||
rsclient
|
||||
.idm_account_person_extend(id, None, None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
for attr in ["ssh_publickey", "legalname", "mail"].iter() {
|
||||
for attr in ["ssh_publickey", "mail"].iter() {
|
||||
assert!(is_attr_writable(&rsclient, id, attr).await.unwrap());
|
||||
}
|
||||
|
||||
if let Some(legalname) = legalname {
|
||||
assert!(is_attr_writable(&rsclient, legalname, "legalname")
|
||||
.await
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
// Write radius credentials
|
||||
if id != "anonymous" {
|
||||
login_account(&rsclient, id).await;
|
||||
|
@ -178,7 +179,7 @@ async fn create_user_with_all_attrs(
|
|||
let group_name = optional_group.unwrap_or(&group_format);
|
||||
|
||||
create_user(&rsclient, id, group_name).await;
|
||||
add_all_attrs(&rsclient, id, group_name).await;
|
||||
add_all_attrs(&rsclient, id, group_name, Some(id)).await;
|
||||
}
|
||||
|
||||
async fn login_account(rsclient: &KanidmClient, id: &str) -> () {
|
||||
|
@ -195,7 +196,7 @@ async fn login_account(rsclient: &KanidmClient, id: &str) -> () {
|
|||
.unwrap();
|
||||
|
||||
rsclient
|
||||
.idm_account_primary_credential_set_password(id, "eicieY7ahchaoCh0eeTa")
|
||||
.idm_person_account_primary_credential_set_password(id, "eicieY7ahchaoCh0eeTa")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -292,6 +293,10 @@ async fn test_default_entries_rbac_users() {
|
|||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||
.await
|
||||
.unwrap();
|
||||
rsclient
|
||||
.idm_group_add_members("idm_admins", &[ADMIN_TEST_USER])
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
create_user_with_all_attrs(&rsclient, "self_account", Some("self_group")).await;
|
||||
create_user_with_all_attrs(&rsclient, "other_account", Some("other_group")).await;
|
||||
|
@ -329,6 +334,10 @@ async fn test_default_entries_rbac_account_managers() {
|
|||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||
.await
|
||||
.unwrap();
|
||||
rsclient
|
||||
.idm_group_add_members("idm_admins", &[ADMIN_TEST_USER])
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
create_user(&rsclient, "account_manager", "idm_account_manage_priv").await;
|
||||
create_user_with_all_attrs(&rsclient, "test", Some("test_group")).await;
|
||||
|
@ -361,6 +370,10 @@ async fn test_default_entries_rbac_group_managers() {
|
|||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||
.await
|
||||
.unwrap();
|
||||
rsclient
|
||||
.idm_group_add_members("idm_admins", &[ADMIN_TEST_USER])
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
create_user(&rsclient, "group_manager", "idm_group_manage_priv").await;
|
||||
// create test user without creating new groups
|
||||
|
@ -405,6 +418,11 @@ async fn test_default_entries_rbac_admins_access_control_entries() {
|
|||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||
.await
|
||||
.unwrap();
|
||||
rsclient
|
||||
.idm_group_add_members("idm_admins", &[ADMIN_TEST_USER])
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
static ACP_COMMON_ATTRS: [&str; 4] = ["name", "description", "acp_receiver", "acp_targetscope"];
|
||||
static ACP_ENTRIES: [&str; 28] = [
|
||||
"idm_admins_acp_recycle_search",
|
||||
|
@ -452,6 +470,11 @@ async fn test_default_entries_rbac_admins_schema_entries() {
|
|||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||
.await
|
||||
.unwrap();
|
||||
rsclient
|
||||
.idm_group_add_members("idm_admins", &[ADMIN_TEST_USER])
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let default_classnames: HashSet<String> = [
|
||||
"access_control_create",
|
||||
"access_control_delete",
|
||||
|
@ -563,6 +586,11 @@ async fn test_default_entries_rbac_admins_group_entries() {
|
|||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||
.await
|
||||
.unwrap();
|
||||
rsclient
|
||||
.idm_group_add_members("idm_admins", &[ADMIN_TEST_USER])
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
create_user(&rsclient, "test", "test_group").await;
|
||||
|
||||
let default_group_names =
|
||||
|
@ -579,6 +607,10 @@ async fn test_default_entries_rbac_admins_ha_accounts() {
|
|||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||
.await
|
||||
.unwrap();
|
||||
rsclient
|
||||
.idm_group_add_members("idm_admins", &[ADMIN_TEST_USER])
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
static MAIN_ATTRS: [&str; 3] = ["name", "displayname", "primary_credential"];
|
||||
test_write_attrs(&rsclient, "idm_admin", &MAIN_ATTRS, true).await;
|
||||
|
@ -592,12 +624,17 @@ async fn test_default_entries_rbac_admins_recycle_accounts() {
|
|||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||
.await
|
||||
.unwrap();
|
||||
rsclient
|
||||
.idm_group_add_members("idm_admins", &[ADMIN_TEST_USER])
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
create_user(&rsclient, "test", "test_group").await;
|
||||
|
||||
rsclient.idm_account_delete("test").await.unwrap();
|
||||
rsclient.idm_person_account_delete("test").await.unwrap();
|
||||
rsclient.recycle_bin_revive("test").await.unwrap();
|
||||
|
||||
let acc = rsclient.idm_account_get("test").await.unwrap();
|
||||
let acc = rsclient.idm_person_account_get("test").await.unwrap();
|
||||
assert!(acc.is_some());
|
||||
}
|
||||
|
||||
|
@ -611,6 +648,10 @@ async fn test_default_entries_rbac_people_managers() {
|
|||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||
.await
|
||||
.unwrap();
|
||||
rsclient
|
||||
.idm_group_add_members("idm_admins", &[ADMIN_TEST_USER])
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
create_user(&rsclient, "read_people_manager", "idm_people_read_priv").await;
|
||||
create_user_with_all_attrs(&rsclient, "test", Some("test_group")).await;
|
||||
|
@ -650,12 +691,17 @@ async fn test_default_entries_rbac_anonymous_entry() {
|
|||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||
.await
|
||||
.unwrap();
|
||||
rsclient
|
||||
.idm_group_add_members("idm_admins", &[ADMIN_TEST_USER])
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
create_user_with_all_attrs(&rsclient, "test", Some("test_group")).await;
|
||||
rsclient
|
||||
.idm_group_add_members("test_group", &["anonymous"])
|
||||
.await
|
||||
.unwrap();
|
||||
add_all_attrs(&rsclient, "anonymous", "test_group").await;
|
||||
add_all_attrs(&rsclient, "anonymous", "test_group", None).await;
|
||||
|
||||
let _ = rsclient.logout();
|
||||
rsclient.auth_anonymous().await.unwrap();
|
||||
|
@ -676,6 +722,11 @@ async fn test_default_entries_rbac_radius_servers() {
|
|||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||
.await
|
||||
.unwrap();
|
||||
rsclient
|
||||
.idm_group_add_members("idm_admins", &[ADMIN_TEST_USER])
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
create_user(&rsclient, "radius_server", "idm_radius_servers").await;
|
||||
create_user_with_all_attrs(&rsclient, "test", Some("test_group")).await;
|
||||
|
||||
|
@ -694,6 +745,10 @@ async fn test_self_write_mail_priv_people() {
|
|||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||
.await
|
||||
.unwrap();
|
||||
rsclient
|
||||
.idm_group_add_members("idm_admins", &[ADMIN_TEST_USER])
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// test and other, each can write to themselves, but not each other
|
||||
create_user_with_all_attrs(&rsclient, "test", None).await;
|
||||
|
@ -703,7 +758,7 @@ async fn test_self_write_mail_priv_people() {
|
|||
.await
|
||||
.unwrap();
|
||||
// a non-person, they can't write to themselves even with the priv
|
||||
create_user(&rsclient, "nonperson", "idm_people_self_write_mail_priv").await;
|
||||
create_user(&rsclient, "nonperson", "nonperson_group").await;
|
||||
|
||||
login_account(&rsclient, "test").await;
|
||||
// can write to own mail
|
||||
|
|
|
@ -57,18 +57,24 @@ async fn test_oauth2_openid_basic_flow() {
|
|||
|
||||
// Extend the admin account with extended details for openid claims.
|
||||
rsclient
|
||||
.idm_group_add_members("idm_hp_people_extend_priv", &["admin"])
|
||||
.idm_group_add_members("idm_admins", &["admin"])
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
rsclient
|
||||
.idm_account_person_extend(
|
||||
"admin",
|
||||
Some(&["admin@localhost".to_string()]),
|
||||
Some("Admin Istrator"),
|
||||
)
|
||||
.idm_person_account_create("oauth_test", "oauth_test")
|
||||
.await
|
||||
.expect("Failed to extend account details");
|
||||
.expect("Failed to create account details");
|
||||
|
||||
rsclient
|
||||
.idm_person_account_set_attr("oauth_test", "mail", &["oauth_test@localhost"])
|
||||
.await
|
||||
.expect("Failed to create account mail");
|
||||
|
||||
rsclient
|
||||
.idm_person_account_primary_credential_set_password("oauth_test", ADMIN_TEST_PASSWORD)
|
||||
.await
|
||||
.expect("Failed to configure account password");
|
||||
|
||||
rsclient
|
||||
.idm_oauth2_rs_update(
|
||||
|
@ -100,10 +106,10 @@ async fn test_oauth2_openid_basic_flow() {
|
|||
// Get our admin's auth token for our new client.
|
||||
// We have to re-auth to update the mail field.
|
||||
let res = rsclient
|
||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||
.auth_simple_password("oauth_test", ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
assert!(res.is_ok());
|
||||
let admin_uat = rsclient
|
||||
let oauth_test_uat = rsclient
|
||||
.get_token()
|
||||
.await
|
||||
.expect("No user auth token found");
|
||||
|
@ -198,7 +204,7 @@ async fn test_oauth2_openid_basic_flow() {
|
|||
|
||||
let response = client
|
||||
.get(format!("{}/oauth2/authorise", url))
|
||||
.bearer_auth(admin_uat.clone())
|
||||
.bearer_auth(oauth_test_uat.clone())
|
||||
.query(&[
|
||||
("response_type", "code"),
|
||||
("client_id", "test_integration"),
|
||||
|
@ -232,7 +238,7 @@ async fn test_oauth2_openid_basic_flow() {
|
|||
|
||||
let response = client
|
||||
.get(format!("{}/oauth2/authorise/permit", url))
|
||||
.bearer_auth(admin_uat)
|
||||
.bearer_auth(oauth_test_uat)
|
||||
.query(&[("token", consent_token.as_str())])
|
||||
.send()
|
||||
.await
|
||||
|
@ -317,7 +323,7 @@ async fn test_oauth2_openid_basic_flow() {
|
|||
assert!(tir.active);
|
||||
assert!(tir.scope.is_some());
|
||||
assert!(tir.client_id.as_deref() == Some("test_integration"));
|
||||
assert!(tir.username.as_deref() == Some("admin@localhost"));
|
||||
assert!(tir.username.as_deref() == Some("oauth_test@localhost"));
|
||||
assert!(tir.token_type.as_deref() == Some("access_token"));
|
||||
assert!(tir.exp.is_some());
|
||||
assert!(tir.iat.is_some());
|
||||
|
@ -338,7 +344,8 @@ async fn test_oauth2_openid_basic_flow() {
|
|||
// This is mostly checked inside of idm/oauth2.rs. This is more to check the oidc
|
||||
// token and the userinfo endpoints.
|
||||
assert!(oidc.iss == Url::parse(&format!("{}/oauth2/openid/test_integration", url)).unwrap());
|
||||
assert!(oidc.s_claims.email.as_deref() == Some("admin@localhost"));
|
||||
eprintln!("{:?}", oidc.s_claims.email);
|
||||
assert!(oidc.s_claims.email.as_deref() == Some("oauth_test@localhost"));
|
||||
assert!(oidc.s_claims.email_verified == Some(true));
|
||||
|
||||
let response = client
|
||||
|
|
|
@ -11,7 +11,6 @@ use crate::common::{setup_async_test, ADMIN_TEST_PASSWORD};
|
|||
|
||||
use webauthn_authenticator_rs::{softpasskey::SoftPasskey, WebauthnAuthenticator};
|
||||
|
||||
const ADMIN_TEST_PASSWORD_CHANGE: &str = "integration test admin new🎉";
|
||||
const UNIX_TEST_PASSWORD: &str = "unix test user password";
|
||||
|
||||
#[tokio::test]
|
||||
|
@ -20,9 +19,9 @@ async fn test_server_create() {
|
|||
let e: Entry = serde_json::from_str(
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["account"],
|
||||
"name": ["testperson"],
|
||||
"displayname": ["testperson"]
|
||||
"class": ["account", "service_account"],
|
||||
"name": ["testaccount"],
|
||||
"displayname": ["testaccount"]
|
||||
}
|
||||
}"#,
|
||||
)
|
||||
|
@ -139,123 +138,6 @@ async fn test_server_search() {
|
|||
assert!(name == &vec!["admin".to_string()]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_server_admin_change_simple_password() {
|
||||
let rsclient = setup_async_test().await;
|
||||
// First show we are un-authenticated.
|
||||
let pre_res = rsclient.whoami().await;
|
||||
// This means it was okay whoami, but no uat attached.
|
||||
assert!(pre_res.unwrap().is_none());
|
||||
|
||||
let res = rsclient
|
||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
assert!(res.is_ok());
|
||||
|
||||
// Now change the password.
|
||||
rsclient
|
||||
.idm_account_set_password(ADMIN_TEST_PASSWORD_CHANGE.to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Now "reset" the client.
|
||||
let _ = rsclient.logout().await;
|
||||
// New password works!
|
||||
let res = rsclient
|
||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD_CHANGE)
|
||||
.await;
|
||||
|
||||
assert!(res.is_ok());
|
||||
|
||||
// On the admin, show our credential state.
|
||||
let cred_state = rsclient
|
||||
.idm_account_get_credential_status("admin")
|
||||
.await
|
||||
.unwrap();
|
||||
// Check the creds are what we expect.
|
||||
if cred_state.creds.len() != 1 {
|
||||
assert!(false);
|
||||
}
|
||||
|
||||
if let Some(cred) = cred_state.creds.get(0) {
|
||||
assert!(cred.type_ == CredentialDetailType::Password)
|
||||
} else {
|
||||
assert!(false);
|
||||
}
|
||||
|
||||
// Old password fails, check after to prevent soft-locking.
|
||||
let res = rsclient
|
||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
// Add a test for resetting another accounts pws via the rest api
|
||||
#[tokio::test]
|
||||
async fn test_server_admin_reset_simple_password() {
|
||||
let rsclient = setup_async_test().await;
|
||||
let res = rsclient
|
||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
assert!(res.is_ok());
|
||||
// Create a diff account
|
||||
let e: Entry = serde_json::from_str(
|
||||
r#"{
|
||||
"attrs": {
|
||||
"class": ["account"],
|
||||
"name": ["testperson"],
|
||||
"displayname": ["testperson"]
|
||||
}
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Not logged in - should fail!
|
||||
let res = rsclient.create(vec![e]).await;
|
||||
assert!(res.is_ok());
|
||||
// By default, admin's can't actually administer accounts, so mod them into
|
||||
// the account admin group.
|
||||
let f = Filter::Eq("name".to_string(), "idm_admins".to_string());
|
||||
let m = ModifyList::new_list(vec![Modify::Present(
|
||||
"member".to_string(),
|
||||
"system_admins".to_string(),
|
||||
)]);
|
||||
let res = rsclient.modify(f, m).await;
|
||||
assert!(res.is_ok());
|
||||
|
||||
// Now set it's password - should be rejected based on low quality
|
||||
let res = rsclient
|
||||
.idm_account_primary_credential_set_password("testperson", "password")
|
||||
.await;
|
||||
assert!(res.is_err());
|
||||
// Set the password to ensure it's good
|
||||
let res = rsclient
|
||||
.idm_account_primary_credential_set_password(
|
||||
"testperson",
|
||||
"tai4eCohtae9aegheo3Uw0oobahVighaig6heeli",
|
||||
)
|
||||
.await;
|
||||
assert!(res.is_ok());
|
||||
// Check it stuck.
|
||||
let tclient = rsclient.new_session().expect("failed to build new session");
|
||||
assert!(tclient
|
||||
.auth_simple_password("testperson", "tai4eCohtae9aegheo3Uw0oobahVighaig6heeli")
|
||||
.await
|
||||
.is_ok());
|
||||
|
||||
// Generate a pw instead
|
||||
let res = rsclient
|
||||
.idm_account_primary_credential_set_generated("testperson")
|
||||
.await;
|
||||
assert!(res.is_ok());
|
||||
let gpw = res.unwrap();
|
||||
let tclient = rsclient.new_session().expect("failed to build new session");
|
||||
assert!(tclient
|
||||
.auth_simple_password("testperson", gpw.as_str())
|
||||
.await
|
||||
.is_ok());
|
||||
}
|
||||
|
||||
// test the rest group endpoint.
|
||||
#[tokio::test]
|
||||
async fn test_server_rest_group_read() {
|
||||
|
@ -358,10 +240,10 @@ async fn test_server_rest_account_read() {
|
|||
assert!(res.is_ok());
|
||||
|
||||
// List the accounts
|
||||
let a_list = rsclient.idm_account_list().await.unwrap();
|
||||
let a_list = rsclient.idm_service_account_list().await.unwrap();
|
||||
assert!(!a_list.is_empty());
|
||||
|
||||
let a = rsclient.idm_account_get("admin").await.unwrap();
|
||||
let a = rsclient.idm_service_account_get("admin").await.unwrap();
|
||||
assert!(a.is_some());
|
||||
println!("{:?}", a);
|
||||
}
|
||||
|
@ -403,22 +285,15 @@ async fn test_server_radius_credential_lifecycle() {
|
|||
.await;
|
||||
assert!(res.is_ok());
|
||||
|
||||
// self management of credentials is only for persons.
|
||||
// All admin to create persons.
|
||||
rsclient
|
||||
.idm_account_person_extend("admin", None, None)
|
||||
.idm_group_add_members("idm_admins", &["admin"])
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let f = Filter::Eq("name".to_string(), "idm_admins".to_string());
|
||||
let m = ModifyList::new_list(vec![Modify::Present(
|
||||
"member".to_string(),
|
||||
"system_admins".to_string(),
|
||||
)]);
|
||||
let res = rsclient.modify(f, m).await;
|
||||
assert!(res.is_ok());
|
||||
|
||||
// self management of credentials is only for persons.
|
||||
rsclient
|
||||
.idm_account_create("demo_account", "Deeeeemo")
|
||||
.idm_person_account_create("demo_account", "Deeeeemo")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -475,7 +350,7 @@ async fn test_server_radius_credential_lifecycle() {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_server_rest_account_lifecycle() {
|
||||
async fn test_server_rest_person_account_lifecycle() {
|
||||
let rsclient = setup_async_test().await;
|
||||
let res = rsclient
|
||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||
|
@ -484,40 +359,46 @@ async fn test_server_rest_account_lifecycle() {
|
|||
// To enable the admin to actually make some of these changes, we have
|
||||
// to make them a people admin. NOT recommended in production!
|
||||
rsclient
|
||||
.idm_group_add_members("idm_account_write_priv", &["admin"])
|
||||
.idm_group_add_members("idm_admins", &["admin"])
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Create a new account
|
||||
rsclient
|
||||
.idm_account_create("demo_account", "Deeeeemo")
|
||||
.idm_person_account_create("demo_account", "Deeeeemo")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// View the account
|
||||
rsclient.idm_account_get("demo_account").await.unwrap();
|
||||
rsclient
|
||||
.idm_person_account_get("demo_account")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// change the name?
|
||||
rsclient
|
||||
.idm_account_set_displayname("demo_account", "Demo Account")
|
||||
.idm_person_account_set_attr("demo_account", "displayname", &["Demo Account"])
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Test adding some mail addrs
|
||||
rsclient
|
||||
.idm_account_add_attr("demo_account", "mail", &["demo@idm.example.com"])
|
||||
.idm_person_account_add_attr("demo_account", "mail", &["demo@idm.example.com"])
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let r = rsclient
|
||||
.idm_account_get_attr("demo_account", "mail")
|
||||
.idm_person_account_get_attr("demo_account", "mail")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(r == Some(vec!["demo@idm.example.com".to_string()]));
|
||||
|
||||
// Delete the account
|
||||
rsclient.idm_account_delete("demo_account").await.unwrap();
|
||||
rsclient
|
||||
.idm_person_account_delete("demo_account")
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
@ -539,13 +420,13 @@ async fn test_server_rest_sshkey_lifecycle() {
|
|||
|
||||
// Post an invalid key (should error)
|
||||
let r1 = rsclient
|
||||
.idm_account_post_ssh_pubkey("admin", "inv", "invalid key")
|
||||
.idm_service_account_post_ssh_pubkey("admin", "inv", "invalid key")
|
||||
.await;
|
||||
assert!(r1.is_err());
|
||||
|
||||
// Post a valid key
|
||||
let r2 = rsclient
|
||||
.idm_account_post_ssh_pubkey("admin", "k1", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAeGW1P6Pc2rPq0XqbRaDKBcXZUPRklo0L1EyR30CwoP william@amethyst").await;
|
||||
.idm_service_account_post_ssh_pubkey("admin", "k1", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAeGW1P6Pc2rPq0XqbRaDKBcXZUPRklo0L1EyR30CwoP william@amethyst").await;
|
||||
println!("{:?}", r2);
|
||||
assert!(r2.is_ok());
|
||||
|
||||
|
@ -555,7 +436,7 @@ async fn test_server_rest_sshkey_lifecycle() {
|
|||
|
||||
// Post a valid key
|
||||
let r3 = rsclient
|
||||
.idm_account_post_ssh_pubkey("admin", "k2", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBx4TpJYQjd0YI5lQIHqblIsCIK5NKVFURYS/eM3o6/Z william@amethyst").await;
|
||||
.idm_service_account_post_ssh_pubkey("admin", "k2", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBx4TpJYQjd0YI5lQIHqblIsCIK5NKVFURYS/eM3o6/Z william@amethyst").await;
|
||||
assert!(r3.is_ok());
|
||||
|
||||
// Get, should have both keys.
|
||||
|
@ -563,7 +444,9 @@ async fn test_server_rest_sshkey_lifecycle() {
|
|||
assert!(sk3.len() == 2);
|
||||
|
||||
// Delete a key (by tag)
|
||||
let r4 = rsclient.idm_account_delete_ssh_pubkey("admin", "k1").await;
|
||||
let r4 = rsclient
|
||||
.idm_service_account_delete_ssh_pubkey("admin", "k1")
|
||||
.await;
|
||||
assert!(r4.is_ok());
|
||||
|
||||
// Get, should have remaining key.
|
||||
|
@ -622,13 +505,13 @@ async fn test_server_rest_posix_lifecycle() {
|
|||
|
||||
// Create a new account
|
||||
rsclient
|
||||
.idm_account_create("posix_account", "Posix Demo Account")
|
||||
.idm_person_account_create("posix_account", "Posix Demo Account")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Extend the account with posix attrs.
|
||||
rsclient
|
||||
.idm_account_unix_extend("posix_account", None, None)
|
||||
.idm_person_account_unix_extend("posix_account", None, None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -723,19 +606,19 @@ async fn test_server_rest_posix_auth_lifecycle() {
|
|||
|
||||
// Setup a unix user
|
||||
rsclient
|
||||
.idm_account_create("posix_account", "Posix Demo Account")
|
||||
.idm_person_account_create("posix_account", "Posix Demo Account")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Extend the account with posix attrs.
|
||||
rsclient
|
||||
.idm_account_unix_extend("posix_account", None, None)
|
||||
.idm_person_account_unix_extend("posix_account", None, None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// add their password (unix self)
|
||||
rsclient
|
||||
.idm_account_unix_cred_put("posix_account", UNIX_TEST_PASSWORD)
|
||||
.idm_person_account_unix_cred_put("posix_account", UNIX_TEST_PASSWORD)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -763,7 +646,7 @@ async fn test_server_rest_posix_auth_lifecycle() {
|
|||
|
||||
// clear password? (unix self)
|
||||
rsclient
|
||||
.idm_account_unix_cred_delete("posix_account")
|
||||
.idm_person_account_unix_cred_delete("posix_account")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -793,18 +676,21 @@ async fn test_server_rest_recycle_lifecycle() {
|
|||
|
||||
// Setup a unix user
|
||||
rsclient
|
||||
.idm_account_create("recycle_account", "Recycle Demo Account")
|
||||
.idm_person_account_create("recycle_account", "Recycle Demo Account")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// delete them
|
||||
rsclient
|
||||
.idm_account_delete("recycle_account")
|
||||
.idm_person_account_delete("recycle_account")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// not there
|
||||
let acc = rsclient.idm_account_get("recycle_account").await.unwrap();
|
||||
let acc = rsclient
|
||||
.idm_person_account_get("recycle_account")
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(acc.is_none());
|
||||
|
||||
// list the recycle bin
|
||||
|
@ -822,7 +708,10 @@ async fn test_server_rest_recycle_lifecycle() {
|
|||
.unwrap();
|
||||
|
||||
// they are there!
|
||||
let acc = rsclient.idm_account_get("recycle_account").await.unwrap();
|
||||
let acc = rsclient
|
||||
.idm_person_account_get("recycle_account")
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(acc.is_some());
|
||||
}
|
||||
|
||||
|
@ -840,25 +729,19 @@ async fn test_server_rest_account_import_password() {
|
|||
.await
|
||||
.unwrap();
|
||||
rsclient
|
||||
.idm_group_add_members("idm_people_extend_priv", &["admin"])
|
||||
.idm_group_add_members("idm_admins", &["admin"])
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Create a new account
|
||||
// Create a new person
|
||||
rsclient
|
||||
.idm_account_create("demo_account", "Deeeeemo")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Make them a person, so we can import the password
|
||||
rsclient
|
||||
.idm_account_person_extend("demo_account", None, None)
|
||||
.idm_person_account_create("demo_account", "Deeeeemo")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Attempt to import a bad password
|
||||
let r = rsclient
|
||||
.idm_account_primary_credential_import_password("demo_account", "password")
|
||||
.idm_person_account_primary_credential_import_password("demo_account", "password")
|
||||
.await;
|
||||
assert!(r.is_err());
|
||||
|
||||
|
@ -866,7 +749,7 @@ async fn test_server_rest_account_import_password() {
|
|||
// eicieY7ahchaoCh0eeTa
|
||||
// pbkdf2_sha256$36000$xIEozuZVAoYm$uW1b35DUKyhvQAf1mBqMvoBDcqSD06juzyO/nmyV0+w=
|
||||
rsclient
|
||||
.idm_account_primary_credential_import_password(
|
||||
.idm_person_account_primary_credential_import_password(
|
||||
"demo_account",
|
||||
"pbkdf2_sha256$36000$xIEozuZVAoYm$uW1b35DUKyhvQAf1mBqMvoBDcqSD06juzyO/nmyV0+w=",
|
||||
)
|
||||
|
@ -883,7 +766,7 @@ async fn test_server_rest_account_import_password() {
|
|||
|
||||
// And that the account can self read the cred status.
|
||||
let cred_state = rsclient
|
||||
.idm_account_get_credential_status("demo_account")
|
||||
.idm_person_account_get_credential_status("demo_account")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -894,174 +777,6 @@ async fn test_server_rest_account_import_password() {
|
|||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_server_rest_totp_auth_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();
|
||||
|
||||
// Create a new account
|
||||
rsclient
|
||||
.idm_account_create("demo_account", "Deeeeemo")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Enroll a totp to the account
|
||||
assert!(rsclient
|
||||
.idm_account_primary_credential_set_password("demo_account", "sohdi3iuHo6mai7noh0a")
|
||||
.await
|
||||
.is_ok());
|
||||
let (sessionid, tok) = rsclient
|
||||
.idm_account_primary_credential_generate_totp("demo_account")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let r_tok: Totp = tok.into();
|
||||
let totp = r_tok
|
||||
.do_totp_duration_from_epoch(
|
||||
&SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap(),
|
||||
)
|
||||
.expect("Failed to do totp?");
|
||||
|
||||
rsclient
|
||||
.idm_account_primary_credential_verify_totp("demo_account", totp, sessionid)
|
||||
.await
|
||||
.unwrap(); // the result
|
||||
|
||||
// Check a good auth
|
||||
let rsclient_good = rsclient.new_session().unwrap();
|
||||
let totp = r_tok
|
||||
.do_totp_duration_from_epoch(
|
||||
&SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap(),
|
||||
)
|
||||
.expect("Failed to do totp?");
|
||||
// TODO: It's extremely rare, but it's happened ONCE where, the time window
|
||||
// elapsed DURING this test, so there is a minor possibility of this actually
|
||||
// having a false negative. Is it possible to prevent this?
|
||||
assert!(rsclient_good
|
||||
.auth_password_totp("demo_account", "sohdi3iuHo6mai7noh0a", totp)
|
||||
.await
|
||||
.is_ok());
|
||||
|
||||
// Check a bad auth - needs to be second as we are going to trigger the slock.
|
||||
// Get a new connection
|
||||
let rsclient_bad = rsclient.new_session().unwrap();
|
||||
assert!(rsclient_bad
|
||||
.auth_password_totp("demo_account", "sohdi3iuHo6mai7noh0a", 0)
|
||||
.await
|
||||
.is_err());
|
||||
// Delay by one second to allow the account to recover from the softlock.
|
||||
std::thread::sleep(std::time::Duration::from_millis(1100));
|
||||
|
||||
// Remove TOTP on the account.
|
||||
rsclient
|
||||
.idm_account_primary_credential_remove_totp("demo_account")
|
||||
.await
|
||||
.unwrap();
|
||||
// Check password auth.
|
||||
let rsclient_good = rsclient.new_session().unwrap();
|
||||
assert!(rsclient_good
|
||||
.auth_simple_password("demo_account", "sohdi3iuHo6mai7noh0a")
|
||||
.await
|
||||
.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_server_rest_backup_code_auth_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();
|
||||
|
||||
// Create a new account
|
||||
rsclient
|
||||
.idm_account_create("demo_account", "Deeeeemo")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Enroll a totp to the account
|
||||
assert!(rsclient
|
||||
.idm_account_primary_credential_set_password("demo_account", "sohdi3iuHo6mai7noh0a")
|
||||
.await
|
||||
.is_ok());
|
||||
let (sessionid, tok) = rsclient
|
||||
.idm_account_primary_credential_generate_totp("demo_account")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let r_tok: Totp = tok.into();
|
||||
let totp = r_tok
|
||||
.do_totp_duration_from_epoch(
|
||||
&SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap(),
|
||||
)
|
||||
.expect("Failed to do totp?");
|
||||
|
||||
rsclient
|
||||
.idm_account_primary_credential_verify_totp("demo_account", totp, sessionid)
|
||||
.await
|
||||
.unwrap(); // the result
|
||||
|
||||
// Generate backup codes
|
||||
let backup_codes = rsclient
|
||||
.idm_account_primary_credential_generate_backup_code("demo_account")
|
||||
.await
|
||||
.expect("Failed to generate backup codes?");
|
||||
|
||||
// Check a good auth using a backup code
|
||||
let rsclient_good = rsclient.new_session().unwrap();
|
||||
assert!(rsclient_good
|
||||
.auth_password_backup_code(
|
||||
"demo_account",
|
||||
"sohdi3iuHo6mai7noh0a",
|
||||
backup_codes[0].as_str()
|
||||
)
|
||||
.await
|
||||
.is_ok());
|
||||
|
||||
// Check a bad auth - needs to be second as we are going to trigger the slock.
|
||||
// Get a new connection
|
||||
let rsclient_bad = rsclient.new_session().unwrap();
|
||||
assert!(rsclient_bad
|
||||
.auth_password_backup_code("demo_account", "sohdi3iuHo6mai7noh0a", "wrong-backup-code")
|
||||
.await
|
||||
.is_err());
|
||||
// Delay by one second to allow the account to recover from the softlock.
|
||||
std::thread::sleep(std::time::Duration::from_millis(1100));
|
||||
|
||||
// Remove TOTP and backup codes on the account.
|
||||
rsclient
|
||||
.idm_account_primary_credential_remove_totp("demo_account")
|
||||
.await
|
||||
.unwrap();
|
||||
// Check password auth.
|
||||
let rsclient_good = rsclient.new_session().unwrap();
|
||||
assert!(rsclient_good
|
||||
.auth_simple_password("demo_account", "sohdi3iuHo6mai7noh0a")
|
||||
.await
|
||||
.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_server_rest_oauth2_basic_lifecycle() {
|
||||
let rsclient = setup_async_test().await;
|
||||
|
@ -1200,13 +915,13 @@ async fn test_server_credential_update_session_pw() {
|
|||
|
||||
// Create an account
|
||||
rsclient
|
||||
.idm_account_create("demo_account", "Demo Account")
|
||||
.idm_person_account_create("demo_account", "Demo Account")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Create an intent token for them
|
||||
let intent_token = rsclient
|
||||
.idm_account_credential_update_intent("demo_account")
|
||||
.idm_person_account_credential_update_intent("demo_account")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -1257,20 +972,15 @@ async fn test_server_credential_update_session_totp_pw() {
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
// Create an account
|
||||
// Create a person
|
||||
// - person so they can self-modify credentials later in the test
|
||||
rsclient
|
||||
.idm_account_create("demo_account", "Demo Account")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Make them a person so they can self-modify credentials later in the test
|
||||
rsclient
|
||||
.idm_account_person_extend("demo_account", None, None)
|
||||
.idm_person_account_create("demo_account", "Demo Account")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let intent_token = rsclient
|
||||
.idm_account_credential_update_intent("demo_account")
|
||||
.idm_person_account_credential_update_intent("demo_account")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -1382,13 +1092,13 @@ async fn test_server_credential_update_session_passkey() {
|
|||
|
||||
// Create an account
|
||||
rsclient
|
||||
.idm_account_create("demo_account", "Demo Account")
|
||||
.idm_person_account_create("demo_account", "Demo Account")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Create an intent token for them
|
||||
let intent_token = rsclient
|
||||
.idm_account_credential_update_intent("demo_account")
|
||||
.idm_person_account_credential_update_intent("demo_account")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
|
@ -24,12 +24,12 @@ use super::totpmodal::TotpModalApp;
|
|||
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(PartialEq, Properties)]
|
||||
#[derive(PartialEq, Eq, Properties)]
|
||||
pub struct ModalProps {
|
||||
pub token: CUSessionToken,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Properties)]
|
||||
#[derive(PartialEq, Eq, Properties)]
|
||||
pub struct PasskeyRemoveModalProps {
|
||||
pub token: CUSessionToken,
|
||||
pub tag: String,
|
||||
|
|
|
@ -17,7 +17,7 @@ use crate::views::{ViewRoute, ViewsApp};
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// router to decide on state.
|
||||
#[derive(Routable, PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Routable, PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum Route {
|
||||
#[at("/")]
|
||||
Landing,
|
||||
|
|
|
@ -31,7 +31,7 @@ pub fn clear_bearer_token() {
|
|||
PersistentStorage::delete("kanidm_bearer_token");
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
pub enum Location {
|
||||
Manager(Route),
|
||||
Views(ViewRoute),
|
||||
|
|
|
@ -23,7 +23,7 @@ use apps::AppsApp;
|
|||
use profile::ProfileApp;
|
||||
use security::SecurityApp;
|
||||
|
||||
#[derive(Routable, PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Routable, PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum ViewRoute {
|
||||
#[at("/ui/view/apps")]
|
||||
Apps,
|
||||
|
@ -46,7 +46,7 @@ enum State {
|
|||
Error { emsg: String, kopid: Option<String> },
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Properties)]
|
||||
#[derive(PartialEq, Eq, Properties)]
|
||||
pub struct ViewProps {
|
||||
pub token: String,
|
||||
}
|
||||
|
|
|
@ -102,7 +102,7 @@ impl KaniHttpServer {
|
|||
match e {
|
||||
Entity::Account(a) => {
|
||||
self.client
|
||||
.idm_account_create(&a.name, &a.display_name)
|
||||
.idm_person_account_create(&a.name, &a.display_name)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.or_else(|e| {
|
||||
|
@ -125,7 +125,7 @@ impl KaniHttpServer {
|
|||
|
||||
// Now set the account password
|
||||
self.client
|
||||
.idm_account_primary_credential_set_password(&a.name, &a.password)
|
||||
.idm_person_account_primary_credential_set_password(&a.name, &a.password)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|e| {
|
||||
|
@ -134,7 +134,7 @@ impl KaniHttpServer {
|
|||
|
||||
// For ldap tests, we need to make these posix accounts.
|
||||
self.client
|
||||
.idm_account_unix_extend(&a.name, None, None)
|
||||
.idm_person_account_unix_extend(&a.name, None, None)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|e| {
|
||||
|
@ -142,7 +142,7 @@ impl KaniHttpServer {
|
|||
})?;
|
||||
|
||||
self.client
|
||||
.idm_account_unix_cred_put(&a.name, &a.password)
|
||||
.idm_person_account_unix_cred_put(&a.name, &a.password)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|e| {
|
||||
|
|
|
@ -105,7 +105,7 @@ impl LdapClient {
|
|||
error!("Failed to initialise TLS -> {:?}", e);
|
||||
})?;
|
||||
|
||||
let _ = SslStream::connect(Pin::new(&mut tlsstream))
|
||||
SslStream::connect(Pin::new(&mut tlsstream))
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("Failed to initialise TLS -> {:?}", e);
|
||||
|
@ -113,7 +113,7 @@ impl LdapClient {
|
|||
|
||||
let mut framed = Framed::new(tlsstream, LdapCodec);
|
||||
|
||||
let _ = framed.send(msg).await.map_err(|e| {
|
||||
framed.send(msg).await.map_err(|e| {
|
||||
error!("Unable to bind -> {:?}", e);
|
||||
})?;
|
||||
|
||||
|
@ -223,7 +223,7 @@ impl LdapClient {
|
|||
};
|
||||
|
||||
// Send it
|
||||
let _ = inner.framed.send(msg).await.map_err(|e| {
|
||||
inner.framed.send(msg).await.map_err(|e| {
|
||||
error!("Unable to search -> {:?}", e);
|
||||
})?;
|
||||
|
||||
|
@ -269,7 +269,7 @@ impl LdapClient {
|
|||
};
|
||||
|
||||
// Send it
|
||||
let _ = inner.framed.send(msg).await.map_err(|e| {
|
||||
inner.framed.send(msg).await.map_err(|e| {
|
||||
error!("Unable to delete -> {:?}", e);
|
||||
})?;
|
||||
if let Some(Ok(msg)) = inner.framed.next().await {
|
||||
|
@ -306,7 +306,7 @@ impl LdapClient {
|
|||
};
|
||||
|
||||
// Send it
|
||||
let _ = inner.framed.send(msg).await.map_err(|e| {
|
||||
inner.framed.send(msg).await.map_err(|e| {
|
||||
error!("Unable to add -> {:?}", e);
|
||||
})?;
|
||||
if let Some(Ok(msg)) = inner.framed.next().await {
|
||||
|
@ -343,7 +343,7 @@ impl LdapClient {
|
|||
};
|
||||
|
||||
// Send it
|
||||
let _ = inner.framed.send(msg).await.map_err(|e| {
|
||||
inner.framed.send(msg).await.map_err(|e| {
|
||||
error!("Unable to modify -> {:?}", e);
|
||||
})?;
|
||||
if let Some(Ok(msg)) = inner.framed.next().await {
|
||||
|
|
|
@ -14,5 +14,5 @@ tracing = { version = "^0.1.35", features = ["attributes", "max_level_trace", "r
|
|||
tracing-subscriber = { version = "^0.3.14", features = ["env-filter"] }
|
||||
|
||||
# tracing-forest = { version = "0.1.4", features = ["uuid", "smallvec", "tokio", "env-filter"] }
|
||||
tracing-forest = { git = "https://github.com/Firstyear/tracing-forest.git", rev = "18d242a4dde060c4946ade0a2c4d5be1df048aea", features = ["uuid", "smallvec", "tokio", "env-filter"] }
|
||||
tracing-forest = { git = "https://github.com/QnnOkabayashi/tracing-forest.git", rev = "48d78f7294ceee47a22eee5c80964143c4fb3fe1", features = ["uuid", "smallvec", "tokio", "env-filter"] }
|
||||
|
||||
|
|
Loading…
Reference in a new issue