This commit is contained in:
Daniel Blignaut 2025-05-12 06:46:23 +02:00 committed by GitHub
commit 6426237c0b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 384 additions and 318 deletions

263
Cargo.lock generated
View file

@ -117,6 +117,15 @@ version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
[[package]]
name = "arbitrary"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223"
dependencies = [
"derive_arbitrary",
]
[[package]]
name = "arc-swap"
version = "1.7.1"
@ -137,60 +146,69 @@ dependencies = [
[[package]]
name = "askama"
version = "0.12.1"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28"
checksum = "f75363874b771be265f4ffe307ca705ef6f3baa19011c149da8674a87f1b75c4"
dependencies = [
"askama_derive",
"askama_escape",
"humansize",
"num-traits",
"itoa",
"percent-encoding",
"serde",
"serde_json",
]
[[package]]
name = "askama_axum"
version = "0.4.0"
name = "askama_derive"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a41603f7cdbf5ac4af60760f17253eb6adf6ec5b6f14a7ed830cf687d375f163"
checksum = "129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f"
dependencies = [
"askama_parser",
"basic-toml",
"memchr",
"proc-macro2",
"quote",
"rustc-hash 2.1.1",
"serde",
"serde_derive",
"syn 2.0.101",
]
[[package]]
name = "askama_parser"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358"
dependencies = [
"memchr",
"serde",
"serde_derive",
"winnow 0.7.9",
]
[[package]]
name = "askama_web"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0715e988725139fa7e73a3d5c1c5e2931ad6f7ebe8e378d1e7925d8e6a687fcf"
dependencies = [
"askama",
"askama_web_derive",
"axum-core",
"http 1.3.1",
]
[[package]]
name = "askama_derive"
version = "0.12.5"
name = "askama_web_derive"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83"
checksum = "34921de3d57974069bad483fdfe0ec65d88c4ff892edd1ab4d8b03be0dda1b9b"
dependencies = [
"askama_parser",
"basic-toml",
"mime",
"mime_guess",
"proc-macro2",
"quote",
"serde",
"syn 2.0.101",
]
[[package]]
name = "askama_escape"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341"
[[package]]
name = "askama_parser"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0"
dependencies = [
"nom 7.1.3",
]
[[package]]
name = "asn1-rs"
version = "0.6.2"
@ -317,14 +335,14 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "axum"
version = "0.7.9"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
checksum = "de45108900e1f9b9242f7f2e254aa3e2c029c921c258fe9e6b4217eeebd54288"
dependencies = [
"async-trait",
"axum-core",
"axum-macros",
"axum-macros 0.5.0",
"bytes",
"form_urlencoded",
"futures-util",
"http 1.3.1",
"http-body 1.0.1",
@ -353,13 +371,12 @@ dependencies = [
[[package]]
name = "axum-core"
version = "0.4.5"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6"
dependencies = [
"async-trait",
"bytes",
"futures-util",
"futures-core",
"http 1.3.1",
"http-body 1.0.1",
"http-body-util",
@ -374,22 +391,21 @@ dependencies = [
[[package]]
name = "axum-extra"
version = "0.9.6"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c794b30c904f0a1c2fb7740f7df7f7972dfaa14ef6f57cb6178dc63e5dca2f04"
checksum = "45bf463831f5131b7d3c756525b305d40f1185b688565648a92e1392ca35713d"
dependencies = [
"axum",
"axum-core",
"bytes",
"cookie 0.18.1",
"fastrand",
"futures-util",
"http 1.3.1",
"http-body 1.0.1",
"http-body-util",
"mime",
"multer",
"pin-project-lite",
"rustversion",
"serde",
"tower 0.5.2",
"tower-layer",
@ -398,18 +414,17 @@ dependencies = [
[[package]]
name = "axum-htmx"
version = "0.5.0"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40f7051fdc094b6e5ea06cab9bca4b198c54dee4472a9419155f0ff19f19901e"
checksum = "d16a4be621f96b959fc829e4cbf02fd79ffb8427525002af31a9e2979599fbb7"
dependencies = [
"async-trait",
"axum-core",
"futures-core",
"http 1.3.1",
"pin-project-lite",
"serde",
"serde_json",
"tower 0.4.13",
"tower 0.5.2",
]
[[package]]
@ -423,6 +438,17 @@ dependencies = [
"syn 2.0.101",
]
[[package]]
name = "axum-macros"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
]
[[package]]
name = "backtrace"
version = "0.3.74"
@ -1123,6 +1149,17 @@ dependencies = [
"serde",
]
[[package]]
name = "derive_arbitrary"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
]
[[package]]
name = "derive_builder"
version = "0.20.2"
@ -2510,15 +2547,6 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "humansize"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
dependencies = [
"libm",
]
[[package]]
name = "hyper"
version = "0.14.32"
@ -3267,11 +3295,11 @@ name = "kanidmd_core"
version = "1.7.0-dev"
dependencies = [
"askama",
"askama_axum",
"askama_web",
"axum",
"axum-extra",
"axum-htmx",
"axum-macros",
"axum-macros 0.4.2",
"bytes",
"chrono",
"compact_jwt",
@ -3522,12 +3550,6 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "libm"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9627da5196e5d8ed0b0495e61e518847578da83483c37288316d9b2e03a7f72"
[[package]]
name = "libmimalloc-sys"
version = "0.1.42"
@ -3677,9 +3699,9 @@ dependencies = [
[[package]]
name = "matchit"
version = "0.7.3"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
[[package]]
name = "mathru"
@ -4205,37 +4227,37 @@ dependencies = [
[[package]]
name = "opentelemetry"
version = "0.27.1"
version = "0.29.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab70038c28ed37b97d8ed414b6429d343a8bbf44c9f79ec854f3a643029ba6d7"
checksum = "9e87237e2775f74896f9ad219d26a2081751187eb7c9f5c58dde20a23b95d16c"
dependencies = [
"futures-core",
"futures-sink",
"js-sys",
"pin-project-lite",
"thiserror 1.0.69",
"thiserror 2.0.12",
"tracing",
]
[[package]]
name = "opentelemetry-http"
version = "0.27.0"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a8a7f5f6ba7c1b286c2fbca0454eaba116f63bbe69ed250b642d36fbb04d80"
checksum = "46d7ab32b827b5b495bd90fa95a6cb65ccc293555dcc3199ae2937d2d237c8ed"
dependencies = [
"async-trait",
"bytes",
"http 1.3.1",
"opentelemetry",
"tracing",
]
[[package]]
name = "opentelemetry-otlp"
version = "0.27.0"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91cf61a1868dacc576bf2b2a1c3e9ab150af7272909e80085c3173384fe11f76"
checksum = "d899720fe06916ccba71c01d04ecd77312734e2de3467fd30d9d580c8ce85656"
dependencies = [
"async-trait",
"futures-core",
"http 1.3.1",
"opentelemetry",
@ -4244,16 +4266,16 @@ dependencies = [
"opentelemetry_sdk",
"prost",
"serde",
"thiserror 1.0.69",
"thiserror 2.0.12",
"tokio",
"tonic",
]
[[package]]
name = "opentelemetry-proto"
version = "0.27.0"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6e05acbfada5ec79023c85368af14abd0b307c015e9064d249b2a950ef459a6"
checksum = "8c40da242381435e18570d5b9d50aca2a4f4f4d8e146231adb4e7768023309b3"
dependencies = [
"opentelemetry",
"opentelemetry_sdk",
@ -4263,26 +4285,25 @@ dependencies = [
[[package]]
name = "opentelemetry-semantic-conventions"
version = "0.27.0"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc1b6902ff63b32ef6c489e8048c5e253e2e4a803ea3ea7e783914536eb15c52"
checksum = "84b29a9f89f1a954936d5aa92f19b2feec3c8f3971d3e96206640db7f9706ae3"
[[package]]
name = "opentelemetry_sdk"
version = "0.27.1"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "231e9d6ceef9b0b2546ddf52335785ce41252bc7474ee8ba05bfad277be13ab8"
checksum = "afdefb21d1d47394abc1ba6c57363ab141be19e27cc70d0e422b7f303e4d290b"
dependencies = [
"async-trait",
"futures-channel",
"futures-executor",
"futures-util",
"glob",
"opentelemetry",
"percent-encoding",
"rand 0.8.5",
"rand 0.9.1",
"serde_json",
"thiserror 1.0.69",
"thiserror 2.0.12",
"tokio",
"tokio-stream",
"tracing",
@ -4575,30 +4596,6 @@ dependencies = [
"toml_edit",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn 1.0.109",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.95"
@ -5521,6 +5518,12 @@ dependencies = [
"libc",
]
[[package]]
name = "simd-adler32"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "sketching"
version = "1.7.0-dev"
@ -6041,12 +6044,9 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52"
dependencies = [
"async-stream",
"async-trait",
"axum",
"base64 0.22.1",
"bytes",
"h2 0.4.9",
"http 1.3.1",
"http-body 1.0.1",
"http-body-util",
@ -6056,7 +6056,6 @@ dependencies = [
"percent-encoding",
"pin-project",
"prost",
"socket2",
"tokio",
"tokio-stream",
"tower 0.4.13",
@ -6202,9 +6201,9 @@ dependencies = [
[[package]]
name = "tracing-opentelemetry"
version = "0.28.0"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97a971f6058498b5c0f1affa23e7ea202057a7301dbff68e968b2d578bcbd053"
checksum = "fd8e764bd6f5813fd8bebc3117875190c5b0415be8f7f8059bffb6ecd979c444"
dependencies = [
"js-sys",
"once_cell",
@ -6367,9 +6366,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "utoipa"
version = "4.2.3"
version = "5.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5afb1a60e207dca502682537fefcfd9921e71d0b83e9576060f09abc6efab23"
checksum = "435c6f69ef38c9017b4b4eea965dfb91e71e53d869e896db40d1cf2441dd75c0"
dependencies = [
"indexmap 2.9.0",
"serde",
@ -6379,11 +6378,10 @@ dependencies = [
[[package]]
name = "utoipa-gen"
version = "4.3.1"
version = "5.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20c24e8ab68ff9ee746aad22d39b5535601e6416d1b0feeabf78be986a5c4392"
checksum = "a77d306bc75294fd52f3e99b13ece67c02c1a2789190a6f31d32f736624326f7"
dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"regex",
@ -6394,16 +6392,18 @@ dependencies = [
[[package]]
name = "utoipa-swagger-ui"
version = "6.0.0"
version = "9.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b39868d43c011961e04b41623e050aedf2cc93652562ff7935ce0f819aaf2da"
checksum = "d29519b3c485df6b13f4478ac909a491387e9ef70204487c3b64b53749aec0be"
dependencies = [
"axum",
"base64 0.22.1",
"mime_guess",
"regex",
"rust-embed",
"serde",
"serde_json",
"url",
"utoipa",
"zip",
]
@ -7329,14 +7329,29 @@ dependencies = [
[[package]]
name = "zip"
version = "0.6.6"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
checksum = "1dcb24d0152526ae49b9b96c1dcf71850ca1e0b882e4e28ed898a93c41334744"
dependencies = [
"byteorder",
"arbitrary",
"crc32fast",
"crossbeam-utils",
"flate2",
"indexmap 2.9.0",
"memchr",
"zopfli",
]
[[package]]
name = "zopfli"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7"
dependencies = [
"bumpalo",
"crc32fast",
"log",
"simd-adler32",
]
[[package]]

View file

@ -145,10 +145,10 @@ sketching = { path = "./libs/sketching", version = "=1.7.0-dev" }
anyhow = { version = "1.0.98" }
argon2 = { version = "0.5.3", features = ["alloc"] }
askama = { version = "0.12.1", features = ["serde", "with-axum"] }
askama_axum = { version = "0.4.0" }
askama = { version = "0.14.0", features = ["serde_json"] }
askama_web = { version = "0.14.0", features = ["axum-0.8"] }
async-trait = "^0.1.85"
axum = { version = "0.7.9", features = [
axum = { version = "^0.8.0", features = [
"form",
"json",
"macros",
@ -158,9 +158,9 @@ axum = { version = "0.7.9", features = [
"tokio",
"tracing",
] }
axum-extra = { version = "0.9.6", features = ["cookie"] }
axum-extra = { version = "0.10.1", features = ["cookie"] }
axum-macros = "0.4.2"
axum-htmx = { version = "0.5.0", features = ["serde", "guards"] }
axum-htmx = { version = "0.7.0", features = ["serde", "guards"] }
base32 = "^0.5.1"
base64 = "^0.22.1"
base64urlsafedata = "0.5.1"
@ -222,18 +222,18 @@ oauth2_ext = { version = "^4.4.2", package = "oauth2", default-features = false
openssl-sys = "^0.9"
openssl = "^0.10.72"
opentelemetry = { version = "0.27.0" }
opentelemetry_api = { version = "0.27.0", features = ["logs", "metrics"] }
opentelemetry-otlp = { version = "0.27.0", default-features = false, features = [
opentelemetry = { version = "0.29.0" }
opentelemetry_api = { version = "0.29.0", features = ["logs", "metrics"] }
opentelemetry-otlp = { version = "0.29.0", default-features = false, features = [
"serde",
"logs",
"metrics",
"http-proto",
"grpc-tonic",
] }
opentelemetry_sdk = { version = "0.27.0", features = ["rt-tokio"] }
opentelemetry-semantic-conventions = "0.27.0"
tracing-opentelemetry = "0.28.0"
opentelemetry_sdk = { version = "0.29.0", features = ["rt-tokio"] }
opentelemetry-semantic-conventions = "0.29.0"
tracing-opentelemetry = "0.30.0"
tracing-core = "0.1.33"
peg = "0.8"
@ -292,8 +292,8 @@ tracing-forest = "^0.1.6"
url = "^2.5.2"
urlencoding = "2.1.3"
utoipa = { version = "4.2.0", features = ["url", "uuid"] }
utoipa-swagger-ui = "6.0.0"
utoipa = { version = "5.3.1", features = ["url", "uuid"] }
utoipa-swagger-ui = "9.0.1"
uuid = "^1.12.1"
webauthn-authenticator-rs = { version = "0.5.1", features = [

View file

@ -44,7 +44,7 @@ introspection.
## Kanidm's OAuth2 URLs
Kanidm will expose its OAuth2 APIs at the following URLs, substituting
`:client_id:` with an OAuth2 client ID.
`{client_id}:` with an OAuth2 client ID.
<!-- markdownlint-disable MD033 -->
<dl>
@ -57,7 +57,7 @@ URL **(recommended)**
</dt>
<dd>
`https://idm.example.com/oauth2/openid/:client_id:/.well-known/openid-configuration`
`https://idm.example.com/oauth2/openid/{client_id}:/.well-known/openid-configuration`
This document includes all the URLs and attributes an app needs to be able to
authenticate using OIDC with Kanidm, _except_ for the `client_id` and
@ -79,7 +79,7 @@ URL **(recommended)**
<dd>
`https://idm.example.com/oauth2/openid/:client_id:/.well-known/oauth-authorization-server`
`https://idm.example.com/oauth2/openid/{client_id}:/.well-known/oauth-authorization-server`
</dd>
@ -91,7 +91,7 @@ URL **(recommended)**
<dd>
`https://idm.example.com/oauth2/openid/:client_id:/.well-known/webfinger`
`https://idm.example.com/oauth2/openid/{client_id}:/.well-known/webfinger`
See [the WebFinger section](#webfinger) for more details, as there a number of
caveats for WebFinger clients.
@ -168,7 +168,7 @@ OpenID Connect Issuer URL
<dd>
`https://idm.example.com/oauth2/openid/:client_id:`
`https://idm.example.com/oauth2/openid/{client_id}:`
</dd>
@ -180,7 +180,7 @@ OpenID Connect user info
<dd>
`https://idm.example.com/oauth2/openid/:client_id:/userinfo`
`https://idm.example.com/oauth2/openid/{client_id}:/userinfo`
</dd>
@ -192,7 +192,7 @@ Token signing public key
<dd>
`https://idm.example.com/oauth2/openid/:client_id:/public_key.jwk`
`https://idm.example.com/oauth2/openid/{client_id}:/public_key.jwk`
</dd>
@ -497,14 +497,14 @@ difficult to use with Kanidm:
You will need a load balancer in front of Kanidm's HTTPS server to send a HTTP
307 redirect to the appropriate
`/oauth2/openid/:client_id:/.well-known/webfinger` URL, *while preserving all
`/oauth2/openid/{client_id}:/.well-known/webfinger` URL, *while preserving all
query parameters*. For example, with Caddy:
```caddy
# Match on a prefix, and use {uri} to preserve all query parameters.
# This only supports *one* client.
example.com {
redir /.well-known/webfinger https://idm.example.com/oauth2/openid/:client_id:{uri} 307
redir /.well-known/webfinger https://idm.example.com/oauth2/openid/{client_id}:{uri} 307
}
```

View file

@ -981,7 +981,7 @@ server.config.yaml:
GUI:
authenticator:
type: OIDC
oidc_issuer: https://idm.example.com/oauth2/openid/:client_id:/
oidc_issuer: https://idm.example.com/oauth2/openid/{client_id}:/
oauth_client_id: <client name/>
oauth_client_secret: <client secret>
```

View file

@ -29,6 +29,7 @@ pub mod prelude {
pub use crate::{ScimAttr, ScimComplexAttr, ScimEntry, ScimEntryHeader, ScimMeta, ScimValue};
}
#[derive(Deserialize, Serialize, Debug, Clone, ToSchema)]
#[serde(untagged)]
pub enum ScimAttr {
@ -41,7 +42,7 @@ pub enum ScimAttr {
// this point.
#[serde(with = "time::serde::rfc3339")]
DateTime(OffsetDateTime),
#[schema(value_type = Object)]
Binary(Base64UrlSafeData),
Reference(Url),
}

View file

@ -5,7 +5,7 @@ use opentelemetry_otlp::{Protocol, WithExportConfig};
use opentelemetry::{global, trace::TracerProvider as _, KeyValue};
use opentelemetry_sdk::{
trace::{Sampler, TracerProvider},
trace::{Sampler, TracerProviderBuilder},
Resource,
};
use tracing::Subscriber;
@ -86,19 +86,27 @@ pub fn start_logging_pipeline(
// let hostname = hostname.to_string_lossy();
// let hostname = hostname.to_lowercase();
let resource = Resource::from_schema_url(
[
let resource = Resource::builder()
.with_schema_url(vec![
// TODO: it'd be really nice to be able to set the instance ID here, from the server UUID so we know *which* instance on this host is logging
KeyValue::new(SERVICE_NAME, service_name),
KeyValue::new(SERVICE_VERSION, version),
// TODO: currently marked as an experimental flag, leaving it out for now
// KeyValue::new(DEPLOYMENT_ENVIRONMENT_NAME, hostname),
],
SCHEMA_URL,
);
], SCHEMA_URL)
.build();
// .with_attributes(vec![
// // TODO: it'd be really nice to be able to set the instance ID here, from the server UUID so we know *which* instance on this host is logging
// KeyValue::new(SERVICE_NAME, service_name),
// KeyValue::new(SERVICE_VERSION, version),
// // TODO: currently marked as an experimental flag, leaving it out for now
// // KeyValue::new(DEPLOYMENT_ENVIRONMENT_NAME, hostname),
// ],
// SCHEMA_URL,
// );
let provider = TracerProvider::builder()
.with_batch_exporter(otlp_exporter, opentelemetry_sdk::runtime::Tokio)
let provider = TracerProviderBuilder::default()
.with_batch_exporter(otlp_exporter)
// we want *everything!*
.with_sampler(Sampler::AlwaysOn)
.with_max_events_per_span(MAX_EVENTS_PER_SPAN)
@ -137,7 +145,8 @@ pub struct TracingPipelineGuard {}
impl Drop for TracingPipelineGuard {
fn drop(&mut self) {
opentelemetry::global::shutdown_tracer_provider();
// TODO: https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry-sdk/CHANGELOG.md how to remove tihs?
// opentelemetry::global::shutdown_tracer_provider();
eprintln!("Logging pipeline completed shutdown");
}
}

View file

@ -133,7 +133,9 @@ pub enum CURegState {
TotpNameTryAgain(String),
TotpInvalidSha1,
BackupCodes(Vec<String>),
#[schema(value_type = Object)]
Passkey(CreationChallengeResponse),
#[schema(value_type = Object)]
AttestedPasskey(CreationChallengeResponse),
}
@ -185,7 +187,7 @@ pub struct CUStatus {
pub unixcred: Option<CredentialDetail>,
pub unixcred_state: CUCredState,
#[schema(value_type = Object)]
pub sshkeys: BTreeMap<String, SshPublicKey>,
pub sshkeys_state: CUCredState,
}

View file

@ -83,6 +83,7 @@ pub struct ScimMail {
#[serde(rename_all = "camelCase")]
pub struct ScimSshPublicKey {
pub label: String,
#[schema(value_type = Object)]
pub value: SshPublicKey,
}

View file

@ -60,9 +60,11 @@ pub enum AuthCredential {
Anonymous,
Password(String),
Totp(u32),
#[schema(value_type = Object)]
SecurityKey(Box<PublicKeyCredential>),
BackupCode(String),
// Should this just be discoverable?
#[schema(value_type = Object)]
Passkey(Box<PublicKeyCredential>),
}
@ -151,7 +153,9 @@ pub enum AuthAllowed {
BackupCode,
Password,
Totp,
#[schema(value_type = Object)]
SecurityKey(RequestChallengeResponse),
#[schema(value_type = Object)]
Passkey(RequestChallengeResponse),
}

View file

@ -12,6 +12,7 @@ use crate::constants::{ATTR_GROUP, ATTR_LDAP_SSHPUBLICKEY};
#[allow(dead_code)]
#[derive(ToSchema)]
#[schema(as = KeyTypeKind)]
#[schema(value_type = Object)]
pub struct KeyTypeKindSchema(KeyTypeKind);
#[derive(ToSchema)]
@ -21,6 +22,7 @@ pub struct KeyTypeSchema {
pub short_name: &'static str,
pub is_cert: bool,
pub is_sk: bool,
#[schema(value_type = Object)]
pub kind: KeyTypeKind,
pub plain: &'static str,
}
@ -28,12 +30,15 @@ pub struct KeyTypeSchema {
#[allow(dead_code)]
#[derive(ToSchema)]
#[schema(as = PublicKeyKind)]
#[schema(value_type = Object)]
pub struct PublicKeyKindSchema(PublicKeyKind);
#[derive(ToSchema)]
#[schema(as = SshPublicKey)]
pub struct SshPublicKeySchema {
#[schema(value_type = Object)]
pub key_type: KeyType,
#[schema(value_type = Object)]
pub kind: PublicKeyKind,
pub comment: Option<String>,
}
@ -74,6 +79,7 @@ pub struct UnixUserToken {
pub uuid: Uuid,
pub shell: Option<String>,
pub groups: Vec<UnixGroupToken>,
#[schema(value_type = Object)]
pub sshkeys: Vec<SshPublicKey>,
// The default value of bool is false.
#[serde(default)]

View file

@ -20,8 +20,8 @@ default = []
dev-oauth2-device-flow = []
[dependencies]
askama = { workspace = true, features = ["with-axum"] }
askama_axum = { workspace = true }
askama = { workspace = true }
askama_web = { workspace = true }
axum = { workspace = true }
axum-htmx = { workspace = true }
axum-extra = { workspace = true }

View file

@ -55,8 +55,8 @@ impl Modify for SecurityAddon {
super::v1::raw_modify,
super::v1::raw_search,
super::v1_oauth2::oauth2_id_image_delete,
super::v1_oauth2::oauth2_id_image_post,
// super::v1_oauth2::oauth2_id_image_delete,
// super::v1_oauth2::oauth2_id_image_post,
super::v1_oauth2::oauth2_get,
super::v1_oauth2::oauth2_basic_post,
super::v1_oauth2::oauth2_public_post,
@ -89,10 +89,10 @@ impl Modify for SecurityAddon {
super::v1::schema_classtype_get_id,
super::v1::person_get,
super::v1::person_post,
super::v1::service_account_credential_generate,
super::v1::service_account_api_token_delete,
super::v1::service_account_api_token_get,
super::v1::service_account_api_token_post,
// super::v1::service_account_credential_generate,
// super::v1::service_account_api_token_delete,
// super::v1::service_account_api_token_get,
// super::v1::service_account_api_token_post,
super::v1::person_search_id,
super::v1::person_id_get,
super::v1::person_id_patch,
@ -121,7 +121,7 @@ impl Modify for SecurityAddon {
super::v1::person_id_radius_delete,
super::v1::person_id_radius_token_get,
super::v1::account_id_ssh_pubkeys_get,
// super::v1::account_id_ssh_pubkeys_get,
super::v1::account_id_radius_token_post,
super::v1::person_id_unix_post,
super::v1::person_id_unix_credential_put,
@ -129,8 +129,8 @@ impl Modify for SecurityAddon {
super::v1::person_identify_user_post,
super::v1::service_account_get,
super::v1::service_account_post,
super::v1::service_account_get,
super::v1::service_account_post,
// super::v1::service_account_get,
// super::v1::service_account_post,
super::v1::service_account_id_get,
super::v1::service_account_id_delete,
super::v1::service_account_id_patch,
@ -150,9 +150,9 @@ impl Modify for SecurityAddon {
super::v1::account_id_unix_post,
super::v1::account_id_unix_auth_post,
super::v1::account_id_unix_token,
super::v1::account_id_unix_token,
super::v1::account_id_radius_token_post,
super::v1::account_id_radius_token_get,
// super::v1::account_id_unix_token,
// super::v1::account_id_radius_token_post,
// super::v1::account_id_radius_token_get,
super::v1::account_id_ssh_pubkeys_get,
super::v1::account_id_ssh_pubkeys_tag_get,
super::v1::account_id_user_auth_token_get,

View file

@ -1,11 +1,7 @@
use axum::{
async_trait,
extract::connect_info::{ConnectInfo, Connected},
extract::FromRequestParts,
http::{
header::HeaderName, header::AUTHORIZATION as AUTHORISATION, request::Parts, StatusCode,
},
RequestPartsExt,
extract::{connect_info::{ConnectInfo, Connected}, FromRequestParts}, http::{
header::{HeaderName, AUTHORIZATION as AUTHORISATION}, request::Parts, StatusCode,
}, serve::IncomingStream, RequestPartsExt
};
use axum_extra::extract::cookie::CookieJar;
@ -17,6 +13,7 @@ use kanidmd_lib::prelude::{ClientAuthInfo, ClientCertInfo, Source};
pub use kanidmd_lib::idm::server::DomainInfoRead;
use compact_jwt::JwsCompact;
use tokio::net::TcpListener;
use std::str::FromStr;
use std::net::{IpAddr, SocketAddr};
@ -28,7 +25,6 @@ const X_FORWARDED_FOR_HEADER: HeaderName = HeaderName::from_static(X_FORWARDED_F
pub struct TrustedClientIp(pub IpAddr);
#[async_trait]
impl FromRequestParts<ServerState> for TrustedClientIp {
type Rejection = (StatusCode, &'static str);
@ -45,8 +41,8 @@ impl FromRequestParts<ServerState> for TrustedClientIp {
}) = parts
.extract::<ConnectInfo<ClientConnInfo>>()
.await
.map_err(|_| {
error!("Connect info contains invalid data");
.map_err(|e| {
error!("Connect info contains invalid data: {:}", e);
(
StatusCode::BAD_REQUEST,
"connect info contains invalid data",
@ -97,7 +93,6 @@ impl FromRequestParts<ServerState> for TrustedClientIp {
pub struct VerifiedClientInformation(pub ClientAuthInfo);
#[async_trait]
impl FromRequestParts<ServerState> for VerifiedClientInformation {
type Rejection = (StatusCode, &'static str);
@ -114,8 +109,8 @@ impl FromRequestParts<ServerState> for VerifiedClientInformation {
}) = parts
.extract::<ConnectInfo<ClientConnInfo>>()
.await
.map_err(|_| {
error!("Connect info contains invalid data");
.map_err(|e| {
error!("Connect info contains invalid data: {:}", e);
(
StatusCode::BAD_REQUEST,
"connect info contains invalid data",
@ -205,7 +200,6 @@ impl FromRequestParts<ServerState> for VerifiedClientInformation {
pub struct DomainInfo(pub DomainInfoRead);
#[async_trait]
impl FromRequestParts<ServerState> for DomainInfo {
type Rejection = (StatusCode, &'static str);
@ -249,3 +243,16 @@ impl Connected<SocketAddr> for ClientConnInfo {
}
}
}
impl Connected<IncomingStream<'_, TcpListener>> for ClientConnInfo {
fn connect_info(target: IncomingStream<'_, TcpListener>) -> Self {
let local_addr = target.io().local_addr().unwrap();
let remote_addr = target.remote_addr();
ClientConnInfo {
client_addr: remote_addr.clone(),
connection_addr: local_addr,
client_cert: None,
}
}
}

View file

@ -1,4 +1,5 @@
mod apidocs;
pub(crate) mod cache_buster;
pub(crate) mod errors;
mod extractors;
@ -242,7 +243,7 @@ pub async fn create_https_server(
let static_routes = match config.role {
ServerRole::WriteReplica | ServerRole::ReadOnlyReplica => {
Router::new()
.route("/ui/images/oauth2/:rs_name", get(oauth2::oauth2_image_get))
.route("/ui/images/oauth2/{rs_name}", get(oauth2::oauth2_image_get))
.route("/ui/images/domain", get(v1_domain::image_get))
.route("/manifest.webmanifest", get(manifest::manifest)) // skip_route_check
// Layers only apply to routes that are *already* added, not the ones
@ -356,6 +357,7 @@ pub async fn create_https_server(
}
}
async fn server_tls_loop(
mut tls_acceptor: SslAcceptor,
listener: TcpListener,

View file

@ -807,17 +807,17 @@ pub fn route_setup(state: ServerState) -> Router<ServerState> {
// // ⚠️ ⚠️ WARNING ⚠️ ⚠️
// // IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
.route(
"/oauth2/openid/:client_id/.well-known/openid-configuration",
"/oauth2/openid/{client_id}/.well-known/openid-configuration",
get(oauth2_openid_discovery_get).options(oauth2_preflight_options),
)
.route(
"/oauth2/openid/:client_id/.well-known/webfinger",
"/oauth2/openid/{client_id}/.well-known/webfinger",
get(oauth2_openid_webfinger_get).options(oauth2_preflight_options),
)
// // ⚠️ ⚠️ WARNING ⚠️ ⚠️
// // IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
.route(
"/oauth2/openid/:client_id/userinfo",
"/oauth2/openid/{client_id}/userinfo",
get(oauth2_openid_userinfo_get)
.post(oauth2_openid_userinfo_get)
.options(oauth2_preflight_options),
@ -825,13 +825,13 @@ pub fn route_setup(state: ServerState) -> Router<ServerState> {
// // ⚠️ ⚠️ WARNING ⚠️ ⚠️
// // IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
.route(
"/oauth2/openid/:client_id/public_key.jwk",
"/oauth2/openid/{client_id}/public_key.jwk",
get(oauth2_openid_publickey_get).options(oauth2_preflight_options),
)
// // ⚠️ ⚠️ WARNING ⚠️ ⚠️
// // IF YOU CHANGE THESE VALUES YOU MUST UPDATE OAUTH2 DISCOVERY URLS
.route(
"/oauth2/openid/:client_id/.well-known/oauth-authorization-server",
"/oauth2/openid/{client_id}/.well-known/oauth-authorization-server",
get(oauth2_rfc8414_metadata_get).options(oauth2_preflight_options),
)
.with_state(state.clone());

View file

@ -11,6 +11,7 @@ use compact_jwt::{Jwk, Jws, JwsSigner};
use kanidm_proto::constants::uri::V1_AUTH_VALID;
use std::net::IpAddr;
use uuid::Uuid;
use crate::https::apidocs::response_schema::Jwk as JwkResponse;
use kanidm_proto::internal::{
ApiToken, AppLink, CUIntentToken, CURequest, CUSessionToken, CUStatus, CreateRequest,
@ -1902,7 +1903,7 @@ pub async fn person_id_radius_token_get(
person_id_radius_handler(state, id, kopid, client_auth_info).await
}
// /v1/account/:id/_radius/_token
// /v1/account/{id}/_radius/_token
#[utoipa::path(
get,
path = "/v1/account/{id}/_radius/_token",
@ -3053,11 +3054,13 @@ pub async fn debug_ipinfo(
Ok(Json::from(ip_addr))
}
#[utoipa::path(
get,
path = "/v1/jwk/{key_id}",
responses(
(status=200, body=Jwk, content_type="application/json"),
(status=200, body=JwkResponse, content_type="application/json"),
ApiResponseWithout200,
),
security(("token_jwt" = [])),
@ -3083,14 +3086,14 @@ pub async fn public_jwk_key_id_get(
fn cacheable_routes(state: ServerState) -> Router<ServerState> {
Router::new()
.route("/v1/jwk/:key_id", get(public_jwk_key_id_get))
.route("/v1/jwk/{key_id}", get(public_jwk_key_id_get))
.route(
"/v1/person/:id/_radius/_token",
"/v1/person/{id}/_radius/_token",
get(person_id_radius_token_get),
)
.route("/v1/account/:id/_unix/_token", get(account_id_unix_token))
.route("/v1/account/{id}/_unix/_token", get(account_id_unix_token))
.route(
"/v1/account/:id/_radius/_token",
"/v1/account/{id}/_radius/_token",
get(account_id_radius_token_get),
)
.layer(from_fn(cache_me_short))
@ -3110,42 +3113,42 @@ pub(crate) fn route_setup(state: ServerState) -> Router<ServerState> {
post(super::v1_oauth2::oauth2_public_post),
)
.route(
"/v1/oauth2/:rs_name",
"/v1/oauth2/{rs_name}",
get(super::v1_oauth2::oauth2_id_get)
.patch(super::v1_oauth2::oauth2_id_patch)
.delete(super::v1_oauth2::oauth2_id_delete),
)
.route(
"/v1/oauth2/:rs_name/_attr/:attr",
"/v1/oauth2/{rs_name}/_attr/{attr}",
post(super::v1_oauth2::oauth2_id_attr_post)
.delete(super::v1_oauth2::oauth2_id_attr_delete),
)
.route(
"/v1/oauth2/:rs_name/_image",
"/v1/oauth2/{rs_name}/_image",
post(super::v1_oauth2::oauth2_id_image_post)
.delete(super::v1_oauth2::oauth2_id_image_delete),
)
.route(
"/v1/oauth2/:rs_name/_basic_secret",
"/v1/oauth2/{rs_name}/_basic_secret",
get(super::v1_oauth2::oauth2_id_get_basic_secret),
)
.route(
"/v1/oauth2/:rs_name/_scopemap/:group",
"/v1/oauth2/{rs_name}/_scopemap/{group}",
post(super::v1_oauth2::oauth2_id_scopemap_post)
.delete(super::v1_oauth2::oauth2_id_scopemap_delete),
)
.route(
"/v1/oauth2/:rs_name/_sup_scopemap/:group",
"/v1/oauth2/{rs_name}/_sup_scopemap/{group}",
post(super::v1_oauth2::oauth2_id_sup_scopemap_post)
.delete(super::v1_oauth2::oauth2_id_sup_scopemap_delete),
)
.route(
"/v1/oauth2/:rs_name/_claimmap/:claim_name/:group",
"/v1/oauth2/{rs_name}/_claimmap/{claim_name}/{group}",
post(super::v1_oauth2::oauth2_id_claimmap_post)
.delete(super::v1_oauth2::oauth2_id_claimmap_delete),
)
.route(
"/v1/oauth2/:rs_name/_claimmap/:claim_name",
"/v1/oauth2/{rs_name}/_claimmap/{claim_name}",
post(super::v1_oauth2::oauth2_id_claimmap_join_post),
)
.route("/v1/raw/create", post(raw_create))
@ -3158,24 +3161,24 @@ pub(crate) fn route_setup(state: ServerState) -> Router<ServerState> {
get(schema_attributetype_get), // post(|| async { "TODO" })
)
.route(
"/v1/schema/attributetype/:id",
"/v1/schema/attributetype/{id}",
get(schema_attributetype_get_id),
)
// .route("/schema/attributetype/:id", put(|| async { "TODO" }).patch(|| async { "TODO" }))
// .route("/schema/attributetype/{id}", put(|| async { "TODO" }).patch(|| async { "TODO" }))
.route(
"/v1/schema/classtype",
get(schema_classtype_get), // .post(|| async { "TODO" })
)
.route(
"/v1/schema/classtype/:id",
"/v1/schema/classtype/{id}",
get(schema_classtype_get_id), // .put(|| async { "TODO" })
// .patch(|| async { "TODO" }),
)
.route("/v1/self", get(whoami))
.route("/v1/self/_uat", get(whoami_uat))
// .route("/v1/self/_attr/:attr", get(|| async { "TODO" }))
// .route("/v1/self/_attr/{attr}", get(|| async { "TODO" }))
// .route("/v1/self/_credential", get(|| async { "TODO" }))
// .route("/v1/self/_credential/:cid/_lock", get(|| async { "TODO" }))
// .route("/v1/self/_credential/{cid}/_lock", get(|| async { "TODO" }))
// .route(
// "/v1/self/_radius",
// get(|| async { "TODO" })
@ -3183,70 +3186,70 @@ pub(crate) fn route_setup(state: ServerState) -> Router<ServerState> {
// .post(|| async { "TODO" }),
// )
// .route("/v1/self/_radius/_config", post(|| async { "TODO" }))
// .route("/v1/self/_radius/_config/:token", get(|| async { "TODO" }))
// .route("/v1/self/_radius/_config/{token}", get(|| async { "TODO" }))
// .route(
// "/v1/self/_radius/_config/:token/apple",
// "/v1/self/_radius/_config/{token}/apple",
// get(|| async { "TODO" }),
// )
// Applinks are the list of apps this account can access.
.route("/v1/self/_applinks", get(applinks_get))
// Person routes
.route("/v1/person", get(person_get).post(person_post))
.route("/v1/person/_search/:id", get(person_search_id))
.route("/v1/person/_search/{id}", get(person_search_id))
.route(
"/v1/person/:id",
"/v1/person/{id}",
get(person_id_get)
.patch(person_id_patch)
.delete(person_id_delete),
)
.route(
"/v1/person/:id/_attr/:attr",
"/v1/person/{id}/_attr/{attr}",
get(person_id_get_attr)
.put(person_id_put_attr)
.post(person_id_post_attr)
.delete(person_id_delete_attr),
)
.route(
"/v1/person/:id/_certificate",
"/v1/person/{id}/_certificate",
get(person_get_id_certificate).post(person_post_id_certificate),
)
.route(
"/v1/person/:id/_credential/_status",
"/v1/person/{id}/_credential/_status",
get(person_get_id_credential_status),
)
.route(
"/v1/person/:id/_credential/_update",
"/v1/person/{id}/_credential/_update",
get(person_id_credential_update_get),
)
.route(
"/v1/person/:id/_credential/_update_intent/:ttl",
"/v1/person/{id}/_credential/_update_intent/{ttl}",
get(person_id_credential_update_intent_ttl_get),
)
.route(
"/v1/person/:id/_credential/_update_intent",
"/v1/person/{id}/_credential/_update_intent",
get(person_id_credential_update_intent_get),
)
.route(
"/v1/person/:id/_ssh_pubkeys",
"/v1/person/{id}/_ssh_pubkeys",
get(person_id_ssh_pubkeys_get).post(person_id_ssh_pubkeys_post),
)
.route(
"/v1/person/:id/_ssh_pubkeys/:tag",
"/v1/person/{id}/_ssh_pubkeys/{tag}",
get(person_id_ssh_pubkeys_tag_get).delete(person_id_ssh_pubkeys_tag_delete),
)
.route(
"/v1/person/:id/_radius",
"/v1/person/{id}/_radius",
get(person_id_radius_get)
.post(person_id_radius_post)
.delete(person_id_radius_delete),
)
.route("/v1/person/:id/_unix", post(person_id_unix_post))
.route("/v1/person/{id}/_unix", post(person_id_unix_post))
.route(
"/v1/person/:id/_unix/_credential",
"/v1/person/{id}/_unix/_credential",
put(person_id_unix_credential_put).delete(person_id_unix_credential_delete),
)
.route(
"/v1/person/:id/_identify_user",
"/v1/person/{id}/_identify_user",
post(person_identify_user_post),
)
// Service accounts
@ -3259,85 +3262,85 @@ pub(crate) fn route_setup(state: ServerState) -> Router<ServerState> {
get(service_account_get).post(service_account_post),
)
.route(
"/v1/service_account/:id",
"/v1/service_account/{id}",
get(service_account_id_get)
.delete(service_account_id_delete)
.patch(service_account_id_patch),
)
.route(
"/v1/service_account/:id/_attr/:attr",
"/v1/service_account/{id}/_attr/{attr}",
get(service_account_id_get_attr)
.put(service_account_id_put_attr)
.post(service_account_id_post_attr)
.delete(service_account_id_delete_attr),
)
// .route("/v1/service_account/:id/_lock", get(|| async { "TODO" }))
// .route("/v1/service_account/{id}/_lock", get(|| async { "TODO" }))
.route(
"/v1/service_account/:id/_into_person",
"/v1/service_account/{id}/_into_person",
#[allow(deprecated)]
post(service_account_into_person),
)
.route(
"/v1/service_account/:id/_api_token",
"/v1/service_account/{id}/_api_token",
post(service_account_api_token_post).get(service_account_api_token_get),
)
.route(
"/v1/service_account/:id/_api_token/:token_id",
"/v1/service_account/{id}/_api_token/{token_id}",
delete(service_account_api_token_delete),
)
// .route(
// "/v1/service_account/:id/_credential",
// "/v1/service_account/{id}/_credential",
// get(|| async { "TODO" }),
// )
.route(
"/v1/service_account/:id/_credential/_generate",
"/v1/service_account/{id}/_credential/_generate",
get(service_account_credential_generate),
)
.route(
"/v1/service_account/:id/_credential/_status",
"/v1/service_account/{id}/_credential/_status",
get(service_account_id_credential_status_get),
)
// .route(
// "/v1/service_account/:id/_credential/:cid/_lock",
// "/v1/service_account/{id}/_credential/{cid}/_lock",
// get(|| async { "TODO" }),
// )
.route(
"/v1/service_account/:id/_ssh_pubkeys",
"/v1/service_account/{id}/_ssh_pubkeys",
get(service_account_id_ssh_pubkeys_get).post(service_account_id_ssh_pubkeys_post),
)
.route(
"/v1/service_account/:id/_ssh_pubkeys/:tag",
"/v1/service_account/{id}/_ssh_pubkeys/{tag}",
get(service_account_id_ssh_pubkeys_tag_get)
.delete(service_account_id_ssh_pubkeys_tag_delete),
)
.route(
"/v1/service_account/:id/_unix",
"/v1/service_account/{id}/_unix",
post(service_account_id_unix_post),
)
.route(
"/v1/account/:id/_unix/_auth",
"/v1/account/{id}/_unix/_auth",
post(account_id_unix_auth_post),
)
.route("/v1/account/:id/_unix/_token", post(account_id_unix_token))
.route("/v1/account/{id}/_unix/_token", post(account_id_unix_token))
.route(
"/v1/account/:id/_radius/_token",
"/v1/account/{id}/_radius/_token",
post(account_id_radius_token_post),
)
.route(
"/v1/account/:id/_ssh_pubkeys",
"/v1/account/{id}/_ssh_pubkeys",
#[allow(deprecated)]
get(account_id_ssh_pubkeys_get),
)
.route(
"/v1/account/:id/_ssh_pubkeys/:tag",
"/v1/account/{id}/_ssh_pubkeys/{tag}",
get(account_id_ssh_pubkeys_tag_get),
)
.route(
"/v1/account/:id/_user_auth_token",
"/v1/account/{id}/_user_auth_token",
get(account_id_user_auth_token_get),
)
.route(
"/v1/account/:id/_user_auth_token/:token_id",
"/v1/account/{id}/_user_auth_token/{token_id}",
delete(account_user_auth_token_delete),
)
.route(
@ -3355,23 +3358,23 @@ pub(crate) fn route_setup(state: ServerState) -> Router<ServerState> {
post(super::v1_domain::image_post).delete(super::v1_domain::image_delete),
)
.route(
"/v1/domain/_attr/:attr",
"/v1/domain/_attr/{attr}",
get(domain_attr_get)
.put(domain_attr_put)
.delete(domain_attr_delete),
)
.route("/v1/group/:id/_unix/_token", get(group_id_unix_token_get))
.route("/v1/group/:id/_unix", post(group_id_unix_post))
.route("/v1/group/{id}/_unix/_token", get(group_id_unix_token_get))
.route("/v1/group/{id}/_unix", post(group_id_unix_post))
.route("/v1/group", get(group_get).post(group_post))
.route("/v1/group/_search/:id", get(group_search_id))
.route("/v1/group/_search/{id}", get(group_search_id))
.route(
"/v1/group/:id",
"/v1/group/{id}",
get(group_id_get)
.patch(group_id_patch)
.delete(group_id_delete),
)
.route(
"/v1/group/:id/_attr/:attr",
"/v1/group/{id}/_attr/{attr}",
delete(group_id_attr_delete)
.get(group_id_attr_get)
.put(group_id_attr_put)
@ -3380,22 +3383,22 @@ pub(crate) fn route_setup(state: ServerState) -> Router<ServerState> {
.with_state(state.clone())
.route("/v1/system", get(system_get))
.route(
"/v1/system/_attr/:attr",
"/v1/system/_attr/{attr}",
get(system_attr_get)
.post(system_attr_post)
.put(system_attr_put)
.delete(system_attr_delete),
)
.route("/v1/recycle_bin", get(recycle_bin_get))
.route("/v1/recycle_bin/:id", get(recycle_bin_id_get))
.route("/v1/recycle_bin/{id}", get(recycle_bin_id_get))
.route(
"/v1/recycle_bin/:id/_revive",
"/v1/recycle_bin/{id}/_revive",
post(recycle_bin_revive_id_post),
)
// .route("/v1/access_profile", get(|| async { "TODO" }))
// .route("/v1/access_profile/:id", get(|| async { "TODO" }))
// .route("/v1/access_profile/{id}", get(|| async { "TODO" }))
// .route(
// "/v1/access_profile/:id/_attr/:attr",
// "/v1/access_profile/{id}/_attr/{attr}",
// get(|| async { "TODO" }),
// )
.route("/v1/auth", post(auth))

View file

@ -16,6 +16,7 @@ use kanidm_proto::scim_v1::{
};
use kanidm_proto::v1::Entry as ProtoEntry;
use kanidmd_lib::prelude::*;
use crate::https::apidocs::response_schema::ScimEntry;
const DEFAULT_SCIM_SYNC_BYTES: usize = 1024 * 1024 * 32;
@ -319,6 +320,7 @@ async fn scim_sync_get(
.map_err(WebError::from)
}
#[utoipa::path(
get,
path = "/scim/v1/Entry/{id}",
@ -390,23 +392,23 @@ pub fn route_setup() -> Router<ServerState> {
get(sync_account_get).post(sync_account_post),
)
.route(
"/v1/sync_account/:id",
"/v1/sync_account/{id}",
get(sync_account_id_get).patch(sync_account_id_patch),
)
.route(
"/v1/sync_account/:id/_attr/:attr",
"/v1/sync_account/{id}/_attr/{attr}",
get(sync_account_id_attr_get).put(sync_account_id_attr_put),
)
.route(
"/v1/sync_account/:id/_finalise",
"/v1/sync_account/{id}/_finalise",
get(sync_account_id_finalise_get),
)
.route(
"/v1/sync_account/:id/_terminate",
"/v1/sync_account/{id}/_terminate",
get(sync_account_id_terminate_get),
)
.route(
"/v1/sync_account/:id/_sync_token",
"/v1/sync_account/{id}/_sync_token",
post(sync_account_token_post).delete(sync_account_token_delete),
)
// https://datatracker.ietf.org/doc/html/rfc7644#section-3.2
@ -473,11 +475,11 @@ pub fn route_setup() -> Router<ServerState> {
// Entry /Entry/{id} GET Retrieve a generic entry
// of any kind from the database.
// {id} is any unique id.
.route("/scim/v1/Entry/:id", get(scim_entry_id_get))
.route("/scim/v1/Entry/{id}", get(scim_entry_id_get))
// Person /Person/{id} GET Retrieve a a person from the
// database.
// {id} is any unique id.
.route("/scim/v1/Person/:id", get(scim_person_id_get))
.route("/scim/v1/Person/{id}", get(scim_person_id_get))
//
// Sync /Sync GET Retrieve the current
// sync state associated

View file

@ -9,7 +9,7 @@ pub fn admin_router() -> Router<ServerState> {
let unguarded_router = Router::new()
.route("/persons", get(persons::view_persons_get))
.route(
"/person/:person_uuid/view",
"/person/{person_uuid}/view",
get(persons::view_person_view_get),
);

View file

@ -5,6 +5,7 @@ use crate::https::views::navbar::NavbarCtx;
use crate::https::views::Urls;
use crate::https::ServerState;
use askama::Template;
use askama_web::WebTemplate;
use axum::extract::{Path, State};
use axum::http::Uri;
use axum::response::{ErrorResponse, IntoResponse, Response};
@ -34,27 +35,27 @@ const PERSON_ATTRIBUTES: [Attribute; 9] = [
Attribute::DirectMemberOf,
];
#[derive(Template)]
#[derive(Template, WebTemplate)]
#[template(path = "admin/admin_panel_template.html")]
pub(crate) struct PersonsView {
navbar_ctx: NavbarCtx,
partial: PersonsPartialView,
}
#[derive(Template)]
#[derive(Template, WebTemplate)]
#[template(path = "admin/admin_persons_partial.html")]
struct PersonsPartialView {
persons: Vec<(ScimPerson, ScimEffectiveAccess)>,
}
#[derive(Template)]
#[derive(Template, WebTemplate)]
#[template(path = "admin/admin_panel_template.html")]
struct PersonView {
partial: PersonViewPartial,
navbar_ctx: NavbarCtx,
}
#[derive(Template)]
#[derive(Template, WebTemplate)]
#[template(path = "admin/admin_person_view_partial.html")]
struct PersonViewPartial {
person: ScimPerson,

View file

@ -1,4 +1,5 @@
use askama::Template;
use askama_web::WebTemplate;
use axum::{
extract::State,
http::uri::Uri,
@ -16,14 +17,14 @@ use crate::https::{
extractors::DomainInfo, extractors::VerifiedClientInformation, middleware::KOpId, ServerState,
};
#[derive(Template)]
#[derive(Template, WebTemplate)]
#[template(path = "apps.html")]
struct AppsView {
navbar_ctx: NavbarCtx,
apps_partial: AppsPartialView,
}
#[derive(Template)]
#[derive(Template, WebTemplate)]
#[template(path = "apps_partial.html")]
struct AppsPartialView {
apps: Vec<AppLink>,

View file

@ -1,8 +1,8 @@
use askama::Template;
use askama_axum::IntoResponse;
use askama_web::WebTemplate;
use axum::extract::State;
use axum::response::Response;
use axum::response::{IntoResponse, Response};
use axum::Extension;
use axum_extra::extract::CookieJar;
@ -23,14 +23,14 @@ use crate::https::views::errors::HtmxError;
use crate::https::views::login::{LoginDisplayCtx, Reauth, ReauthPurpose};
use crate::https::ServerState;
#[derive(Template)]
#[derive(Template, WebTemplate)]
#[template(path = "user_settings.html")]
struct ProfileView {
navbar_ctx: NavbarCtx,
profile_partial: EnrolDeviceView,
}
#[derive(Template)]
#[derive(Template, WebTemplate)]
#[template(path = "enrol_device.html")]
pub(crate) struct EnrolDeviceView {
menu_active_item: ProfileMenuItems,

View file

@ -7,6 +7,7 @@ use crate::https::{
ServerState,
};
use askama::Template;
use askama_web::WebTemplate;
use axum::{
extract::State,
response::{IntoResponse, Redirect, Response},
@ -91,7 +92,7 @@ pub struct LoginDisplayCtx {
pub error: Option<LoginError>,
}
#[derive(Template)]
#[derive(Template, WebTemplate)]
#[template(path = "login.html")]
struct LoginView {
display_ctx: LoginDisplayCtx,
@ -105,7 +106,7 @@ pub struct Mech<'a> {
autofocus: bool,
}
#[derive(Template)]
#[derive(Template, WebTemplate)]
#[template(path = "login_mech_choose.html")]
struct LoginMechView<'a> {
display_ctx: LoginDisplayCtx,
@ -119,7 +120,7 @@ enum LoginTotpError {
Syntax,
}
#[derive(Template)]
#[derive(Template, WebTemplate)]
#[template(path = "login_totp.html")]
struct LoginTotpView {
display_ctx: LoginDisplayCtx,
@ -127,20 +128,20 @@ struct LoginTotpView {
errors: LoginTotpError,
}
#[derive(Template)]
#[derive(Template, WebTemplate)]
#[template(path = "login_password.html")]
struct LoginPasswordView {
display_ctx: LoginDisplayCtx,
password: String,
}
#[derive(Template)]
#[derive(Template, WebTemplate)]
#[template(path = "login_backupcode.html")]
struct LoginBackupCodeView {
display_ctx: LoginDisplayCtx,
}
#[derive(Template)]
#[derive(Template, WebTemplate)]
#[template(path = "login_webauthn.html")]
struct LoginWebauthnView {
display_ctx: LoginDisplayCtx,
@ -150,7 +151,7 @@ struct LoginWebauthnView {
chal: String,
}
#[derive(Template)]
#[derive(Template, WebTemplate)]
#[template(path = "login_denied.html")]
struct LoginDeniedView {
display_ctx: LoginDisplayCtx,

View file

@ -1,4 +1,5 @@
use askama::Template;
use askama_web::WebTemplate;
use axum::{
response::Redirect,
@ -29,7 +30,7 @@ mod oauth2;
mod profile;
mod reset;
#[derive(Template)]
#[derive(Template, WebTemplate)]
#[template(path = "unrecoverable_error.html")]
struct UnrecoverableErrorView {
err_code: OperationError,
@ -38,7 +39,7 @@ struct UnrecoverableErrorView {
domain_info: DomainInfoRead,
}
#[derive(Template)]
#[derive(Template, WebTemplate)]
#[template(path = "admin/error_toast.html")]
struct ErrorToastPartial {
err_code: OperationError,
@ -159,7 +160,9 @@ where
#[cfg(test)]
mod tests {
use askama_axum::IntoResponse;
// use askama_axum::IntoResponse;
use axum::response::IntoResponse;
use super::*;
#[tokio::test]

View file

@ -11,6 +11,7 @@ use kanidm_proto::internal::COOKIE_OAUTH2_REQ;
use std::collections::BTreeSet;
use askama::Template;
use askama_web::WebTemplate;
#[cfg(feature = "dev-oauth2-device-flow")]
use axum::http::StatusCode;
@ -27,7 +28,7 @@ use serde::Deserialize;
use super::login::{LoginDisplayCtx, Oauth2Ctx};
use super::{cookies, UnrecoverableErrorView};
#[derive(Template)]
#[derive(Template, WebTemplate)]
#[template(path = "oauth2_consent_request.html")]
struct ConsentRequestView {
client_name: String,
@ -37,7 +38,7 @@ struct ConsentRequestView {
redirect: Option<String>,
}
#[derive(Template)]
#[derive(Template, WebTemplate)]
#[template(path = "oauth2_access_denied.html")]
struct AccessDeniedView {
operation_id: Uuid,
@ -124,7 +125,7 @@ async fn oauth2_auth_req(
[
(HX_REDIRECT, redirect_uri.as_str().to_string()),
(
ACCESS_CONTROL_ALLOW_ORIGIN.as_str(),
ACCESS_CONTROL_ALLOW_ORIGIN,
redirect_uri.origin().ascii_serialization(),
),
],
@ -262,7 +263,7 @@ pub async fn view_consent_post(
[
(HX_REDIRECT, success.redirect_uri.as_str().to_string()),
(
ACCESS_CONTROL_ALLOW_ORIGIN.as_str(),
ACCESS_CONTROL_ALLOW_ORIGIN,
success.redirect_uri.origin().ascii_serialization(),
),
],
@ -276,7 +277,7 @@ pub async fn view_consent_post(
[
(HX_REDIRECT, redirect_uri.as_str().to_string()),
(
ACCESS_CONTROL_ALLOW_ORIGIN.as_str(),
ACCESS_CONTROL_ALLOW_ORIGIN,
redirect_uri.origin().ascii_serialization(),
),
],

View file

@ -3,6 +3,7 @@ use crate::https::extractors::{DomainInfo, VerifiedClientInformation};
use crate::https::middleware::KOpId;
use crate::https::ServerState;
use askama::Template;
use askama_web::WebTemplate;
use axum::extract::State;
use axum::response::Response;
use axum::Extension;
@ -14,14 +15,14 @@ use super::errors::HtmxError;
use super::login::{LoginDisplayCtx, Reauth, ReauthPurpose};
use super::navbar::NavbarCtx;
#[derive(Template)]
#[derive(Template, WebTemplate)]
#[template(path = "user_settings.html")]
pub(crate) struct ProfileView {
navbar_ctx: NavbarCtx,
profile_partial: ProfilePartialView,
}
#[derive(Template, Clone)]
#[derive(Template, WebTemplate, Clone)]
#[template(path = "user_settings_profile_partial.html")]
struct ProfilePartialView {
menu_active_item: ProfileMenuItems,

View file

@ -1,4 +1,5 @@
use askama::Template;
use askama_web::WebTemplate;
use axum::extract::{Query, State};
use axum::http::{StatusCode, Uri};
use axum::response::{ErrorResponse, IntoResponse, Redirect, Response};
@ -42,21 +43,21 @@ use crate::https::ServerState;
use super::UnrecoverableErrorView;
#[derive(Template)]
#[derive(Template, WebTemplate)]
#[template(path = "user_settings.html")]
struct ProfileView {
navbar_ctx: NavbarCtx,
profile_partial: CredStatusView,
}
#[derive(Template)]
#[derive(Template, WebTemplate)]
#[template(path = "credentials_reset_form.html")]
struct ResetCredFormView {
domain_info: DomainInfoRead,
wrong_code: bool,
}
#[derive(Template)]
#[derive(Template, WebTemplate)]
#[template(path = "credentials_reset.html")]
struct CredResetView {
domain_info: DomainInfoRead,
@ -64,7 +65,7 @@ struct CredResetView {
credentials_update_partial: CredResetPartialView,
}
#[derive(Template)]
#[derive(Template, WebTemplate)]
#[template(path = "credentials_status.html")]
struct CredStatusView {
domain_info: DomainInfoRead,
@ -79,7 +80,7 @@ struct SshKey {
comment: Option<String>,
}
#[derive(Template)]
#[derive(Template, WebTemplate)]
#[template(path = "credentials_update_partial.html")]
struct CredResetPartialView {
ext_cred_portal: CUExtPortal,
@ -104,19 +105,19 @@ pub(crate) struct ResetTokenParam {
token: Option<String>,
}
#[derive(Template)]
#[derive(Template, WebTemplate)]
#[template(path = "credential_update_add_password_partial.html")]
struct AddPasswordPartial {
check_res: PwdCheckResult,
}
#[derive(Template)]
#[derive(Template, WebTemplate)]
#[template(path = "credential_update_set_unixcred_partial.html")]
struct SetUnixCredPartial {
check_res: PwdCheckResult,
}
#[derive(Template)]
#[derive(Template, WebTemplate)]
#[template(path = "credential_update_add_ssh_publickey_partial.html")]
struct AddSshPublicKeyPartial {
title_error: Option<String>,
@ -160,7 +161,7 @@ pub(crate) struct NewTotp {
ignore_broken_app: bool,
}
#[derive(Template)]
#[derive(Template, WebTemplate)]
#[template(path = "credential_update_add_passkey_partial.html")]
struct AddPasskeyPartial {
// Passkey challenge for adding a new passkey
@ -215,7 +216,7 @@ pub(crate) struct TotpCheck {
taken_name: Option<String>,
}
#[derive(Template)]
#[derive(Template, WebTemplate)]
#[template(path = "credential_update_add_totp_partial.html")]
struct AddTotpPartial {
totp_init: Option<TotpInit>,
@ -691,14 +692,14 @@ pub(crate) async fn view_new_pwd(
VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
DomainInfo(domain_info): DomainInfo,
jar: CookieJar,
opt_form: Option<Form<NewPassword>>,
opt_form: Result<Form<NewPassword>, axum::extract::rejection::FormRejection>,
) -> axum::response::Result<Response> {
let cu_session_token: CUSessionToken = get_cu_session(&jar).await?;
let swapped_handler_trigger =
HxResponseTrigger::after_swap([HxEvent::new("addPasswordSwapped".to_string())]);
let new_passwords = match opt_form {
None => {
Err(_) => {
return Ok((
swapped_handler_trigger,
AddPasswordPartial {
@ -707,7 +708,7 @@ pub(crate) async fn view_new_pwd(
)
.into_response());
}
Some(Form(new_passwords)) => new_passwords,
Ok(Form(new_passwords)) => new_passwords,
};
let pwd_equal = new_passwords.new_password == new_passwords.new_password_check;
@ -822,14 +823,14 @@ pub(crate) async fn view_set_unixcred(
VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
DomainInfo(domain_info): DomainInfo,
jar: CookieJar,
opt_form: Option<Form<NewPassword>>,
opt_form: Result<Form<NewPassword>, axum::extract::rejection::FormRejection>,
) -> axum::response::Result<Response> {
let cu_session_token: CUSessionToken = get_cu_session(&jar).await?;
let swapped_handler_trigger =
HxResponseTrigger::after_swap([HxEvent::new("addPasswordSwapped".to_string())]);
let new_passwords = match opt_form {
None => {
Err(_) => {
return Ok((
swapped_handler_trigger,
SetUnixCredPartial {
@ -838,7 +839,7 @@ pub(crate) async fn view_set_unixcred(
)
.into_response());
}
Some(Form(new_passwords)) => new_passwords,
Ok(Form(new_passwords)) => new_passwords,
};
let pwd_equal = new_passwords.new_password == new_passwords.new_password_check;
@ -887,6 +888,7 @@ struct AddSshPublicKeyError {
title: Option<String>,
}
#[axum::debug_handler]
pub(crate) async fn view_add_ssh_publickey(
State(state): State<ServerState>,
Extension(kopid): Extension<KOpId>,
@ -894,12 +896,13 @@ pub(crate) async fn view_add_ssh_publickey(
VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
DomainInfo(domain_info): DomainInfo,
jar: CookieJar,
opt_form: Option<Form<NewPublicKey>>,
) -> axum::response::Result<Response> {
opt_form: Result<Form<NewPublicKey>, axum::extract::rejection::FormRejection>,
) -> impl IntoResponse {
let cu_session_token: CUSessionToken = get_cu_session(&jar).await?;
let new_key = match opt_form {
None => {
Err(_e) => {
return Ok((AddSshPublicKeyPartial {
title_error: None,
key_error: None,
@ -907,7 +910,7 @@ pub(crate) async fn view_add_ssh_publickey(
},)
.into_response());
}
Some(Form(new_key)) => new_key,
Ok(Form(new_key)) => new_key,
};
let (

View file

@ -38,7 +38,7 @@ mod utils;
use crate::actors::{QueryServerReadV1, QueryServerWriteV1};
use crate::admin::AdminActor;
use crate::config::{Configuration, ServerRole};
use crate::config::ServerRole;
use crate::interval::IntervalActor;
use crate::utils::touch_file_or_quit;
use compact_jwt::{JwsHs256Signer, JwsSigner};
@ -58,6 +58,7 @@ use tokio::sync::broadcast;
use tokio::sync::mpsc;
use tokio::sync::Notify;
use tokio::task;
use config::Configuration;
// === internal setup helpers
@ -1204,4 +1205,4 @@ pub async fn create_server_core(
tx: broadcast_tx,
handles,
})
}
}

View file

@ -1,5 +1,5 @@
(% macro string_attr(dispname, name, value, editable, attribute) %)
(% if scim_effective_access.search.check(attribute|as_ref) %)
(% if scim_effective_access.search.check(attribute | 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">

View file

@ -14,7 +14,8 @@
<hr>
(% if scim_effective_access.search.check(Attribute::Mail|as_ref) %)
<!-- TODO: issues with as_ref -->
(% if scim_effective_access.search.check(Attribute::Mail | ref) %)
<label class="mt-3 fw-bold">Emails</label>
<form hx-validate="true" hx-ext="bs-validation">
(% if person.mails.len() == 0 %)
@ -33,7 +34,8 @@
</form>
(% endif %)
(% if scim_effective_access.search.check(Attribute::DirectMemberOf|as_ref) %)
<!-- TODO: issues with as_ref -->
(% if scim_effective_access.search.check(Attribute::DirectMemberOf | ref) %)
<label class="mt-3 fw-bold">DirectMemberOf</label>
<form hx-validate="true" hx-ext="bs-validation">
(% if person.groups.len() == 0 %)