Compare commits

...

4 commits

Author SHA1 Message Date
ChanceHarrison 31ac8d845a
Merge cc9433fbd4 into 38c260214b 2025-02-24 22:38:37 +03:00
dependabot[bot] 38c260214b
Bump the all group across 1 directory with 5 updates ()
Bumps the all group with 5 updates in the /pykanidm directory:

| Package | From | To |
| --- | --- | --- |
| [ruff](https://github.com/astral-sh/ruff) | `0.9.5` | `0.9.7` |
| [coverage](https://github.com/nedbat/coveragepy) | `7.6.11` | `7.6.12` |
| [mkdocs-material](https://github.com/squidfunk/mkdocs-material) | `9.6.3` | `9.6.5` |
| [mkdocstrings](https://github.com/mkdocstrings/mkdocstrings) | `0.28.0` | `0.28.1` |
| [mkdocstrings-python](https://github.com/mkdocstrings/python) | `1.14.6` | `1.16.1` |



Updates `ruff` from 0.9.5 to 0.9.7
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.9.5...0.9.7)

Updates `coverage` from 7.6.11 to 7.6.12
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.6.11...7.6.12)

Updates `mkdocs-material` from 9.6.3 to 9.6.5
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.6.3...9.6.5)

Updates `mkdocstrings` from 0.28.0 to 0.28.1
- [Release notes](https://github.com/mkdocstrings/mkdocstrings/releases)
- [Changelog](https://github.com/mkdocstrings/mkdocstrings/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mkdocstrings/mkdocstrings/compare/0.28.0...0.28.1)

Updates `mkdocstrings-python` from 1.14.6 to 1.16.1
- [Release notes](https://github.com/mkdocstrings/python/releases)
- [Changelog](https://github.com/mkdocstrings/python/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mkdocstrings/python/compare/1.14.6...1.16.1)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: coverage
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: mkdocs-material
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: mkdocstrings
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: mkdocstrings-python
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: all
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-24 13:22:51 +10:00
Merlijn 857dcf5087
[htmx] Admin ui for groups and users management ()
* Some progress on admin ui for managing groups and users
* Improve scim querying

---------

Co-authored-by: William Brown <william@blackhats.net.au>
2025-02-22 13:43:54 +10:00
Chance Harrison cc9433fbd4
docs(faq): Discuss options for TLS between LB and kanidm 2024-04-28 22:58:00 -07:00
30 changed files with 1133 additions and 200 deletions

159
Cargo.lock generated
View file

@ -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"
@ -3085,7 +3094,7 @@ dependencies = [
"kanidmd_core",
"kanidmd_testkit",
"libc",
"lru",
"lru 0.12.5",
"mimalloc",
"notify-debouncer-full",
"prctl",
@ -3467,9 +3476,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 +3489,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 +3585,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 +3640,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 +3951,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 +3983,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 +4385,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 +4395,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 +4478,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 +4531,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 +4683,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 +4731,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 +4849,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 +5034,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 +5072,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 +5083,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 +5241,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 +5444,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 +5581,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 +5591,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 +5722,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 +5956,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 +5974,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 +6086,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",

View file

@ -52,6 +52,19 @@ configured.
Similarly, WebAuthn and its various other names like Passkeys, FIDO2 or "scan the QR code to log in"
will [only work over TLS](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API).
There are a variety of ways that you can configure TLS between your load balancer and Kanidm.
Ultimately, any option that maintains the confidentiality and integrity of the communication will
suffice. Some options include, but are not limited to:
- Generating a self-signed certificate
- Utilize certificate pinning to ensure that the load balancer only trusts connections made with
that particular certificate
- Not terminating TLS / TLS passthrough / TCP proxy
- Running your own certificate authority (CA)
The "best" option for you will depend on a number of factors, including your threat model and the
specifc load balancer you are using.
## OAuth2
[RFC6819 - OAuth2 Threat Model and Security Considerations](https://www.rfc-editor.org/rfc/rfc6819)

View file

@ -631,6 +631,71 @@ impl From<Attribute> for String {
}
}
/// Sub attributes are a component of SCIM, allowing tagged sub properties of a complex
/// attribute to be accessed.
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
#[serde(rename_all = "lowercase", try_from = "&str", into = "AttrString")]
pub enum SubAttribute {
/// Denotes a primary value.
Primary,
#[cfg(not(test))]
Custom(AttrString),
}
impl From<SubAttribute> for AttrString {
fn from(val: SubAttribute) -> Self {
AttrString::from(val.as_str())
}
}
impl From<&str> for SubAttribute {
fn from(value: &str) -> Self {
Self::inner_from_str(value)
}
}
impl FromStr for SubAttribute {
type Err = Infallible;
fn from_str(value: &str) -> Result<Self, Self::Err> {
Ok(Self::inner_from_str(value))
}
}
impl SubAttribute {
pub fn as_str(&self) -> &str {
match self {
SubAttribute::Primary => SUB_ATTR_PRIMARY,
#[cfg(not(test))]
SubAttribute::Custom(s) => s,
}
}
// We allow this because the standard lib from_str is fallible, and we want an infallible version.
#[allow(clippy::should_implement_trait)]
fn inner_from_str(value: &str) -> Self {
// Could this be something like heapless to save allocations? Also gives a way
// to limit length of str?
match value.to_lowercase().as_str() {
SUB_ATTR_PRIMARY => SubAttribute::Primary,
#[cfg(not(test))]
_ => SubAttribute::Custom(AttrString::from(value)),
// Allowed only in tests
#[allow(clippy::unreachable)]
#[cfg(test)]
_ => {
unreachable!(
"Check that you've implemented the SubAttribute conversion for {:?}",
value
);
}
}
}
}
#[cfg(test)]
mod test {
use super::Attribute;

View file

@ -220,6 +220,8 @@ pub const ATTR_VERSION: &str = "version";
pub const ATTR_WEBAUTHN_ATTESTATION_CA_LIST: &str = "webauthn_attestation_ca_list";
pub const ATTR_ALLOW_PRIMARY_CRED_FALLBACK: &str = "allow_primary_cred_fallback";
pub const SUB_ATTR_PRIMARY: &str = "primary";
pub const OAUTH2_SCOPE_EMAIL: &str = ATTR_EMAIL;
pub const OAUTH2_SCOPE_GROUPS: &str = "groups";
pub const OAUTH2_SCOPE_SSH_PUBLICKEYS: &str = "ssh_publickeys";

View file

@ -1,7 +1,7 @@
//! These are types that a client will send to the server.
use super::ScimEntryGetQuery;
use super::ScimOauth2ClaimMapJoinChar;
use crate::attribute::Attribute;
use crate::attribute::{Attribute, SubAttribute};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use serde_with::formats::PreferMany;
@ -134,3 +134,59 @@ impl TryFrom<ScimEntryPutKanidm> for ScimEntryPutGeneric {
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct AttrPath {
pub a: Attribute,
pub s: Option<SubAttribute>,
}
impl From<Attribute> for AttrPath {
fn from(a: Attribute) -> Self {
Self { a, s: None }
}
}
impl From<(Attribute, SubAttribute)> for AttrPath {
fn from((a, s): (Attribute, SubAttribute)) -> Self {
Self { a, s: Some(s) }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub enum ScimFilter {
Or(Box<ScimFilter>, Box<ScimFilter>),
And(Box<ScimFilter>, Box<ScimFilter>),
Not(Box<ScimFilter>),
Present(AttrPath),
Equal(AttrPath, JsonValue),
NotEqual(AttrPath, JsonValue),
Contains(AttrPath, JsonValue),
StartsWith(AttrPath, JsonValue),
EndsWith(AttrPath, JsonValue),
Greater(AttrPath, JsonValue),
Less(AttrPath, JsonValue),
GreaterOrEqual(AttrPath, JsonValue),
LessOrEqual(AttrPath, JsonValue),
Complex(Attribute, Box<ScimComplexFilter>),
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub enum ScimComplexFilter {
Or(Box<ScimComplexFilter>, Box<ScimComplexFilter>),
And(Box<ScimComplexFilter>, Box<ScimComplexFilter>),
Not(Box<ScimComplexFilter>),
Present(SubAttribute),
Equal(SubAttribute, JsonValue),
NotEqual(SubAttribute, JsonValue),
Contains(SubAttribute, JsonValue),
StartsWith(SubAttribute, JsonValue),
EndsWith(SubAttribute, JsonValue),
Greater(SubAttribute, JsonValue),
Less(SubAttribute, JsonValue),
GreaterOrEqual(SubAttribute, JsonValue),
LessOrEqual(SubAttribute, JsonValue),
}

View file

@ -257,6 +257,98 @@ pub enum ScimValueKanidm {
UiHints(Vec<UiHint>),
}
#[serde_as]
#[derive(Serialize, Debug, Clone, ToSchema)]
pub struct ScimPerson {
pub uuid: Uuid,
pub name: String,
pub displayname: 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 uuid = scim_entry.header.id;
let name = scim_entry
.attrs
.get(&Attribute::Name)
.and_then(|v| match v {
ScimValueKanidm::String(s) => Some(s.clone()),
_ => None,
})
.ok_or(())?;
let displayname = scim_entry
.attrs
.get(&Attribute::DisplayName)
.and_then(|v| match v {
ScimValueKanidm::String(s) => Some(s.clone()),
_ => None,
})
.ok_or(())?;
let spn = scim_entry
.attrs
.get(&Attribute::Spn)
.and_then(|v| match v {
ScimValueKanidm::String(s) => Some(s.clone()),
_ => None,
})
.ok_or(())?;
let description = scim_entry
.attrs
.get(&Attribute::Description)
.and_then(|v| match v {
ScimValueKanidm::String(s) => Some(s.clone()),
_ => None,
});
let mails = scim_entry
.attrs
.get(&Attribute::Mail)
.and_then(|v| match v {
ScimValueKanidm::Mail(m) => Some(m.clone()),
_ => None,
})
.unwrap_or_default();
let groups = scim_entry
.attrs
.get(&Attribute::DirectMemberOf)
.and_then(|v| match v {
ScimValueKanidm::EntryReferences(v) => Some(v.clone()),
_ => None,
})
.unwrap_or_default();
let managed_by = scim_entry
.attrs
.get(&Attribute::EntryManagedBy)
.and_then(|v| match v {
ScimValueKanidm::EntryReference(v) => Some(v.clone()),
_ => None,
});
Ok(ScimPerson {
uuid,
name,
displayname,
spn,
description,
mails,
managed_by,
groups,
})
}
}
impl From<bool> for ScimValueKanidm {
fn from(b: bool) -> Self {
Self::Bool(b)

View file

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

180
pykanidm/poetry.lock generated
View file

@ -427,68 +427,74 @@ files = [
[[package]]
name = "coverage"
version = "7.6.11"
version = "7.6.12"
description = "Code coverage measurement for Python"
optional = false
python-versions = ">=3.9"
files = [
{file = "coverage-7.6.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eafea49da254a8289bed3fab960f808b322eda5577cb17a3733014928bbfbebd"},
{file = "coverage-7.6.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5a3f7cbbcb4ad95067a6525f83a6fc78d9cbc1e70f8abaeeaeaa72ef34f48fc3"},
{file = "coverage-7.6.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de6b079b39246a7da9a40cfa62d5766bd52b4b7a88cf5a82ec4c45bf6e152306"},
{file = "coverage-7.6.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:60d4ad09dfc8c36c4910685faafcb8044c84e4dae302e86c585b3e2e7778726c"},
{file = "coverage-7.6.11-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e433b6e3a834a43dae2889adc125f3fa4c66668df420d8e49bc4ee817dd7a70"},
{file = "coverage-7.6.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ac5d92e2cc121a13270697e4cb37e1eb4511ac01d23fe1b6c097facc3b46489e"},
{file = "coverage-7.6.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5128f3ba694c0a1bde55fc480090392c336236c3e1a10dad40dc1ab17c7675ff"},
{file = "coverage-7.6.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:397489c611b76302dfa1d9ea079e138dddc4af80fc6819d5f5119ec8ca6c0e47"},
{file = "coverage-7.6.11-cp310-cp310-win32.whl", hash = "sha256:c7719a5e1dc93883a6b319bc0374ecd46fb6091ed659f3fbe281ab991634b9b0"},
{file = "coverage-7.6.11-cp310-cp310-win_amd64.whl", hash = "sha256:c27df03730059118b8a923cfc8b84b7e9976742560af528242f201880879c1da"},
{file = "coverage-7.6.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:532fe139691af134aa8b54ed60dd3c806aa81312d93693bd2883c7b61592c840"},
{file = "coverage-7.6.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0b0f272901a5172090c0802053fbc503cdc3fa2612720d2669a98a7384a7bec"},
{file = "coverage-7.6.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4bda710139ea646890d1c000feb533caff86904a0e0638f85e967c28cb8eec50"},
{file = "coverage-7.6.11-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a165b09e7d5f685bf659063334a9a7b1a2d57b531753d3e04bd442b3cfe5845b"},
{file = "coverage-7.6.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ff136607689c1c87f43d24203b6d2055b42030f352d5176f9c8b204d4235ef27"},
{file = "coverage-7.6.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:050172741de03525290e67f0161ae5f7f387c88fca50d47fceb4724ceaa591d2"},
{file = "coverage-7.6.11-cp311-cp311-win32.whl", hash = "sha256:27700d859be68e4fb2e7bf774cf49933dcac6f81a9bc4c13bd41735b8d26a53b"},
{file = "coverage-7.6.11-cp311-cp311-win_amd64.whl", hash = "sha256:cd4839813b09ab1dd1be1bbc74f9a7787615f931f83952b6a9af1b2d3f708bf7"},
{file = "coverage-7.6.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:dbb1a822fd858d9853333a7c95d4e70dde9a79e65893138ce32c2ec6457d7a36"},
{file = "coverage-7.6.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:61c834cbb80946d6ebfddd9b393a4c46bec92fcc0fa069321fcb8049117f76ea"},
{file = "coverage-7.6.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a46d56e99a31d858d6912d31ffa4ede6a325c86af13139539beefca10a1234ce"},
{file = "coverage-7.6.11-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b48db06f53d1864fea6dbd855e6d51d41c0f06c212c3004511c0bdc6847b297"},
{file = "coverage-7.6.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b6ff5be3b1853e0862da9d349fe87f869f68e63a25f7c37ce1130b321140f963"},
{file = "coverage-7.6.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be05bde21d5e6eefbc3a6de6b9bee2b47894b8945342e8663192809c4d1f08ce"},
{file = "coverage-7.6.11-cp312-cp312-win32.whl", hash = "sha256:e3b746fa0ffc5b6b8856529de487da8b9aeb4fb394bb58de6502ef45f3434f12"},
{file = "coverage-7.6.11-cp312-cp312-win_amd64.whl", hash = "sha256:ac476e6d0128fb7919b3fae726de72b28b5c9644cb4b579e4a523d693187c551"},
{file = "coverage-7.6.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c86f4c7a6d1a54a24d804d9684d96e36a62d3ef7c0d7745ae2ea39e3e0293251"},
{file = "coverage-7.6.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7eb0504bb307401fd08bc5163a351df301438b3beb88a4fa044681295bbefc67"},
{file = "coverage-7.6.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca95d40900cf614e07f00cee8c2fad0371df03ca4d7a80161d84be2ec132b7a4"},
{file = "coverage-7.6.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db4b1a69976b1b02acda15937538a1d3fe10b185f9d99920b17a740a0a102e06"},
{file = "coverage-7.6.11-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf96beb05d004e4c51cd846fcdf9eee9eb2681518524b66b2e7610507944c2f"},
{file = "coverage-7.6.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:08e5fb93576a6b054d3d326242af5ef93daaac9bb52bc25f12ccbc3fa94227cd"},
{file = "coverage-7.6.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:25575cd5a7d2acc46b42711e8aff826027c0e4f80fb38028a74f31ac22aae69d"},
{file = "coverage-7.6.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8fa4fffd90ee92f62ff7404b4801b59e8ea8502e19c9bf2d3241ce745b52926c"},
{file = "coverage-7.6.11-cp313-cp313-win32.whl", hash = "sha256:0d03c9452d9d1ccfe5d3a5df0427705022a49b356ac212d529762eaea5ef97b4"},
{file = "coverage-7.6.11-cp313-cp313-win_amd64.whl", hash = "sha256:fd2fffc8ce8692ce540103dff26279d2af22d424516ddebe2d7e4d6dbb3816b2"},
{file = "coverage-7.6.11-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:5e7ac966ab110bd94ee844f2643f196d78fde1cd2450399116d3efdd706e19f5"},
{file = "coverage-7.6.11-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6ba27a0375c5ef4d2a7712f829265102decd5ff78b96d342ac2fa555742c4f4f"},
{file = "coverage-7.6.11-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2778be4f574b39ec9dcd9e5e13644f770351ee0990a0ecd27e364aba95af89b"},
{file = "coverage-7.6.11-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5edc16712187139ab635a2e644cc41fc239bc6d245b16124045743130455c652"},
{file = "coverage-7.6.11-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df6ff122a0a10a30121d9f0cb3fbd03a6fe05861e4ec47adb9f25e9245aabc19"},
{file = "coverage-7.6.11-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ff562952f15eff27247a4c4b03e45ce8a82e3fb197de6a7c54080f9d4ba07845"},
{file = "coverage-7.6.11-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:4f21e3617f48d683f30cf2a6c8b739c838e600cb1454fe6b2eb486ac2bce8fbd"},
{file = "coverage-7.6.11-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6d60577673ba48d8ae8e362e61fd4ad1a640293ffe8991d11c86f195479100b7"},
{file = "coverage-7.6.11-cp313-cp313t-win32.whl", hash = "sha256:13100f98497086b359bf56fc035a762c674de8ef526daa389ac8932cb9bff1e0"},
{file = "coverage-7.6.11-cp313-cp313t-win_amd64.whl", hash = "sha256:2c81e53782043b323bd34c7de711ed9b4673414eb517eaf35af92185b873839c"},
{file = "coverage-7.6.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ff52b4e2ac0080c96e506819586c4b16cdbf46724bda90d308a7330a73cc8521"},
{file = "coverage-7.6.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f4679fcc9eb9004fdd1b00231ef1ec7167168071bebc4d66327e28c1979b4449"},
{file = "coverage-7.6.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90de4e9ca4489e823138bd13098af9ac8028cc029f33f60098b5c08c675c7bda"},
{file = "coverage-7.6.11-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c96a142057d83ee993eaf71629ca3fb952cda8afa9a70af4132950c2bd3deb9"},
{file = "coverage-7.6.11-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:476f29a258b9cd153f2be5bf5f119d670d2806363595263917bddc167d6e5cce"},
{file = "coverage-7.6.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:09d03f48d9025b8a6a116cddcb6c7b8ce80e4fb4c31dd2e124a7c377036ad58e"},
{file = "coverage-7.6.11-cp39-cp39-win32.whl", hash = "sha256:bb35ae9f134fbd9cf7302a9654d5a1e597c974202678082dcc569eb39a8cde03"},
{file = "coverage-7.6.11-cp39-cp39-win_amd64.whl", hash = "sha256:f382004fa4c93c01016d9226b9d696a08c53f6818b7ad59b4e96cb67e863353a"},
{file = "coverage-7.6.11-pp39.pp310-none-any.whl", hash = "sha256:adc2d941c0381edfcf3897f94b9f41b1e504902fab78a04b1677f2f72afead4b"},
{file = "coverage-7.6.11-py3-none-any.whl", hash = "sha256:f0f334ae844675420164175bf32b04e18a81fe57ad8eb7e0cfd4689d681ffed7"},
{file = "coverage-7.6.11.tar.gz", hash = "sha256:e642e6a46a04e992ebfdabed79e46f478ec60e2c528e1e1a074d63800eda4286"},
{file = "coverage-7.6.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:704c8c8c6ce6569286ae9622e534b4f5b9759b6f2cd643f1c1a61f666d534fe8"},
{file = "coverage-7.6.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ad7525bf0241e5502168ae9c643a2f6c219fa0a283001cee4cf23a9b7da75879"},
{file = "coverage-7.6.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06097c7abfa611c91edb9e6920264e5be1d6ceb374efb4986f38b09eed4cb2fe"},
{file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:220fa6c0ad7d9caef57f2c8771918324563ef0d8272c94974717c3909664e674"},
{file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3688b99604a24492bcfe1c106278c45586eb819bf66a654d8a9a1433022fb2eb"},
{file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d1a987778b9c71da2fc8948e6f2656da6ef68f59298b7e9786849634c35d2c3c"},
{file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cec6b9ce3bd2b7853d4a4563801292bfee40b030c05a3d29555fd2a8ee9bd68c"},
{file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ace9048de91293e467b44bce0f0381345078389814ff6e18dbac8fdbf896360e"},
{file = "coverage-7.6.12-cp310-cp310-win32.whl", hash = "sha256:ea31689f05043d520113e0552f039603c4dd71fa4c287b64cb3606140c66f425"},
{file = "coverage-7.6.12-cp310-cp310-win_amd64.whl", hash = "sha256:676f92141e3c5492d2a1596d52287d0d963df21bf5e55c8b03075a60e1ddf8aa"},
{file = "coverage-7.6.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e18aafdfb3e9ec0d261c942d35bd7c28d031c5855dadb491d2723ba54f4c3015"},
{file = "coverage-7.6.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66fe626fd7aa5982cdebad23e49e78ef7dbb3e3c2a5960a2b53632f1f703ea45"},
{file = "coverage-7.6.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ef01d70198431719af0b1f5dcbefc557d44a190e749004042927b2a3fed0702"},
{file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e92ae5a289a4bc4c0aae710c0948d3c7892e20fd3588224ebe242039573bf0"},
{file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e695df2c58ce526eeab11a2e915448d3eb76f75dffe338ea613c1201b33bab2f"},
{file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d74c08e9aaef995f8c4ef6d202dbd219c318450fe2a76da624f2ebb9c8ec5d9f"},
{file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e995b3b76ccedc27fe4f477b349b7d64597e53a43fc2961db9d3fbace085d69d"},
{file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b1f097878d74fe51e1ddd1be62d8e3682748875b461232cf4b52ddc6e6db0bba"},
{file = "coverage-7.6.12-cp311-cp311-win32.whl", hash = "sha256:1f7ffa05da41754e20512202c866d0ebfc440bba3b0ed15133070e20bf5aeb5f"},
{file = "coverage-7.6.12-cp311-cp311-win_amd64.whl", hash = "sha256:e216c5c45f89ef8971373fd1c5d8d1164b81f7f5f06bbf23c37e7908d19e8558"},
{file = "coverage-7.6.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b172f8e030e8ef247b3104902cc671e20df80163b60a203653150d2fc204d1ad"},
{file = "coverage-7.6.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:641dfe0ab73deb7069fb972d4d9725bf11c239c309ce694dd50b1473c0f641c3"},
{file = "coverage-7.6.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e549f54ac5f301e8e04c569dfdb907f7be71b06b88b5063ce9d6953d2d58574"},
{file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959244a17184515f8c52dcb65fb662808767c0bd233c1d8a166e7cf74c9ea985"},
{file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bda1c5f347550c359f841d6614fb8ca42ae5cb0b74d39f8a1e204815ebe25750"},
{file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ceeb90c3eda1f2d8c4c578c14167dbd8c674ecd7d38e45647543f19839dd6ea"},
{file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f16f44025c06792e0fb09571ae454bcc7a3ec75eeb3c36b025eccf501b1a4c3"},
{file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b076e625396e787448d27a411aefff867db2bffac8ed04e8f7056b07024eed5a"},
{file = "coverage-7.6.12-cp312-cp312-win32.whl", hash = "sha256:00b2086892cf06c7c2d74983c9595dc511acca00665480b3ddff749ec4fb2a95"},
{file = "coverage-7.6.12-cp312-cp312-win_amd64.whl", hash = "sha256:7ae6eabf519bc7871ce117fb18bf14e0e343eeb96c377667e3e5dd12095e0288"},
{file = "coverage-7.6.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:488c27b3db0ebee97a830e6b5a3ea930c4a6e2c07f27a5e67e1b3532e76b9ef1"},
{file = "coverage-7.6.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d1095bbee1851269f79fd8e0c9b5544e4c00c0c24965e66d8cba2eb5bb535fd"},
{file = "coverage-7.6.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0533adc29adf6a69c1baa88c3d7dbcaadcffa21afbed3ca7a225a440e4744bf9"},
{file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53c56358d470fa507a2b6e67a68fd002364d23c83741dbc4c2e0680d80ca227e"},
{file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64cbb1a3027c79ca6310bf101014614f6e6e18c226474606cf725238cf5bc2d4"},
{file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79cac3390bfa9836bb795be377395f28410811c9066bc4eefd8015258a7578c6"},
{file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b148068e881faa26d878ff63e79650e208e95cf1c22bd3f77c3ca7b1d9821a3"},
{file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8bec2ac5da793c2685ce5319ca9bcf4eee683b8a1679051f8e6ec04c4f2fd7dc"},
{file = "coverage-7.6.12-cp313-cp313-win32.whl", hash = "sha256:200e10beb6ddd7c3ded322a4186313d5ca9e63e33d8fab4faa67ef46d3460af3"},
{file = "coverage-7.6.12-cp313-cp313-win_amd64.whl", hash = "sha256:2b996819ced9f7dbb812c701485d58f261bef08f9b85304d41219b1496b591ef"},
{file = "coverage-7.6.12-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:299cf973a7abff87a30609879c10df0b3bfc33d021e1adabc29138a48888841e"},
{file = "coverage-7.6.12-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4b467a8c56974bf06e543e69ad803c6865249d7a5ccf6980457ed2bc50312703"},
{file = "coverage-7.6.12-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2458f275944db8129f95d91aee32c828a408481ecde3b30af31d552c2ce284a0"},
{file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a9d8be07fb0832636a0f72b80d2a652fe665e80e720301fb22b191c3434d924"},
{file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d47376a4f445e9743f6c83291e60adb1b127607a3618e3185bbc8091f0467b"},
{file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b95574d06aa9d2bd6e5cc35a5bbe35696342c96760b69dc4287dbd5abd4ad51d"},
{file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ecea0c38c9079570163d663c0433a9af4094a60aafdca491c6a3d248c7432827"},
{file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2251fabcfee0a55a8578a9d29cecfee5f2de02f11530e7d5c5a05859aa85aee9"},
{file = "coverage-7.6.12-cp313-cp313t-win32.whl", hash = "sha256:eb5507795caabd9b2ae3f1adc95f67b1104971c22c624bb354232d65c4fc90b3"},
{file = "coverage-7.6.12-cp313-cp313t-win_amd64.whl", hash = "sha256:f60a297c3987c6c02ffb29effc70eadcbb412fe76947d394a1091a3615948e2f"},
{file = "coverage-7.6.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e7575ab65ca8399c8c4f9a7d61bbd2d204c8b8e447aab9d355682205c9dd948d"},
{file = "coverage-7.6.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8161d9fbc7e9fe2326de89cd0abb9f3599bccc1287db0aba285cb68d204ce929"},
{file = "coverage-7.6.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a1e465f398c713f1b212400b4e79a09829cd42aebd360362cd89c5bdc44eb87"},
{file = "coverage-7.6.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f25d8b92a4e31ff1bd873654ec367ae811b3a943583e05432ea29264782dc32c"},
{file = "coverage-7.6.12-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a936309a65cc5ca80fa9f20a442ff9e2d06927ec9a4f54bcba9c14c066323f2"},
{file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aa6f302a3a0b5f240ee201297fff0bbfe2fa0d415a94aeb257d8b461032389bd"},
{file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f973643ef532d4f9be71dd88cf7588936685fdb576d93a79fe9f65bc337d9d73"},
{file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:78f5243bb6b1060aed6213d5107744c19f9571ec76d54c99cc15938eb69e0e86"},
{file = "coverage-7.6.12-cp39-cp39-win32.whl", hash = "sha256:69e62c5034291c845fc4df7f8155e8544178b6c774f97a99e2734b05eb5bed31"},
{file = "coverage-7.6.12-cp39-cp39-win_amd64.whl", hash = "sha256:b01a840ecc25dce235ae4c1b6a0daefb2a203dba0e6e980637ee9c2f6ee0df57"},
{file = "coverage-7.6.12-pp39.pp310-none-any.whl", hash = "sha256:7e39e845c4d764208e7b8f6a21c541ade741e2c41afabdfa1caa28687a3c98cf"},
{file = "coverage-7.6.12-py3-none-any.whl", hash = "sha256:eb8668cfbc279a536c633137deeb9435d2962caec279c3f8cf8b91fff6ff8953"},
{file = "coverage-7.6.12.tar.gz", hash = "sha256:48cfc4641d95d34766ad41d9573cc0f22a48aa88d22657a1fe01dca0dbae4de2"},
]
[package.extras]
@ -986,13 +992,13 @@ pyyaml = ">=5.1"
[[package]]
name = "mkdocs-material"
version = "9.6.3"
version = "9.6.5"
description = "Documentation that simply works"
optional = false
python-versions = ">=3.8"
files = [
{file = "mkdocs_material-9.6.3-py3-none-any.whl", hash = "sha256:1125622067e26940806701219303b27c0933e04533560725d97ec26fd16a39cf"},
{file = "mkdocs_material-9.6.3.tar.gz", hash = "sha256:c87f7d1c39ce6326da5e10e232aed51bae46252e646755900f4b0fc9192fa832"},
{file = "mkdocs_material-9.6.5-py3-none-any.whl", hash = "sha256:aad3e6fb860c20870f75fb2a69ef901f1be727891e41adb60b753efcae19453b"},
{file = "mkdocs_material-9.6.5.tar.gz", hash = "sha256:b714679a8c91b0ffe2188e11ed58c44d2523e9c2ae26a29cc652fa7478faa21f"},
]
[package.dependencies]
@ -1026,13 +1032,13 @@ files = [
[[package]]
name = "mkdocstrings"
version = "0.28.0"
version = "0.28.1"
description = "Automatic documentation from sources, for MkDocs."
optional = false
python-versions = ">=3.9"
files = [
{file = "mkdocstrings-0.28.0-py3-none-any.whl", hash = "sha256:84cf3dc910614781fe0fee46ce8006fde7df6cc7cca2e3f799895fb8a9170b39"},
{file = "mkdocstrings-0.28.0.tar.gz", hash = "sha256:df20afef1eafe36ba466ae20732509ecb74237653a585f5061937e54b553b4e0"},
{file = "mkdocstrings-0.28.1-py3-none-any.whl", hash = "sha256:a5878ae5cd1e26f491ff084c1f9ab995687d52d39a5c558e9b7023d0e4e0b740"},
{file = "mkdocstrings-0.28.1.tar.gz", hash = "sha256:fb64576906771b7701e8e962fd90073650ff689e95eb86e86751a66d65ab4489"},
]
[package.dependencies]
@ -1053,13 +1059,13 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"]
[[package]]
name = "mkdocstrings-python"
version = "1.14.6"
version = "1.16.1"
description = "A Python handler for mkdocstrings."
optional = false
python-versions = ">=3.9"
files = [
{file = "mkdocstrings_python-1.14.6-py3-none-any.whl", hash = "sha256:e0ca11b49ac0f23070afb566245f4ff80ea1700e03c9dbc143d70dbf1cae074e"},
{file = "mkdocstrings_python-1.14.6.tar.gz", hash = "sha256:3fb6589491614422d781dacca085c0c5a53a7063af37a2fea5864e24e378b03e"},
{file = "mkdocstrings_python-1.16.1-py3-none-any.whl", hash = "sha256:b88ff6fc6a293cee9cb42313f1cba37a2c5cdf37bcc60b241ec7ab66b5d41b58"},
{file = "mkdocstrings_python-1.16.1.tar.gz", hash = "sha256:d7152d17da74d3616a0f17df5d2da771ecf7340518c158650e5a64a0a95973f4"},
]
[package.dependencies]
@ -2074,29 +2080,29 @@ files = [
[[package]]
name = "ruff"
version = "0.9.5"
version = "0.9.7"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
{file = "ruff-0.9.5-py3-none-linux_armv6l.whl", hash = "sha256:d466d2abc05f39018d53f681fa1c0ffe9570e6d73cde1b65d23bb557c846f442"},
{file = "ruff-0.9.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:38840dbcef63948657fa7605ca363194d2fe8c26ce8f9ae12eee7f098c85ac8a"},
{file = "ruff-0.9.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d56ba06da53536b575fbd2b56517f6f95774ff7be0f62c80b9e67430391eeb36"},
{file = "ruff-0.9.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f7cb2a01da08244c50b20ccfaeb5972e4228c3c3a1989d3ece2bc4b1f996001"},
{file = "ruff-0.9.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:96d5c76358419bc63a671caac70c18732d4fd0341646ecd01641ddda5c39ca0b"},
{file = "ruff-0.9.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:deb8304636ed394211f3a6d46c0e7d9535b016f53adaa8340139859b2359a070"},
{file = "ruff-0.9.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:df455000bf59e62b3e8c7ba5ed88a4a2bc64896f900f311dc23ff2dc38156440"},
{file = "ruff-0.9.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de92170dfa50c32a2b8206a647949590e752aca8100a0f6b8cefa02ae29dce80"},
{file = "ruff-0.9.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d28532d73b1f3f627ba88e1456f50748b37f3a345d2be76e4c653bec6c3e393"},
{file = "ruff-0.9.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c746d7d1df64f31d90503ece5cc34d7007c06751a7a3bbeee10e5f2463d52d2"},
{file = "ruff-0.9.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:11417521d6f2d121fda376f0d2169fb529976c544d653d1d6044f4c5562516ee"},
{file = "ruff-0.9.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5b9d71c3879eb32de700f2f6fac3d46566f644a91d3130119a6378f9312a38e1"},
{file = "ruff-0.9.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2e36c61145e70febcb78483903c43444c6b9d40f6d2f800b5552fec6e4a7bb9a"},
{file = "ruff-0.9.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:2f71d09aeba026c922aa7aa19a08d7bd27c867aedb2f74285a2639644c1c12f5"},
{file = "ruff-0.9.5-py3-none-win32.whl", hash = "sha256:134f958d52aa6fdec3b294b8ebe2320a950d10c041473c4316d2e7d7c2544723"},
{file = "ruff-0.9.5-py3-none-win_amd64.whl", hash = "sha256:78cc6067f6d80b6745b67498fb84e87d32c6fc34992b52bffefbdae3442967d6"},
{file = "ruff-0.9.5-py3-none-win_arm64.whl", hash = "sha256:18a29f1a005bddb229e580795627d297dfa99f16b30c7039e73278cf6b5f9fa9"},
{file = "ruff-0.9.5.tar.gz", hash = "sha256:11aecd7a633932875ab3cb05a484c99970b9d52606ce9ea912b690b02653d56c"},
{file = "ruff-0.9.7-py3-none-linux_armv6l.whl", hash = "sha256:99d50def47305fe6f233eb8dabfd60047578ca87c9dcb235c9723ab1175180f4"},
{file = "ruff-0.9.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d59105ae9c44152c3d40a9c40d6331a7acd1cdf5ef404fbe31178a77b174ea66"},
{file = "ruff-0.9.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f313b5800483770bd540cddac7c90fc46f895f427b7820f18fe1822697f1fec9"},
{file = "ruff-0.9.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:042ae32b41343888f59c0a4148f103208bf6b21c90118d51dc93a68366f4e903"},
{file = "ruff-0.9.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87862589373b33cc484b10831004e5e5ec47dc10d2b41ba770e837d4f429d721"},
{file = "ruff-0.9.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a17e1e01bee0926d351a1ee9bc15c445beae888f90069a6192a07a84af544b6b"},
{file = "ruff-0.9.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7c1f880ac5b2cbebd58b8ebde57069a374865c73f3bf41f05fe7a179c1c8ef22"},
{file = "ruff-0.9.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e63fc20143c291cab2841dbb8260e96bafbe1ba13fd3d60d28be2c71e312da49"},
{file = "ruff-0.9.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91ff963baed3e9a6a4eba2a02f4ca8eaa6eba1cc0521aec0987da8d62f53cbef"},
{file = "ruff-0.9.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88362e3227c82f63eaebf0b2eff5b88990280fb1ecf7105523883ba8c3aaf6fb"},
{file = "ruff-0.9.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0372c5a90349f00212270421fe91874b866fd3626eb3b397ede06cd385f6f7e0"},
{file = "ruff-0.9.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d76b8ab60e99e6424cd9d3d923274a1324aefce04f8ea537136b8398bbae0a62"},
{file = "ruff-0.9.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0c439bdfc8983e1336577f00e09a4e7a78944fe01e4ea7fe616d00c3ec69a3d0"},
{file = "ruff-0.9.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:115d1f15e8fdd445a7b4dc9a30abae22de3f6bcabeb503964904471691ef7606"},
{file = "ruff-0.9.7-py3-none-win32.whl", hash = "sha256:e9ece95b7de5923cbf38893f066ed2872be2f2f477ba94f826c8defdd6ec6b7d"},
{file = "ruff-0.9.7-py3-none-win_amd64.whl", hash = "sha256:3770fe52b9d691a15f0b87ada29c45324b2ace8f01200fb0c14845e499eb0c2c"},
{file = "ruff-0.9.7-py3-none-win_arm64.whl", hash = "sha256:b075a700b2533feb7a01130ff656a4ec0d5f340bb540ad98759b8401c32c2037"},
{file = "ruff-0.9.7.tar.gz", hash = "sha256:643757633417907510157b206e490c3aa11cab0c087c912f60e07fbafa87a4c6"},
]
[[package]]
@ -2367,4 +2373,4 @@ type = ["pytest-mypy"]
[metadata]
lock-version = "2.0"
python-versions = "^3.9"
content-hash = "f614f512e5cfcf28eb72369010b689568f2736563e498bd6cf252292eb8bf08c"
content-hash = "72e84e903994ae033467c589c6506cc54fc9c3075df75795b4d0f748ff91dfeb"

View file

@ -29,7 +29,7 @@ Authlib = "^1.2.0"
[tool.poetry.group.dev.dependencies]
ruff = ">=0.5.1,<0.9.6"
ruff = ">=0.5.1,<0.9.8"
pytest = "^8.3.4"
mypy = "^1.14.1"
types-requests = "^2.32.0.20241016"

View file

@ -1,6 +1,6 @@
use super::{QueryServerReadV1, QueryServerWriteV1};
use kanidm_proto::scim_v1::{
server::ScimEntryKanidm, ScimEntryGetQuery, ScimSyncRequest, ScimSyncState,
client::ScimFilter, server::ScimEntryKanidm, ScimEntryGetQuery, ScimSyncRequest, ScimSyncState,
};
use kanidmd_lib::idm::scim::{
GenerateScimSyncTokenEvent, ScimSyncFinaliseEvent, ScimSyncTerminateEvent, ScimSyncUpdateEvent,
@ -229,4 +229,27 @@ 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,
eventid: Uuid,
filter: ScimFilter,
query: ScimEntryGetQuery,
) -> 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");
})?;
idms_prox_read.qs_read.scim_search_ext(ident, filter, query)
}
}

View 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)
}

View file

@ -0,0 +1,193 @@
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::client::ScimFilter;
use kanidm_proto::scim_v1::server::{ScimEffectiveAccess, ScimEntryKanidm, ScimPerson};
use kanidm_proto::scim_v1::ScimEntryGetQuery;
use kanidmd_lib::constants::EntryClass;
use kanidmd_lib::idm::server::DomainInfoRead;
use kanidmd_lib::idm::ClientAuthInfo;
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 };
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 = ScimFilter::Equal(Attribute::Class.into(), EntryClass::Person.into());
let base: Vec<ScimEntryKanidm> = state
.qe_r_ref
.scim_entry_search(
client_auth_info.clone(),
kopid.eventid,
filter,
ScimEntryGetQuery {
attributes: Some(Vec::from(PERSON_ATTRIBUTES)),
ext_access_check: 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))
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,10 @@
(% extends "base_htmx_with_nav.html" %)
(% block title %)Admin Panel(% endblock %)
(% block head %)
(% endblock %)
(% block main %)
(( partial|safe ))
(% endblock %)

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

View file

@ -0,0 +1,29 @@
(% 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) %)
(% call string_attr("Displayname", "displayname", person.displayname, true, Attribute::DisplayName) %)
(% 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>

View 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 %)

View 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 %)

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

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

View file

@ -2,7 +2,9 @@
(% block body %)
(% include "navbar.html" %)
<div id="main">
(% block main %)(% endblock %)
</div>
(% include "signout_modal.html" %)
(% endblock %)

View file

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

View file

@ -11,6 +11,7 @@ use crate::valueset::{ValueSet, ValueSetIutf8};
pub use kanidm_proto::attribute::Attribute;
use kanidm_proto::constants::*;
use kanidm_proto::internal::OperationError;
use kanidm_proto::scim_v1::JsonValue;
use kanidm_proto::v1::AccountType;
use uuid::Uuid;
@ -129,6 +130,12 @@ impl From<EntryClass> for &'static str {
}
}
impl From<EntryClass> for JsonValue {
fn from(value: EntryClass) -> Self {
Self::String(value.as_ref().to_string())
}
}
impl AsRef<str> for EntryClass {
fn as_ref(&self) -> &str {
self.into()

View file

@ -23,6 +23,7 @@ use hashbrown::HashMap;
use hashbrown::HashSet;
use kanidm_proto::constants::ATTR_UUID;
use kanidm_proto::internal::{Filter as ProtoFilter, OperationError, SchemaError};
use kanidm_proto::scim_v1::client::{AttrPath as ScimAttrPath, ScimFilter};
use ldap3_proto::proto::{LdapFilter, LdapSubstringFilter};
use serde::Deserialize;
use uuid::Uuid;
@ -764,6 +765,21 @@ impl Filter<FilterInvalid> {
},
})
}
#[instrument(name = "filter::from_scim_ro", level = "trace", skip_all)]
pub fn from_scim_ro(
ev: &Identity,
f: &ScimFilter,
qs: &mut QueryServerReadTransaction,
) -> Result<Self, OperationError> {
let depth = DEFAULT_LIMIT_FILTER_DEPTH_MAX as usize;
let mut elems = ev.limits().filter_max_elements;
Ok(Filter {
state: FilterInvalid {
inner: FilterComp::from_scim_ro(f, qs, depth, &mut elems)?,
},
})
}
}
impl FromStr for Filter<FilterInvalid> {
@ -1087,32 +1103,21 @@ impl FilterComp {
elems: &mut usize,
) -> Result<Self, OperationError> {
let ndepth = depth.checked_sub(1).ok_or(OperationError::ResourceLimit)?;
*elems = (*elems)
.checked_sub(1)
.ok_or(OperationError::ResourceLimit)?;
Ok(match f {
LdapFilter::And(l) => {
*elems = (*elems)
.checked_sub(l.len())
.ok_or(OperationError::ResourceLimit)?;
FilterComp::And(
l.iter()
.map(|f| Self::from_ldap_ro(f, qs, ndepth, elems))
.collect::<Result<Vec<_>, _>>()?,
)
}
LdapFilter::Or(l) => {
*elems = (*elems)
.checked_sub(l.len())
.ok_or(OperationError::ResourceLimit)?;
FilterComp::Or(
l.iter()
.map(|f| Self::from_ldap_ro(f, qs, ndepth, elems))
.collect::<Result<Vec<_>, _>>()?,
)
}
LdapFilter::And(l) => FilterComp::And(
l.iter()
.map(|f| Self::from_ldap_ro(f, qs, ndepth, elems))
.collect::<Result<Vec<_>, _>>()?,
),
LdapFilter::Or(l) => FilterComp::Or(
l.iter()
.map(|f| Self::from_ldap_ro(f, qs, ndepth, elems))
.collect::<Result<Vec<_>, _>>()?,
),
LdapFilter::Not(l) => {
*elems = (*elems)
.checked_sub(1)
.ok_or(OperationError::ResourceLimit)?;
FilterComp::AndNot(Box::new(Self::from_ldap_ro(l, qs, ndepth, elems)?))
}
LdapFilter::Equality(a, v) => {
@ -1172,6 +1177,103 @@ impl FilterComp {
}
})
}
fn from_scim_ro(
f: &ScimFilter,
qs: &mut QueryServerReadTransaction,
depth: usize,
elems: &mut usize,
) -> Result<Self, OperationError> {
let ndepth = depth.checked_sub(1).ok_or(OperationError::ResourceLimit)?;
*elems = (*elems)
.checked_sub(1)
.ok_or(OperationError::ResourceLimit)?;
Ok(match f {
ScimFilter::Present(ScimAttrPath { a, s: None }) => FilterComp::Pres(a.clone()),
ScimFilter::Equal(ScimAttrPath { a, s: None }, json_value) => {
let pv = qs.resolve_scim_json_get(a, json_value)?;
FilterComp::Eq(a.clone(), pv)
}
ScimFilter::Contains(ScimAttrPath { a, s: None }, json_value) => {
let pv = qs.resolve_scim_json_get(a, json_value)?;
FilterComp::Cnt(a.clone(), pv)
}
ScimFilter::StartsWith(ScimAttrPath { a, s: None }, json_value) => {
let pv = qs.resolve_scim_json_get(a, json_value)?;
FilterComp::Stw(a.clone(), pv)
}
ScimFilter::EndsWith(ScimAttrPath { a, s: None }, json_value) => {
let pv = qs.resolve_scim_json_get(a, json_value)?;
FilterComp::Enw(a.clone(), pv)
}
ScimFilter::Greater(ScimAttrPath { a, s: None }, json_value) => {
let pv = qs.resolve_scim_json_get(a, json_value)?;
// Greater is equivalent to "not equal or less than".
FilterComp::And(vec![
FilterComp::Pres(a.clone()),
FilterComp::AndNot(Box::new(FilterComp::Or(vec![
FilterComp::LessThan(a.clone(), pv.clone()),
FilterComp::Eq(a.clone(), pv),
]))),
])
}
ScimFilter::Less(ScimAttrPath { a, s: None }, json_value) => {
let pv = qs.resolve_scim_json_get(a, json_value)?;
FilterComp::LessThan(a.clone(), pv)
}
ScimFilter::GreaterOrEqual(ScimAttrPath { a, s: None }, json_value) => {
let pv = qs.resolve_scim_json_get(a, json_value)?;
// Greater or equal is equivalent to "not less than".
FilterComp::And(vec![
FilterComp::Pres(a.clone()),
FilterComp::AndNot(Box::new(FilterComp::LessThan(a.clone(), pv.clone()))),
])
}
ScimFilter::LessOrEqual(ScimAttrPath { a, s: None }, json_value) => {
let pv = qs.resolve_scim_json_get(a, json_value)?;
FilterComp::Or(vec![
FilterComp::LessThan(a.clone(), pv.clone()),
FilterComp::Eq(a.clone(), pv),
])
}
ScimFilter::Not(f) => {
let f = Self::from_scim_ro(f, qs, ndepth, elems)?;
FilterComp::AndNot(Box::new(f))
}
ScimFilter::Or(left, right) => {
let left = Self::from_scim_ro(left, qs, ndepth, elems)?;
let right = Self::from_scim_ro(right, qs, ndepth, elems)?;
FilterComp::Or(vec![left, right])
}
ScimFilter::And(left, right) => {
let left = Self::from_scim_ro(left, qs, ndepth, elems)?;
let right = Self::from_scim_ro(right, qs, ndepth, elems)?;
FilterComp::And(vec![left, right])
}
ScimFilter::NotEqual(ScimAttrPath { s: None, .. }, _) => {
error!("Unsupported filter operation - not-equal");
return Err(OperationError::FilterGeneration);
}
ScimFilter::Present(ScimAttrPath { s: Some(_), .. })
| ScimFilter::Equal(ScimAttrPath { s: Some(_), .. }, _)
| ScimFilter::NotEqual(ScimAttrPath { s: Some(_), .. }, _)
| ScimFilter::Contains(ScimAttrPath { s: Some(_), .. }, _)
| ScimFilter::StartsWith(ScimAttrPath { s: Some(_), .. }, _)
| ScimFilter::EndsWith(ScimAttrPath { s: Some(_), .. }, _)
| ScimFilter::Greater(ScimAttrPath { s: Some(_), .. }, _)
| ScimFilter::Less(ScimAttrPath { s: Some(_), .. }, _)
| ScimFilter::GreaterOrEqual(ScimAttrPath { s: Some(_), .. }, _)
| ScimFilter::LessOrEqual(ScimAttrPath { s: Some(_), .. }, _) => {
error!("Unsupported filter operation - sub-attribute");
return Err(OperationError::FilterGeneration);
}
ScimFilter::Complex(..) => {
error!("Unsupported filter operation - complex");
return Err(OperationError::FilterGeneration);
}
})
}
}
/* We only configure partial eq if cfg test on the invalid/valid types */

View file

@ -35,6 +35,7 @@ use concread::arcache::{ARCacheBuilder, ARCacheReadTxn};
use concread::cowcell::*;
use hashbrown::{HashMap, HashSet};
use kanidm_proto::internal::{DomainInfo as ProtoDomainInfo, ImageValue, UiHint};
use kanidm_proto::scim_v1::client::ScimFilter;
use kanidm_proto::scim_v1::server::ScimOAuth2ClaimMap;
use kanidm_proto::scim_v1::server::ScimOAuth2ScopeMap;
use kanidm_proto::scim_v1::server::ScimReference;
@ -934,6 +935,64 @@ pub trait QueryServerTransaction<'a> {
}
}
fn resolve_scim_json_get(
&mut self,
attr: &Attribute,
value: &JsonValue,
) -> Result<PartialValue, OperationError> {
let schema = self.get_schema();
// Lookup the attr
let Some(schema_a) = schema.get_attributes().get(attr) else {
// No attribute of this name exists - fail fast, there is no point to
// proceed, as nothing can be satisfied.
return Err(OperationError::InvalidAttributeName(attr.to_string()));
};
match schema_a.syntax {
SyntaxType::Utf8String => {
let JsonValue::String(value) = value else {
return Err(OperationError::InvalidAttribute(attr.to_string()));
};
Ok(PartialValue::Utf8(value.to_string()))
}
SyntaxType::Utf8StringInsensitive => {
let JsonValue::String(value) = value else {
return Err(OperationError::InvalidAttribute(attr.to_string()));
};
Ok(PartialValue::new_iutf8(value))
}
SyntaxType::Utf8StringIname => {
let JsonValue::String(value) = value else {
return Err(OperationError::InvalidAttribute(attr.to_string()));
};
Ok(PartialValue::new_iname(value))
}
SyntaxType::Uuid => {
let JsonValue::String(value) = value else {
return Err(OperationError::InvalidAttribute(attr.to_string()));
};
let un = self.name_to_uuid(value).unwrap_or(UUID_DOES_NOT_EXIST);
Ok(PartialValue::Uuid(un))
}
SyntaxType::ReferenceUuid
| SyntaxType::OauthScopeMap
| SyntaxType::Session
| SyntaxType::ApiToken
| SyntaxType::Oauth2Session
| SyntaxType::ApplicationPassword => {
let JsonValue::String(value) = value else {
return Err(OperationError::InvalidAttribute(attr.to_string()));
};
let un = self.name_to_uuid(value).unwrap_or(UUID_DOES_NOT_EXIST);
Ok(PartialValue::Refer(un))
}
_ => Err(OperationError::InvalidAttribute(attr.to_string())),
}
}
fn resolve_scim_json_put(
&mut self,
attr: &Attribute,
@ -1555,6 +1614,40 @@ impl QueryServerReadTransaction<'_> {
}
}
}
#[instrument(level = "debug", skip_all)]
pub fn scim_search_ext(
&mut self,
ident: Identity,
filter: ScimFilter,
query: ScimEntryGetQuery,
) -> Result<Vec<ScimEntryKanidm>, OperationError> {
let filter_intent = Filter::from_scim_ro(&ident, &filter, self)?;
let f_intent_valid = filter_intent
.validate(self.get_schema())
.map_err(OperationError::SchemaViolation)?;
let f_valid = f_intent_valid.clone().into_ignore_hidden();
let r_attrs = query
.attributes
.map(|attr_set| attr_set.into_iter().collect());
let se = SearchEvent {
ident,
filter: f_valid,
filter_orig: f_intent_valid,
attrs: r_attrs,
effective_access_check: query.ext_access_check,
};
let vs = self.search_ext(&se)?;
vs.into_iter()
.map(|entry| entry.to_scim_kanidm(self))
.collect()
}
}
impl<'a> QueryServerTransaction<'a> for QueryServerWriteTransaction<'a> {
@ -2625,7 +2718,9 @@ impl<'a> QueryServerWriteTransaction<'a> {
#[cfg(test)]
mod tests {
use crate::prelude::*;
use kanidm_proto::scim_v1::client::ScimFilter;
use kanidm_proto::scim_v1::server::ScimReference;
use kanidm_proto::scim_v1::JsonValue;
use kanidm_proto::scim_v1::ScimEntryGetQuery;
#[qs_test]
@ -3077,4 +3172,44 @@ mod tests {
assert!(ext_access_check.modify_present.check(&Attribute::Name));
assert!(ext_access_check.modify_remove.check(&Attribute::Name));
}
#[qs_test]
async fn test_scim_basic_search_ext_query(server: &QueryServer) {
let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
let group_uuid = Uuid::new_v4();
let e1 = entry_init!(
(Attribute::Class, EntryClass::Object.to_value()),
(Attribute::Class, EntryClass::Group.to_value()),
(Attribute::Name, Value::new_iname("testgroup")),
(Attribute::Uuid, Value::Uuid(group_uuid))
);
assert!(server_txn.internal_create(vec![e1]).is_ok());
assert!(server_txn.commit().is_ok());
// Now read that entry.
let mut server_txn = server.read().await.unwrap();
let idm_admin_entry = server_txn.internal_search_uuid(UUID_IDM_ADMIN).unwrap();
let idm_admin_ident = Identity::from_impersonate_entry_readwrite(idm_admin_entry);
let filter = ScimFilter::And(
Box::new(ScimFilter::Equal(
Attribute::Class.into(),
EntryClass::Group.into(),
)),
Box::new(ScimFilter::Equal(
Attribute::Uuid.into(),
JsonValue::String(group_uuid.to_string()),
)),
);
let base: Vec<ScimEntryKanidm> = server_txn
.scim_search_ext(idm_admin_ident, filter, ScimEntryGetQuery::default())
.unwrap();
assert_eq!(base.len(), 1);
assert_eq!(base[0].header.id, group_uuid);
}
}