mirror of
https://github.com/kanidm/kanidm.git
synced 2025-06-07 16:47:47 +02:00
Some progress on admin ui for managing groups and users
This commit is contained in:
parent
9611a7f976
commit
892c013613
160
Cargo.lock
generated
160
Cargo.lock
generated
|
@ -113,9 +113,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.95"
|
||||
version = "1.0.96"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
||||
checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4"
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
|
@ -665,9 +665,9 @@ checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9"
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.13"
|
||||
version = "1.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7777341816418c02e033934a09f20dc0ccaf65a5201ef8a450ae0105a573fda"
|
||||
checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
@ -727,9 +727,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.28"
|
||||
version = "4.5.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e77c3243bd94243c03672cb5154667347c457ca271254724f9f393aee1c05ff"
|
||||
checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
|
@ -737,9 +737,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.27"
|
||||
version = "4.5.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7"
|
||||
checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
|
@ -749,9 +749,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "4.5.44"
|
||||
version = "4.5.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "375f9d8255adeeedd51053574fd8d4ba875ea5fa558e86617b07f09f1680c8b6"
|
||||
checksum = "1e3040c8291884ddf39445dc033c70abc2bc44a42f0a3a00571a0f483a83f0cd"
|
||||
dependencies = [
|
||||
"clap",
|
||||
]
|
||||
|
@ -812,16 +812,16 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "concread"
|
||||
version = "0.5.3"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cba00cef522c2597dfbb0a8d1b0ac8ac2b99714f50cc354cda71da63164da0be"
|
||||
checksum = "0a06c26e76cd1d7a88a44324d0cf18b11589be552e97af09bee345f7e7334c6d"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"arc-swap",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-queue",
|
||||
"crossbeam-utils",
|
||||
"lru",
|
||||
"lru 0.13.0",
|
||||
"smallvec",
|
||||
"sptr",
|
||||
"tokio",
|
||||
|
@ -1021,9 +1021,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "csv-core"
|
||||
version = "0.1.11"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70"
|
||||
checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
@ -1126,9 +1126,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.7.0"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f"
|
||||
checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010"
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
|
@ -1292,9 +1292,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "document-features"
|
||||
version = "0.2.10"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0"
|
||||
checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d"
|
||||
dependencies = [
|
||||
"litrs",
|
||||
]
|
||||
|
@ -1383,9 +1383,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
|
@ -2240,9 +2240,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.7"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e"
|
||||
checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
|
@ -2443,7 +2443,7 @@ dependencies = [
|
|||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"h2 0.4.7",
|
||||
"h2 0.4.8",
|
||||
"http 1.2.0",
|
||||
"http-body 1.0.1",
|
||||
"httparse",
|
||||
|
@ -2801,6 +2801,15 @@ dependencies = [
|
|||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.14"
|
||||
|
@ -3006,6 +3015,7 @@ dependencies = [
|
|||
"sshkey-attest",
|
||||
"sshkeys",
|
||||
"time",
|
||||
"tracing",
|
||||
"url",
|
||||
"urlencoding",
|
||||
"utoipa",
|
||||
|
@ -3085,7 +3095,7 @@ dependencies = [
|
|||
"kanidmd_core",
|
||||
"kanidmd_testkit",
|
||||
"libc",
|
||||
"lru",
|
||||
"lru 0.12.5",
|
||||
"mimalloc",
|
||||
"notify-debouncer-full",
|
||||
"prctl",
|
||||
|
@ -3467,9 +3477,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.25"
|
||||
version = "0.4.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
|
||||
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
|
||||
|
||||
[[package]]
|
||||
name = "lru"
|
||||
|
@ -3480,6 +3490,15 @@ dependencies = [
|
|||
"hashbrown 0.15.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lru"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465"
|
||||
dependencies = [
|
||||
"hashbrown 0.15.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "malloced"
|
||||
version = "1.3.1"
|
||||
|
@ -3567,9 +3586,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
|||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.3"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924"
|
||||
checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
]
|
||||
|
@ -3622,9 +3641,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.13"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c"
|
||||
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
|
@ -3933,9 +3952,9 @@ checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
|
|||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.70"
|
||||
version = "0.10.71"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61cfb4e166a8bb8c9b55c500bc2308550148ece889be90f609377e58140f42c6"
|
||||
checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"cfg-if",
|
||||
|
@ -3965,9 +3984,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
|||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.105"
|
||||
version = "0.9.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc"
|
||||
checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
|
@ -4367,9 +4386,9 @@ checksum = "744a264d26b88a6a7e37cbad97953fa233b94d585236310bcbc88474b4092d79"
|
|||
|
||||
[[package]]
|
||||
name = "prost"
|
||||
version = "0.13.4"
|
||||
version = "0.13.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c0fef6c4230e4ccf618a35c59d7ede15dea37de8427500f50aff708806e42ec"
|
||||
checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"prost-derive",
|
||||
|
@ -4377,12 +4396,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "prost-derive"
|
||||
version = "0.13.4"
|
||||
version = "0.13.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3"
|
||||
checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.13.0",
|
||||
"itertools 0.14.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
|
@ -4460,9 +4479,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "quinn-udp"
|
||||
version = "0.5.9"
|
||||
version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c40286217b4ba3a71d644d752e6a0b71f13f1b6a2c5311acfcbe0c2418ed904"
|
||||
checksum = "e46f3055866785f6b92bc6164b76be02ca8f2eb4b002c0354b28cf4c119e5944"
|
||||
dependencies = [
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
|
@ -4513,9 +4532,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.8"
|
||||
version = "0.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
|
||||
checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
]
|
||||
|
@ -4665,7 +4684,7 @@ dependencies = [
|
|||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2 0.4.7",
|
||||
"h2 0.4.8",
|
||||
"http 1.2.0",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
|
@ -4713,15 +4732,14 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.8"
|
||||
version = "0.17.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
|
||||
checksum = "d34b5020fcdea098ef7d95e9f89ec15952123a4a039badd09fabebe9e963e839"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom 0.2.15",
|
||||
"libc",
|
||||
"spin",
|
||||
"untrusted",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
@ -4832,9 +4850,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.22"
|
||||
version = "0.23.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fb9263ab4eb695e42321db096e3b8fbd715a59b154d5c88d82db2175b681ba7"
|
||||
checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"ring",
|
||||
|
@ -5017,9 +5035,9 @@ checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.217"
|
||||
version = "1.0.218"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
|
||||
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
@ -5055,9 +5073,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.217"
|
||||
version = "1.0.218"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
||||
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -5066,9 +5084,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.138"
|
||||
version = "1.0.139"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949"
|
||||
checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
|
@ -5224,9 +5242,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.13.2"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
@ -5427,9 +5445,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
|||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.16.0"
|
||||
version = "3.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91"
|
||||
checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
|
@ -5564,9 +5582,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||
|
||||
[[package]]
|
||||
name = "tls_codec"
|
||||
version = "0.4.1"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5e78c9c330f8c85b2bae7c8368f2739157db9991235123aa1b15ef9502bfb6a"
|
||||
checksum = "0de2e01245e2bb89d6f05801c564fa27624dbd7b1846859876c7dad82e90bf6b"
|
||||
dependencies = [
|
||||
"tls_codec_derive",
|
||||
"zeroize",
|
||||
|
@ -5574,9 +5592,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tls_codec_derive"
|
||||
version = "0.4.1"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d9ef545650e79f30233c0003bcc2504d7efac6dad25fca40744de773fe2049c"
|
||||
checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -5705,7 +5723,7 @@ dependencies = [
|
|||
"axum",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"h2 0.4.7",
|
||||
"h2 0.4.8",
|
||||
"http 1.2.0",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
|
@ -5939,9 +5957,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
|
@ -5957,9 +5975,9 @@ checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217"
|
|||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.16"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
|
||||
checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
|
@ -6069,9 +6087,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.13.1"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ced87ca4be083373936a67f8de945faa23b6b42384bd5b64434850802c6dccd0"
|
||||
checksum = "93d59ca99a559661b96bf898d8fce28ed87935fd2bea9f05983c1464dd6c71b1"
|
||||
dependencies = [
|
||||
"getrandom 0.3.1",
|
||||
"serde",
|
||||
|
|
|
@ -38,6 +38,7 @@ uuid = { workspace = true, features = ["serde"] }
|
|||
webauthn-rs-proto = { workspace = true }
|
||||
sshkey-attest = { workspace = true }
|
||||
sshkeys = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
enum-iterator = { workspace = true }
|
||||
|
|
|
@ -4,11 +4,12 @@ use super::ScimSshPublicKey;
|
|||
use crate::attribute::Attribute;
|
||||
use crate::internal::UiHint;
|
||||
use scim_proto::ScimEntryHeader;
|
||||
use serde::Serialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::{base64, formats, hex::Hex, serde_as, skip_serializing_none};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use time::format_description::well_known::Rfc3339;
|
||||
use time::OffsetDateTime;
|
||||
use tracing::debug;
|
||||
use url::Url;
|
||||
use utoipa::ToSchema;
|
||||
use uuid::Uuid;
|
||||
|
@ -28,7 +29,7 @@ pub struct ScimEntryKanidm {
|
|||
pub attrs: BTreeMap<Attribute, ScimValueKanidm>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, ToSchema)]
|
||||
pub enum ScimAttributeEffectiveAccess {
|
||||
/// All attributes on the entry have this permission granted
|
||||
Grant,
|
||||
|
@ -49,7 +50,7 @@ impl ScimAttributeEffectiveAccess {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimEffectiveAccess {
|
||||
/// The identity that inherits the effective permission
|
||||
|
@ -209,7 +210,7 @@ pub struct ScimOAuth2ClaimMap {
|
|||
pub values: BTreeSet<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, PartialEq, Eq, ToSchema)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimReference {
|
||||
pub uuid: Uuid,
|
||||
|
@ -257,6 +258,92 @@ pub enum ScimValueKanidm {
|
|||
UiHints(Vec<UiHint>),
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, ToSchema)]
|
||||
pub struct ScimPerson {
|
||||
pub uuid: Uuid,
|
||||
pub name: String,
|
||||
pub displayname: Option<String>,
|
||||
pub spn: String,
|
||||
pub description: Option<String>,
|
||||
pub mails: Vec<ScimMail>,
|
||||
pub managed_by: Option<ScimReference>,
|
||||
pub groups: Vec<ScimReference>,
|
||||
}
|
||||
|
||||
impl TryFrom<ScimEntryKanidm> for ScimPerson {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(scim_entry: ScimEntryKanidm) -> Result<Self, Self::Error> {
|
||||
let attr_str = |attr: &Attribute| -> Option<&str> {
|
||||
match scim_entry.attrs.get(attr) {
|
||||
Some(ScimValueKanidm::String(inner_string)) => Some(inner_string.as_str()),
|
||||
Some(sv) => {
|
||||
debug!("SCIM entry had the {} attribute but it was not a ScimValueKanidm::String type, actual: {:?}", attr, sv);
|
||||
None
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
};
|
||||
|
||||
let attr_mails = || -> Option<&Vec<ScimMail>> {
|
||||
match scim_entry.attrs.get(&Attribute::Mail) {
|
||||
Some(ScimValueKanidm::Mail(inner_string)) => Some(inner_string),
|
||||
Some(sv) => {
|
||||
debug!("SCIM entry had the {} attribute but it was not a ScimValueKanidm::Mail type, actual: {:?}", Attribute::Mail, sv);
|
||||
None
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
};
|
||||
|
||||
let attr_reference = |attr: &Attribute| -> Option<&ScimReference> {
|
||||
match scim_entry.attrs.get(attr) {
|
||||
Some(ScimValueKanidm::EntryReference(refer)) => Some(refer),
|
||||
Some(sv) => {
|
||||
debug!("SCIM entry had the {} attribute but it was not a ScimValueKanidm::ScimReference type, actual: {:?}", attr, sv);
|
||||
None
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
};
|
||||
|
||||
let attr_references = |attr: &Attribute| -> Option<&Vec<ScimReference>> {
|
||||
match scim_entry.attrs.get(attr) {
|
||||
Some(ScimValueKanidm::EntryReferences(refs)) => Some(refs),
|
||||
Some(sv) => {
|
||||
debug!("SCIM entry had the {} attribute but it was not a ScimValueKanidm::EntryReferences type, actual: {:?}", attr, sv);
|
||||
None
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
};
|
||||
|
||||
let uuid = scim_entry.header.id;
|
||||
let name = attr_str(&Attribute::Name).ok_or(())?.to_string();
|
||||
let displayname = attr_str(&Attribute::DisplayName).map(|s| s.to_string());
|
||||
let spn = attr_str(&Attribute::Spn).ok_or(())?.to_string();
|
||||
let description = attr_str(&Attribute::Description).map(|t| t.to_string());
|
||||
let mails = attr_mails().cloned().unwrap_or_default();
|
||||
let groups = attr_references(&Attribute::DirectMemberOf)
|
||||
.cloned()
|
||||
.unwrap_or(vec![]);
|
||||
|
||||
let managed_by = attr_reference(&Attribute::EntryManagedBy).cloned();
|
||||
|
||||
Ok(ScimPerson {
|
||||
uuid,
|
||||
name,
|
||||
displayname,
|
||||
spn,
|
||||
description,
|
||||
mails,
|
||||
managed_by,
|
||||
groups,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for ScimValueKanidm {
|
||||
fn from(b: bool) -> Self {
|
||||
Self::Bool(b)
|
||||
|
|
|
@ -19,7 +19,7 @@ pub use self::auth::*;
|
|||
pub use self::unix::*;
|
||||
|
||||
/// The type of Account in use.
|
||||
#[derive(Clone, Copy, Debug, ToSchema)]
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, Debug, ToSchema)]
|
||||
pub enum AccountType {
|
||||
Person,
|
||||
ServiceAccount,
|
||||
|
|
|
@ -7,6 +7,7 @@ use kanidmd_lib::idm::scim::{
|
|||
};
|
||||
use kanidmd_lib::idm::server::IdmServerTransaction;
|
||||
use kanidmd_lib::prelude::*;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
impl QueryServerWriteV1 {
|
||||
#[instrument(
|
||||
|
@ -229,4 +230,35 @@ impl QueryServerReadV1 {
|
|||
.qs_read
|
||||
.scim_entry_id_get_ext(target_uuid, class, query, ident)
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
level = "info",
|
||||
skip_all,
|
||||
fields(uuid = ?eventid)
|
||||
)]
|
||||
pub async fn scim_entry_search(
|
||||
&self,
|
||||
client_auth_info: ClientAuthInfo,
|
||||
filter_intent: Filter<FilterInvalid>,
|
||||
eventid: Uuid,
|
||||
attrs: Option<BTreeSet<Attribute>>,
|
||||
acp: bool,
|
||||
) -> Result<Vec<ScimEntryKanidm>, OperationError> {
|
||||
let ct = duration_from_epoch_now();
|
||||
let mut idms_prox_read = self.idms.proxy_read().await?;
|
||||
let ident = idms_prox_read
|
||||
.validate_client_auth_info_to_ident(client_auth_info, ct)
|
||||
.inspect_err(|err| {
|
||||
error!(?err, "Invalid identity");
|
||||
})?;
|
||||
|
||||
let filter = filter_all!(f_and!([f_eq(Attribute::Class, EntryClass::Account.into())]));
|
||||
|
||||
idms_prox_read
|
||||
.qs_read
|
||||
.impersonate_search_ext(filter_intent, filter, &ident, attrs, acp)?
|
||||
.into_iter()
|
||||
.map(|entry| entry.to_scim_kanidm(&mut idms_prox_read.qs_read))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
|
19
server/core/src/https/views/admin/mod.rs
Normal file
19
server/core/src/https/views/admin/mod.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
use crate::https::ServerState;
|
||||
use axum::routing::get;
|
||||
use axum::Router;
|
||||
use axum_htmx::HxRequestGuardLayer;
|
||||
|
||||
mod persons;
|
||||
|
||||
pub fn admin_router() -> Router<ServerState> {
|
||||
let unguarded_router = Router::new()
|
||||
.route("/persons", get(persons::view_persons_get))
|
||||
.route(
|
||||
"/person/:person_uuid/view",
|
||||
get(persons::view_person_view_get),
|
||||
);
|
||||
|
||||
let guarded_router = Router::new().layer(HxRequestGuardLayer::new("/ui"));
|
||||
|
||||
Router::new().merge(unguarded_router).merge(guarded_router)
|
||||
}
|
185
server/core/src/https/views/admin/persons.rs
Normal file
185
server/core/src/https/views/admin/persons.rs
Normal file
|
@ -0,0 +1,185 @@
|
|||
use crate::https::extractors::{DomainInfo, VerifiedClientInformation};
|
||||
use crate::https::middleware::KOpId;
|
||||
use crate::https::views::errors::HtmxError;
|
||||
use crate::https::views::navbar::NavbarCtx;
|
||||
use crate::https::views::Urls;
|
||||
use crate::https::ServerState;
|
||||
use askama::Template;
|
||||
use axum::extract::{Path, State};
|
||||
use axum::http::Uri;
|
||||
use axum::response::{ErrorResponse, IntoResponse, Response};
|
||||
use axum::Extension;
|
||||
use axum_htmx::{HxPushUrl, HxRequest};
|
||||
use futures_util::TryFutureExt;
|
||||
use kanidm_proto::attribute::Attribute;
|
||||
use kanidm_proto::internal::OperationError;
|
||||
use kanidm_proto::scim_v1::server::{ScimEffectiveAccess, ScimEntryKanidm, ScimPerson};
|
||||
use kanidm_proto::scim_v1::ScimEntryGetQuery;
|
||||
use kanidmd_lib::constants::EntryClass;
|
||||
use kanidmd_lib::filter::{f_and, f_eq, Filter, FC};
|
||||
use kanidmd_lib::idm::server::DomainInfoRead;
|
||||
use kanidmd_lib::idm::ClientAuthInfo;
|
||||
use std::collections::BTreeSet;
|
||||
use std::str::FromStr;
|
||||
use uuid::Uuid;
|
||||
|
||||
const PERSON_ATTRIBUTES: [Attribute; 9] = [
|
||||
Attribute::Uuid,
|
||||
Attribute::Description,
|
||||
Attribute::Name,
|
||||
Attribute::DisplayName,
|
||||
Attribute::Spn,
|
||||
Attribute::Mail,
|
||||
Attribute::Class,
|
||||
Attribute::EntryManagedBy,
|
||||
Attribute::DirectMemberOf,
|
||||
];
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "admin/admin_panel_template.html")]
|
||||
pub(crate) struct PersonsView {
|
||||
navbar_ctx: NavbarCtx,
|
||||
partial: PersonsPartialView,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "admin/admin_persons_partial.html")]
|
||||
struct PersonsPartialView {
|
||||
persons: Vec<(ScimPerson, ScimEffectiveAccess)>,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "admin/admin_panel_template.html")]
|
||||
struct PersonView {
|
||||
partial: PersonViewPartial,
|
||||
navbar_ctx: NavbarCtx,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "admin/admin_person_view_partial.html")]
|
||||
struct PersonViewPartial {
|
||||
person: ScimPerson,
|
||||
scim_effective_access: ScimEffectiveAccess,
|
||||
}
|
||||
|
||||
pub(crate) async fn view_person_view_get(
|
||||
State(state): State<ServerState>,
|
||||
HxRequest(is_htmx): HxRequest,
|
||||
Extension(kopid): Extension<KOpId>,
|
||||
VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
|
||||
Path(uuid): Path<Uuid>,
|
||||
DomainInfo(domain_info): DomainInfo,
|
||||
) -> axum::response::Result<Response> {
|
||||
let (person, scim_effective_access) =
|
||||
get_person_info(uuid, state, &kopid, client_auth_info, domain_info.clone()).await?;
|
||||
let person_partial = PersonViewPartial {
|
||||
person,
|
||||
scim_effective_access,
|
||||
};
|
||||
|
||||
let path_string = format!("/ui/admin/person/{uuid}/view");
|
||||
let uri = Uri::from_str(path_string.as_str())
|
||||
.map_err(|_| HtmxError::new(&kopid, OperationError::Backend, domain_info.clone()))?;
|
||||
let push_url = HxPushUrl(uri);
|
||||
Ok(if is_htmx {
|
||||
(push_url, person_partial).into_response()
|
||||
} else {
|
||||
(
|
||||
push_url,
|
||||
PersonView {
|
||||
partial: person_partial,
|
||||
navbar_ctx: NavbarCtx { domain_info },
|
||||
},
|
||||
)
|
||||
.into_response()
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn view_persons_get(
|
||||
State(state): State<ServerState>,
|
||||
HxRequest(is_htmx): HxRequest,
|
||||
Extension(kopid): Extension<KOpId>,
|
||||
DomainInfo(domain_info): DomainInfo,
|
||||
VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
|
||||
) -> axum::response::Result<Response> {
|
||||
let persons = get_persons_info(state, &kopid, client_auth_info, domain_info.clone()).await?;
|
||||
let persons_partial = PersonsPartialView { persons: persons };
|
||||
|
||||
let push_url = HxPushUrl(Uri::from_static("/ui/admin/persons"));
|
||||
Ok(if is_htmx {
|
||||
(push_url, persons_partial).into_response()
|
||||
} else {
|
||||
(
|
||||
push_url,
|
||||
PersonsView {
|
||||
navbar_ctx: NavbarCtx { domain_info },
|
||||
partial: persons_partial,
|
||||
},
|
||||
)
|
||||
.into_response()
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_person_info(
|
||||
uuid: Uuid,
|
||||
state: ServerState,
|
||||
kopid: &KOpId,
|
||||
client_auth_info: ClientAuthInfo,
|
||||
domain_info: DomainInfoRead,
|
||||
) -> Result<(ScimPerson, ScimEffectiveAccess), ErrorResponse> {
|
||||
let scim_entry: ScimEntryKanidm = state
|
||||
.qe_r_ref
|
||||
.scim_entry_id_get(
|
||||
client_auth_info.clone(),
|
||||
kopid.eventid,
|
||||
uuid.to_string(),
|
||||
EntryClass::Person,
|
||||
ScimEntryGetQuery {
|
||||
attributes: Some(Vec::from(PERSON_ATTRIBUTES)),
|
||||
ext_access_check: true,
|
||||
},
|
||||
)
|
||||
.map_err(|op_err| HtmxError::new(kopid, op_err, domain_info.clone()))
|
||||
.await?;
|
||||
|
||||
if let Some(personinfo_info) = scimentry_into_personinfo(scim_entry) {
|
||||
Ok(personinfo_info)
|
||||
} else {
|
||||
Err(HtmxError::new(kopid, OperationError::InvalidState, domain_info.clone()).into())
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_persons_info(
|
||||
state: ServerState,
|
||||
kopid: &KOpId,
|
||||
client_auth_info: ClientAuthInfo,
|
||||
domain_info: DomainInfoRead,
|
||||
) -> Result<Vec<(ScimPerson, ScimEffectiveAccess)>, ErrorResponse> {
|
||||
let filter = filter_all!(f_and!([f_eq(Attribute::Class, EntryClass::Person.into())]));
|
||||
let attrs = Some(BTreeSet::from(PERSON_ATTRIBUTES));
|
||||
let base: Vec<ScimEntryKanidm> = state
|
||||
.qe_r_ref
|
||||
.scim_entry_search(client_auth_info.clone(), filter, kopid.eventid, attrs, true)
|
||||
.map_err(|op_err| HtmxError::new(kopid, op_err, domain_info.clone()))
|
||||
.await?;
|
||||
|
||||
// TODO: inefficient to sort here
|
||||
let mut persons: Vec<_> = base
|
||||
.into_iter()
|
||||
// TODO: Filtering away unsuccessful entries may not be desired.
|
||||
.filter_map(scimentry_into_personinfo)
|
||||
.collect();
|
||||
|
||||
persons.sort_by_key(|(sp, _)| sp.uuid);
|
||||
persons.reverse();
|
||||
Ok(persons)
|
||||
}
|
||||
|
||||
fn scimentry_into_personinfo(
|
||||
scim_entry: ScimEntryKanidm,
|
||||
) -> Option<(ScimPerson, ScimEffectiveAccess)> {
|
||||
let scim_effective_access = scim_entry.ext_access_check.clone()?; // TODO: This should be an error msg.
|
||||
let person = ScimPerson::try_from(scim_entry).ok()?;
|
||||
|
||||
Some((person, scim_effective_access))
|
||||
}
|
|
@ -45,14 +45,14 @@ pub(crate) async fn view_apps_get(
|
|||
.await
|
||||
.map_err(|old| HtmxError::new(&kopid, old, domain_info.clone()))?;
|
||||
|
||||
let apps_partial = AppsPartialView { apps: app_links };
|
||||
|
||||
Ok({
|
||||
(
|
||||
HxPushUrl(Uri::from_static(Urls::Apps.as_ref())),
|
||||
AppsView {
|
||||
navbar_ctx: NavbarCtx { domain_info },
|
||||
apps_partial: AppsPartialView { apps: app_links },
|
||||
},
|
||||
)
|
||||
.into_response()
|
||||
let apps_view = AppsView {
|
||||
navbar_ctx: NavbarCtx { domain_info },
|
||||
|
||||
apps_partial,
|
||||
};
|
||||
(HxPushUrl(Uri::from_static(Urls::Apps.as_ref())), apps_view).into_response()
|
||||
})
|
||||
}
|
||||
|
|
|
@ -105,6 +105,7 @@ pub(crate) async fn view_enrol_get(
|
|||
|
||||
Ok(ProfileView {
|
||||
navbar_ctx: NavbarCtx { domain_info },
|
||||
|
||||
profile_partial: EnrolDeviceView {
|
||||
menu_active_item: ProfileMenuItems::EnrolDevice,
|
||||
qr_code_svg,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use axum::http::StatusCode;
|
||||
use axum::response::{IntoResponse, Redirect, Response};
|
||||
use axum_htmx::{HxReswap, HxRetarget, SwapOption};
|
||||
use axum_htmx::{HxEvent, HxResponseTrigger, HxReswap, HxRetarget, SwapOption};
|
||||
use kanidmd_lib::idm::server::DomainInfoRead;
|
||||
use utoipa::ToSchema;
|
||||
use uuid::Uuid;
|
||||
|
@ -8,7 +8,7 @@ use uuid::Uuid;
|
|||
use kanidm_proto::internal::OperationError;
|
||||
|
||||
use crate::https::middleware::KOpId;
|
||||
use crate::https::views::UnrecoverableErrorView;
|
||||
use crate::https::views::{ErrorToastPartial, UnrecoverableErrorView};
|
||||
// #[derive(Template)]
|
||||
// #[template(path = "recoverable_error_partial.html")]
|
||||
// struct ErrorPartialView {
|
||||
|
@ -41,7 +41,23 @@ impl IntoResponse for HtmxError {
|
|||
| OperationError::SessionExpired
|
||||
| OperationError::InvalidSessionState => Redirect::to("/ui").into_response(),
|
||||
OperationError::SystemProtectedObject | OperationError::AccessDenied => {
|
||||
(StatusCode::FORBIDDEN, body).into_response()
|
||||
let trigger = HxResponseTrigger::after_swap([HxEvent::new(
|
||||
"permissionDenied".to_string(),
|
||||
)]);
|
||||
(
|
||||
trigger,
|
||||
HxRetarget("main".to_string()),
|
||||
HxReswap(SwapOption::BeforeEnd),
|
||||
(
|
||||
StatusCode::FORBIDDEN,
|
||||
ErrorToastPartial {
|
||||
err_code: inner,
|
||||
operation_id: kopid,
|
||||
},
|
||||
)
|
||||
.into_response(),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
OperationError::NoMatchingEntries => {
|
||||
(StatusCode::NOT_FOUND, body).into_response()
|
||||
|
|
|
@ -8,6 +8,7 @@ use axum::{
|
|||
|
||||
use axum_htmx::HxRequestGuardLayer;
|
||||
|
||||
use crate::https::views::admin::admin_router;
|
||||
use constants::Urls;
|
||||
use kanidmd_lib::{
|
||||
idm::server::DomainInfoRead,
|
||||
|
@ -16,6 +17,7 @@ use kanidmd_lib::{
|
|||
|
||||
use crate::https::ServerState;
|
||||
|
||||
mod admin;
|
||||
mod apps;
|
||||
pub(crate) mod constants;
|
||||
mod cookies;
|
||||
|
@ -36,6 +38,13 @@ struct UnrecoverableErrorView {
|
|||
domain_info: DomainInfoRead,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "admin/error_toast.html")]
|
||||
struct ErrorToastPartial {
|
||||
err_code: OperationError,
|
||||
operation_id: Uuid,
|
||||
}
|
||||
|
||||
pub fn view_router() -> Router<ServerState> {
|
||||
let mut unguarded_router = Router::new()
|
||||
.route(
|
||||
|
@ -122,7 +131,11 @@ pub fn view_router() -> Router<ServerState> {
|
|||
.route("/api/cu_commit", post(reset::commit))
|
||||
.layer(HxRequestGuardLayer::new("/ui"));
|
||||
|
||||
Router::new().merge(unguarded_router).merge(guarded_router)
|
||||
let admin_router = admin_router();
|
||||
Router::new()
|
||||
.merge(unguarded_router)
|
||||
.merge(guarded_router)
|
||||
.nest("/admin", admin_router)
|
||||
}
|
||||
|
||||
/// Serde deserialization decorator to map empty Strings to None,
|
||||
|
|
|
@ -48,6 +48,7 @@ pub(crate) async fn view_profile_get(
|
|||
|
||||
Ok(ProfileView {
|
||||
navbar_ctx: NavbarCtx { domain_info },
|
||||
|
||||
profile_partial: ProfilePartialView {
|
||||
menu_active_item: ProfileMenuItems::UserProfile,
|
||||
can_rw,
|
||||
|
|
|
@ -20,6 +20,15 @@ body {
|
|||
max-width: 680px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Bootstrap 5.3 fix for input-group validation
|
||||
* :has checks that a child can be selected with the selector
|
||||
* + selects the next sibling.
|
||||
*/
|
||||
.was-validated .input-group:has(.form-control:invalid) + .invalid-feedback {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sidebar
|
||||
*/
|
||||
|
|
10
server/core/templates/admin/admin_panel_template.html
Normal file
10
server/core/templates/admin/admin_panel_template.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
(% extends "base_htmx_with_nav.html" %)
|
||||
|
||||
(% block title %)Admin Panel(% endblock %)
|
||||
|
||||
(% block head %)
|
||||
(% endblock %)
|
||||
|
||||
(% block main %)
|
||||
(( partial|safe ))
|
||||
(% endblock %)
|
19
server/core/templates/admin/admin_partial_base.html
Normal file
19
server/core/templates/admin/admin_partial_base.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
<main class="container-xxl pb-5">
|
||||
<div class="d-flex flex-sm-row flex-column">
|
||||
<div class="list-group side-menu">
|
||||
<a href="/ui/admin/persons" hx-target="#main" class="list-group-item list-group-item-action (% block persons_item_extra_classes%)(%endblock%)">
|
||||
<img src="/pkg/img/icon-accounts.svg" alt="Persons" width="20" height="20">
|
||||
Persons</a>
|
||||
<a href="/ui/admin/groups" hx-target="#main" class="list-group-item list-group-item-action (% block groups_item_extra_classes%)(%endblock%)">
|
||||
<img src="/pkg/img/icon-groups.svg" alt="Groups" width="20" height="20">
|
||||
Groups (placeholder)</a>
|
||||
<a href="/ui/admin/oauth2" hx-target="#main" class="list-group-item list-group-item-action (% block oauth2_item_extra_classes%)(%endblock%)">
|
||||
<img src="/pkg/img/icon-oauth2.svg" alt="Oauth2" width="20" height="20">
|
||||
Oauth2 (placeholder)</a>
|
||||
</div>
|
||||
<div id="settings-window" class="flex-grow-1 ps-sm-4 pt-sm-0 pt-4">
|
||||
(% block admin_page %)
|
||||
(% endblock %)
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
|
@ -0,0 +1,34 @@
|
|||
(% macro string_attr(dispname, name, value, editable, attribute) %)
|
||||
(% if scim_effective_access.search.check(attribute|as_ref) %)
|
||||
<div class="row mt-3">
|
||||
<label for="person(( name ))" class="col-12 col-md-3 col-lg-2 col-form-label fw-bold py-0">(( dispname ))</label>
|
||||
<div class="col-12 col-md-8 col-lg-6">
|
||||
<input readonly class="form-control-plaintext py-0" id="person(( name ))" name="(( name ))" value="(( value ))">
|
||||
</div>
|
||||
</div>
|
||||
(% endif %)
|
||||
(% endmacro %)
|
||||
|
||||
<form hx-validate="true" hx-ext="bs-validation">
|
||||
(% call string_attr("UUID", "uuid", person.uuid, false, Attribute::Uuid) %)
|
||||
(% call string_attr("SPN", "spn", person.spn, false, Attribute::Spn) %)
|
||||
(% call string_attr("Name", "name", person.name, true, Attribute::Name) %)
|
||||
|
||||
(% if let Some(displayname) = person.displayname %)
|
||||
(% call string_attr("Displayname", "displayname", displayname, true, Attribute::DisplayName) %)
|
||||
(% else %)
|
||||
(% call string_attr("Displayname", "displayname", "none", true, Attribute::DisplayName) %)
|
||||
(% endif %)
|
||||
|
||||
(% if let Some(description) = person.description %)
|
||||
(% call string_attr("Description", "description", description, true, Attribute::Description) %)
|
||||
(% else %)
|
||||
(% call string_attr("Description", "description", "none", true, Attribute::Description) %)
|
||||
(% endif %)
|
||||
|
||||
(% if let Some(entry_managed_by) = person.managed_by %)
|
||||
(% call string_attr("Managed By", "managed_by", entry_managed_by.value, true, Attribute::EntryManagedBy) %)
|
||||
(% else %)
|
||||
(% call string_attr("Managed By", "managed_by", "none", true, Attribute::EntryManagedBy) %)
|
||||
(% endif %)
|
||||
</form>
|
57
server/core/templates/admin/admin_person_view_partial.html
Normal file
57
server/core/templates/admin/admin_person_view_partial.html
Normal file
|
@ -0,0 +1,57 @@
|
|||
(% extends "admin/admin_partial_base.html" %)
|
||||
|
||||
(% block persons_item_extra_classes %)active(% endblock %)
|
||||
|
||||
(% block admin_page %)
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/ui/admin/persons" hx-target="#main">persons Management</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Viewing</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
(% include "admin_person_details_partial.html" %)
|
||||
|
||||
<hr>
|
||||
|
||||
(% if scim_effective_access.search.check(Attribute::Mail|as_ref) %)
|
||||
<label class="mt-3 fw-bold">Emails</label>
|
||||
<form hx-validate="true" hx-ext="bs-validation">
|
||||
(% if person.mails.len() == 0 %)
|
||||
<p>There are no email addresses associated with this person.</p>
|
||||
(% else %)
|
||||
<ol class="list-group col-12 col-md-8 col-lg-6">
|
||||
(% for mail in person.mails %)
|
||||
<li id="personMail(( loop.index ))" class="list-group-item d-flex flex-row justify-content-between">
|
||||
<div class="d-flex align-items-center">(( mail.value ))</div>
|
||||
<div class="buttons float-end">
|
||||
</div>
|
||||
</li>
|
||||
(% endfor %)
|
||||
</ol>
|
||||
(% endif %)
|
||||
</form>
|
||||
(% endif %)
|
||||
|
||||
(% if scim_effective_access.search.check(Attribute::DirectMemberOf|as_ref) %)
|
||||
<label class="mt-3 fw-bold">DirectMemberOf</label>
|
||||
<form hx-validate="true" hx-ext="bs-validation">
|
||||
(% if person.groups.len() == 0 %)
|
||||
<p>There are no groups this person is a direct member of.</p>
|
||||
(% else %)
|
||||
<ol class="list-group col-12 col-md-8 col-lg-6">
|
||||
(% for group in person.groups %)
|
||||
<li id="personGroup(( loop.index ))" class="list-group-item d-flex flex-row justify-content-between">
|
||||
<div class="d-flex align-items-center">(( group.value ))</div>
|
||||
<div class="buttons float-end">
|
||||
</div>
|
||||
</li>
|
||||
(% endfor %)
|
||||
</ol>
|
||||
(% endif %)
|
||||
</form>
|
||||
(% endif %)
|
||||
|
||||
|
||||
|
||||
(% endblock %)
|
23
server/core/templates/admin/admin_persons_partial.html
Normal file
23
server/core/templates/admin/admin_persons_partial.html
Normal file
|
@ -0,0 +1,23 @@
|
|||
(% extends "admin/admin_partial_base.html" %)
|
||||
|
||||
(% block persons_item_extra_classes %)active(% endblock %)
|
||||
|
||||
(% block admin_page %)
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item active" aria-current="page">Person Management</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<ul class="list-group">
|
||||
(% for (person, _) in persons %)
|
||||
<li class="list-group-item d-flex flex-row justify-content-between">
|
||||
<div class="d-flex align-items-center">
|
||||
<a href="/ui/admin/person/(( person.uuid ))/view" hx-target="#main">(( person.name ))</a> <span class="text-secondary d-none d-lg-inline-block mx-4">(( person.uuid ))</span>
|
||||
</div>
|
||||
<div class="buttons float-end">
|
||||
</div>
|
||||
</li>
|
||||
(% endfor %)
|
||||
</ul>
|
||||
(% endblock %)
|
12
server/core/templates/admin/error_toast.html
Normal file
12
server/core/templates/admin/error_toast.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
<div class="toast-container position-fixed bottom-0 end-0 p-3">
|
||||
<div id="permissionDeniedToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="toast-header">
|
||||
<strong class="me-auto">Error</strong>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="toast-body">
|
||||
(( err_code )).<br>
|
||||
OpId: (( operation_id ))
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
11
server/core/templates/admin/saved_toast.html
Normal file
11
server/core/templates/admin/saved_toast.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
<div class="toast-container position-fixed bottom-0 end-0 p-3">
|
||||
<div id="savedToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="toast-header">
|
||||
<strong class="me-auto">Success</strong>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="toast-body">
|
||||
Saved.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -2,7 +2,9 @@
|
|||
|
||||
(% block body %)
|
||||
(% include "navbar.html" %)
|
||||
<div id="main">
|
||||
(% block main %)(% endblock %)
|
||||
</div>
|
||||
(% include "signout_modal.html" %)
|
||||
(% endblock %)
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<nav class="navbar navbar-expand-md kanidm_navbar mb-4">
|
||||
<nav hx-boost="false" class="navbar navbar-expand-md kanidm_navbar mb-4">
|
||||
<div class="container-lg">
|
||||
<a class="navbar-brand d-flex align-items-center" href="/ui/apps">
|
||||
(% if navbar_ctx.domain_info.image().is_some() %)
|
||||
|
@ -39,4 +39,4 @@
|
|||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</nav>
|
|
@ -244,13 +244,15 @@ impl SearchEvent {
|
|||
ident: &Identity,
|
||||
filter: Filter<FilterValid>,
|
||||
filter_orig: Filter<FilterValid>,
|
||||
attrs: Option<BTreeSet<Attribute>>,
|
||||
effective_access_check: bool,
|
||||
) -> Self {
|
||||
SearchEvent {
|
||||
ident: Identity::from_impersonate(ident),
|
||||
filter,
|
||||
filter_orig,
|
||||
attrs: None,
|
||||
effective_access_check: false,
|
||||
attrs,
|
||||
effective_access_check,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ impl IdmServerProxyReadTransaction<'_> {
|
|||
// _ext reduces the entries based on access.
|
||||
let oauth2_related = self
|
||||
.qs_read
|
||||
.impersonate_search_ext(f_executed, f_intent, ident)?;
|
||||
.impersonate_search_ext(f_executed, f_intent, ident, None, false)?;
|
||||
trace!(?oauth2_related);
|
||||
|
||||
// Aggregate results to a Vec of AppLink
|
||||
|
|
|
@ -492,7 +492,7 @@ pub trait QueryServerTransaction<'a> {
|
|||
f_intent_valid: Filter<FilterValid>,
|
||||
event: &Identity,
|
||||
) -> Result<Vec<Arc<EntrySealedCommitted>>, OperationError> {
|
||||
let se = SearchEvent::new_impersonate(event, f_valid, f_intent_valid);
|
||||
let se = SearchEvent::new_impersonate(event, f_valid, f_intent_valid, None, false);
|
||||
self.search(&se)
|
||||
}
|
||||
|
||||
|
@ -502,8 +502,10 @@ pub trait QueryServerTransaction<'a> {
|
|||
f_valid: Filter<FilterValid>,
|
||||
f_intent_valid: Filter<FilterValid>,
|
||||
event: &Identity,
|
||||
attrs: Option<BTreeSet<Attribute>>,
|
||||
acp: bool,
|
||||
) -> Result<Vec<Entry<EntryReduced, EntryCommitted>>, OperationError> {
|
||||
let se = SearchEvent::new_impersonate(event, f_valid, f_intent_valid);
|
||||
let se = SearchEvent::new_impersonate(event, f_valid, f_intent_valid, attrs, acp);
|
||||
self.search_ext(&se)
|
||||
}
|
||||
|
||||
|
@ -529,6 +531,8 @@ pub trait QueryServerTransaction<'a> {
|
|||
filter: Filter<FilterInvalid>,
|
||||
filter_intent: Filter<FilterInvalid>,
|
||||
event: &Identity,
|
||||
attrs: Option<BTreeSet<Attribute>>,
|
||||
acp: bool,
|
||||
) -> Result<Vec<Entry<EntryReduced, EntryCommitted>>, OperationError> {
|
||||
let f_valid = filter
|
||||
.validate(self.get_schema())
|
||||
|
@ -536,7 +540,7 @@ pub trait QueryServerTransaction<'a> {
|
|||
let f_intent_valid = filter_intent
|
||||
.validate(self.get_schema())
|
||||
.map_err(OperationError::SchemaViolation)?;
|
||||
self.impersonate_search_ext_valid(f_valid, f_intent_valid, event)
|
||||
self.impersonate_search_ext_valid(f_valid, f_intent_valid, event, attrs, acp)
|
||||
}
|
||||
|
||||
/// Get a single entry by its UUID. This is used heavily for internal
|
||||
|
@ -609,7 +613,7 @@ pub trait QueryServerTransaction<'a> {
|
|||
let filter_intent = filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)));
|
||||
let filter = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid)));
|
||||
|
||||
let mut vs = self.impersonate_search_ext(filter, filter_intent, event)?;
|
||||
let mut vs = self.impersonate_search_ext(filter, filter_intent, event, None, false)?;
|
||||
match vs.pop() {
|
||||
Some(entry) if vs.is_empty() => Ok(entry),
|
||||
_ => {
|
||||
|
|
Loading…
Reference in a new issue