mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 04:27:02 +01:00
Merge branch 'master' of github.com:alteriks/kanidm
This commit is contained in:
commit
48f7324080
|
@ -2,9 +2,21 @@
|
|||
|
||||
root = true
|
||||
|
||||
|
||||
|
||||
[*.md]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
max_line_length = 100
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.js]
|
||||
tab_width = 4
|
||||
max_line_length = 120
|
||||
print_width = 120
|
||||
|
||||
[*.mjs]
|
||||
tab_width = 4
|
||||
max_line_length = 120
|
||||
print_width = 120
|
12
.github/workflows/clippy.yml
vendored
12
.github/workflows/clippy.yml
vendored
|
@ -19,7 +19,7 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.6
|
||||
uses: mozilla-actions/sccache-action@v0.0.7
|
||||
with:
|
||||
version: "v0.4.2"
|
||||
- name: Install dependencies
|
||||
|
@ -27,11 +27,13 @@ jobs:
|
|||
sudo apt-get update && \
|
||||
sudo apt-get install -y \
|
||||
libpam0g-dev \
|
||||
libudev-dev \
|
||||
libselinux1-dev \
|
||||
libssl-dev \
|
||||
libsystemd-dev \
|
||||
libtss2-dev \
|
||||
libudev-dev \
|
||||
pkg-config \
|
||||
tpm-udev \
|
||||
libtss2-dev
|
||||
tpm-udev
|
||||
- name: "Run clippy"
|
||||
run: cargo clippy --lib --bins --examples --all-features
|
||||
fmt:
|
||||
|
@ -39,7 +41,7 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.6
|
||||
uses: mozilla-actions/sccache-action@v0.0.7
|
||||
with:
|
||||
version: "v0.4.2"
|
||||
- name: "Run cargo fmt"
|
||||
|
|
3
.github/workflows/docker_build_kanidm.yml
vendored
3
.github/workflows/docker_build_kanidm.yml
vendored
|
@ -71,7 +71,8 @@ jobs:
|
|||
with:
|
||||
name: kanidm-docker
|
||||
path: /tmp
|
||||
|
||||
- name: Set up ORAS
|
||||
uses: oras-project/setup-oras@v1
|
||||
- name: Push image to GHCR
|
||||
run: |
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | \
|
||||
|
|
3
.github/workflows/docker_build_kanidmd.yml
vendored
3
.github/workflows/docker_build_kanidmd.yml
vendored
|
@ -88,7 +88,8 @@ jobs:
|
|||
with:
|
||||
name: kanidmd-docker
|
||||
path: /tmp
|
||||
|
||||
- name: Set up ORAS
|
||||
uses: oras-project/setup-oras@v1
|
||||
- name: Push image to GHCR
|
||||
run: |
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | \
|
||||
|
|
3
.github/workflows/docker_build_radiusd.yml
vendored
3
.github/workflows/docker_build_radiusd.yml
vendored
|
@ -70,7 +70,8 @@ jobs:
|
|||
with:
|
||||
name: radius-docker
|
||||
path: /tmp
|
||||
|
||||
- name: Set up ORAS
|
||||
uses: oras-project/setup-oras@v1
|
||||
# Docker won't directly import OCI images and keep their multi-arch
|
||||
# features, but ORAS will: https://oras.land/docs/commands/oras_copy
|
||||
- name: Push image to GHCR
|
||||
|
|
21
.github/workflows/javascript_lint.yml
vendored
Normal file
21
.github/workflows/javascript_lint.yml
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
name: Javascript Linting
|
||||
"on":
|
||||
- push
|
||||
- pull_request
|
||||
jobs:
|
||||
javascript_lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Run ESLint to check Javascript files
|
||||
run:
|
||||
make eslint
|
||||
javascript_fmt:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Run Prettier to check Javascript files
|
||||
run:
|
||||
make prettier
|
||||
|
2
.github/workflows/kanidm_individual_book.yml
vendored
2
.github/workflows/kanidm_individual_book.yml
vendored
|
@ -24,7 +24,7 @@ jobs:
|
|||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.6
|
||||
uses: mozilla-actions/sccache-action@v0.0.7
|
||||
with:
|
||||
version: "v0.4.2"
|
||||
- name: Install deps
|
||||
|
|
13
.github/workflows/rust_build.yml
vendored
13
.github/workflows/rust_build.yml
vendored
|
@ -27,7 +27,7 @@ jobs:
|
|||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.6
|
||||
uses: mozilla-actions/sccache-action@v0.0.7
|
||||
with:
|
||||
version: "v0.4.2"
|
||||
|
||||
|
@ -37,7 +37,8 @@ jobs:
|
|||
sudo apt-get install -y \
|
||||
libpam0g-dev \
|
||||
libudev-dev \
|
||||
libssl-dev
|
||||
libssl-dev \
|
||||
libsystemd-dev
|
||||
|
||||
- name: "Build the workspace"
|
||||
run: cargo build --workspace
|
||||
|
@ -74,7 +75,7 @@ jobs:
|
|||
with:
|
||||
toolchain: ${{ matrix.rust_version }}
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.6
|
||||
uses: mozilla-actions/sccache-action@v0.0.7
|
||||
with:
|
||||
version: "v0.4.2"
|
||||
|
||||
|
@ -84,7 +85,8 @@ jobs:
|
|||
sudo apt-get install -y \
|
||||
libpam0g-dev \
|
||||
libudev-dev \
|
||||
libssl-dev
|
||||
libssl-dev \
|
||||
libsystemd-dev
|
||||
|
||||
- name: "Build the workspace"
|
||||
run: cargo build --workspace
|
||||
|
@ -116,7 +118,7 @@ jobs:
|
|||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.6
|
||||
uses: mozilla-actions/sccache-action@v0.0.7
|
||||
with:
|
||||
version: "v0.4.2"
|
||||
|
||||
|
@ -127,6 +129,7 @@ jobs:
|
|||
libpam0g-dev \
|
||||
libudev-dev \
|
||||
libssl-dev \
|
||||
libsystemd-dev \
|
||||
ripgrep
|
||||
- name: "Run the release build test script"
|
||||
env:
|
||||
|
|
2
.github/workflows/windows_build.yml
vendored
2
.github/workflows/windows_build.yml
vendored
|
@ -28,7 +28,7 @@ jobs:
|
|||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.6
|
||||
uses: mozilla-actions/sccache-action@v0.0.7
|
||||
with:
|
||||
version: "v0.4.2"
|
||||
- run: cargo build -p kanidm_client -p kanidm_tools --bin kanidm
|
||||
|
|
9
.gitignore
vendored
9
.gitignore
vendored
|
@ -17,9 +17,12 @@ tools/orca/example_profiles/small/orca-edited.toml
|
|||
/docs/
|
||||
# webui things we don't need
|
||||
*.d.ts
|
||||
|
||||
server/web_ui/*/pkg/*.js
|
||||
|
||||
# coverage-related things
|
||||
*.profraw
|
||||
tarpaulin-report.html
|
||||
|
||||
# kanidm simple packaging
|
||||
deployment-config/
|
||||
kanidm_simple_pkg/
|
||||
|
@ -35,9 +38,13 @@ pykanidm/site/
|
|||
# oauth2 integration test things
|
||||
scripts/oauth_proxy/client.secret
|
||||
scripts/oauth_proxy/envfile
|
||||
|
||||
# local config things
|
||||
.envrc
|
||||
|
||||
# IDEs
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# javascript test things
|
||||
node_modules/
|
|
@ -1,2 +1,4 @@
|
|||
# yale-mistakes were made
|
||||
/pykanidm/* @yaleman
|
||||
# Least qualified nix guy :P
|
||||
shell.nix @cebbinghaus
|
||||
|
|
|
@ -45,6 +45,9 @@
|
|||
- Chris Olstrom (colstrom)
|
||||
- Christopher-Robin (cebbinghaus)
|
||||
- Krzysztof Dajka (alteriks)
|
||||
- Fabian Kammel (datosh)
|
||||
- Andris Raugulis (arthepsy)
|
||||
- Jason (argonaut0)
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
|
|
1716
Cargo.lock
generated
1716
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
113
Cargo.toml
113
Cargo.toml
|
@ -1,10 +1,10 @@
|
|||
[workspace.package]
|
||||
version = "1.5.0-dev"
|
||||
version = "1.6.0-dev"
|
||||
authors = [
|
||||
"William Brown <william@blackhats.net.au>",
|
||||
"James Hodgkinson <james@terminaloutcomes.com>",
|
||||
]
|
||||
rust-version = "1.79"
|
||||
rust-version = "1.80"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
homepage = "https://github.com/kanidm/kanidm/"
|
||||
|
@ -120,27 +120,29 @@ codegen-units = 256
|
|||
|
||||
# kanidm-hsm-crypto = { path = "../hsm-crypto" }
|
||||
|
||||
[workspace.dependencies]
|
||||
kanidmd_core = { path = "./server/core", version = "=1.5.0-dev" }
|
||||
kanidmd_lib = { path = "./server/lib", version = "=1.5.0-dev" }
|
||||
kanidmd_lib_macros = { path = "./server/lib-macros", version = "=1.5.0-dev" }
|
||||
kanidmd_testkit = { path = "./server/testkit", version = "=1.5.0-dev" }
|
||||
kanidm_build_profiles = { path = "./libs/profiles", version = "=1.5.0-dev" }
|
||||
kanidm_client = { path = "./libs/client", version = "=1.5.0-dev" }
|
||||
kanidm-hsm-crypto = "^0.2.0"
|
||||
kanidm_lib_crypto = { path = "./libs/crypto", version = "=1.5.0-dev" }
|
||||
kanidm_lib_file_permissions = { path = "./libs/file_permissions", version = "=1.5.0-dev" }
|
||||
kanidm_proto = { path = "./proto", version = "=1.5.0-dev" }
|
||||
kanidm_unix_common = { path = "./unix_integration/common", version = "=1.5.0-dev" }
|
||||
kanidm_utils_users = { path = "./libs/users", version = "=1.5.0-dev" }
|
||||
scim_proto = { path = "./libs/scim_proto", version = "=1.5.0-dev" }
|
||||
sketching = { path = "./libs/sketching", version = "=1.5.0-dev" }
|
||||
libnss = { git = "https://github.com/Firstyear/libnss-rs.git", branch = "20250207-freebsd" }
|
||||
|
||||
anyhow = { version = "1.0.93" }
|
||||
[workspace.dependencies]
|
||||
kanidmd_core = { path = "./server/core", version = "=1.6.0-dev" }
|
||||
kanidmd_lib = { path = "./server/lib", version = "=1.6.0-dev" }
|
||||
kanidmd_lib_macros = { path = "./server/lib-macros", version = "=1.6.0-dev" }
|
||||
kanidmd_testkit = { path = "./server/testkit", version = "=1.6.0-dev" }
|
||||
kanidm_build_profiles = { path = "./libs/profiles", version = "=1.6.0-dev" }
|
||||
kanidm_client = { path = "./libs/client", version = "=1.6.0-dev" }
|
||||
kanidm-hsm-crypto = "^0.2.0"
|
||||
kanidm_lib_crypto = { path = "./libs/crypto", version = "=1.6.0-dev" }
|
||||
kanidm_lib_file_permissions = { path = "./libs/file_permissions", version = "=1.6.0-dev" }
|
||||
kanidm_proto = { path = "./proto", version = "=1.6.0-dev" }
|
||||
kanidm_unix_common = { path = "./unix_integration/common", version = "=1.6.0-dev" }
|
||||
kanidm_utils_users = { path = "./libs/users", version = "=1.6.0-dev" }
|
||||
scim_proto = { path = "./libs/scim_proto", version = "=1.6.0-dev" }
|
||||
sketching = { path = "./libs/sketching", version = "=1.6.0-dev" }
|
||||
|
||||
anyhow = { version = "1.0.95" }
|
||||
argon2 = { version = "0.5.3", features = ["alloc"] }
|
||||
askama = { version = "0.12.1", features = ["serde", "with-axum"] }
|
||||
askama_axum = { version = "0.4.0" }
|
||||
async-trait = "^0.1.83"
|
||||
async-trait = "^0.1.85"
|
||||
axum = { version = "0.7.9", features = [
|
||||
"form",
|
||||
"json",
|
||||
|
@ -154,30 +156,30 @@ axum = { version = "0.7.9", features = [
|
|||
axum-htmx = { version = "0.5.0", features = ["serde", "guards"] }
|
||||
base32 = "^0.5.1"
|
||||
base64 = "^0.22.1"
|
||||
base64urlsafedata = "0.5.0"
|
||||
bitflags = "^2.6.0"
|
||||
base64urlsafedata = "0.5.1"
|
||||
bitflags = "^2.8.0"
|
||||
bytes = "^1.9.0"
|
||||
clap = { version = "^4.5.21", features = ["derive", "env"] }
|
||||
clap_complete = "^4.5.38"
|
||||
clap = { version = "^4.5.27", features = ["derive", "env"] }
|
||||
clap_complete = "^4.5.42"
|
||||
# Forced by saffron/cron
|
||||
chrono = "^0.4.35"
|
||||
chrono = "^0.4.39"
|
||||
compact_jwt = { version = "^0.4.2", default-features = false }
|
||||
concread = "^0.5.3"
|
||||
cron = "0.12.1"
|
||||
cron = "0.15.0"
|
||||
crossbeam = "0.8.4"
|
||||
csv = "1.3.1"
|
||||
dialoguer = "0.10.4"
|
||||
dialoguer = "0.11.0"
|
||||
dhat = "0.3.3"
|
||||
dyn-clone = "^1.0.17"
|
||||
fernet = "^0.2.1"
|
||||
filetime = "^0.2.24"
|
||||
fs4 = "^0.8.3"
|
||||
fs4 = "^0.12.0"
|
||||
futures = "^0.3.31"
|
||||
futures-util = { version = "^0.3.30", features = ["sink"] }
|
||||
gix = { version = "0.64.0", default-features = false }
|
||||
hashbrown = { version = "0.14.3", features = ["serde", "inline-more", "ahash"] }
|
||||
hex = "^0.4.3"
|
||||
http = "1.1.0"
|
||||
http = "1.2.0"
|
||||
hyper = { version = "1.5.1", features = [
|
||||
"full",
|
||||
] } # hyper full includes client/server/http2
|
||||
|
@ -196,10 +198,10 @@ lazy_static = "^1.5.0"
|
|||
ldap3_client = "^0.5.2"
|
||||
ldap3_proto = { version = "^0.5.2", features = ["serde"] }
|
||||
|
||||
libc = "^0.2.167"
|
||||
libc = "^0.2.168"
|
||||
libnss = "^0.8.0"
|
||||
libsqlite3-sys = "^0.25.2"
|
||||
lodepng = "3.10.7"
|
||||
lodepng = "3.11.0"
|
||||
lru = "^0.12.5"
|
||||
mathru = "^0.13.0"
|
||||
mimalloc = "0.1.43"
|
||||
|
@ -207,31 +209,32 @@ notify-debouncer-full = { version = "0.1" }
|
|||
num_enum = "^0.5.11"
|
||||
oauth2_ext = { version = "^4.4.2", package = "oauth2", default-features = false }
|
||||
openssl-sys = "^0.9"
|
||||
openssl = "^0.10.68"
|
||||
openssl = "^0.10.70"
|
||||
|
||||
opentelemetry = { version = "0.20.0" }
|
||||
opentelemetry_api = { version = "0.20.0", features = ["logs", "metrics"] }
|
||||
opentelemetry-otlp = { version = "0.13.0", default-features = false, features = [
|
||||
opentelemetry = { version = "0.27.0" }
|
||||
opentelemetry_api = { version = "0.27.0", features = ["logs", "metrics"] }
|
||||
opentelemetry-otlp = { version = "0.27.0", default-features = false, features = [
|
||||
"serde",
|
||||
"logs",
|
||||
"metrics",
|
||||
"http-proto",
|
||||
"grpc-tonic",
|
||||
] }
|
||||
opentelemetry_sdk = "0.20.0"
|
||||
tracing-opentelemetry = "0.21.0"
|
||||
opentelemetry_sdk = { version = "0.27.0", features = ["rt-tokio"] }
|
||||
opentelemetry-semantic-conventions = "0.27.0"
|
||||
tracing-opentelemetry = "0.28.0"
|
||||
tracing-core = "0.1.33"
|
||||
|
||||
paste = "^1.0.14"
|
||||
peg = "0.8"
|
||||
pkg-config = "^0.3.31"
|
||||
prctl = "1.0.0"
|
||||
proc-macro2 = "1.0.92"
|
||||
proc-macro2 = "1.0.93"
|
||||
qrcode = "^0.12.0"
|
||||
quote = "1"
|
||||
rand = "^0.8.5"
|
||||
rand_chacha = "0.3.1"
|
||||
regex = "1.11.0"
|
||||
reqwest = { version = "0.12.9", default-features = false, features = [
|
||||
reqwest = { version = "0.12.12", default-features = false, features = [
|
||||
"cookies",
|
||||
"http2",
|
||||
"json",
|
||||
|
@ -239,17 +242,17 @@ reqwest = { version = "0.12.9", default-features = false, features = [
|
|||
"rustls-tls-native-roots",
|
||||
] }
|
||||
rusqlite = { version = "^0.28.0", features = ["array", "bundled"] }
|
||||
rustls = { version = "0.23.19", default-features = false, features = [
|
||||
rustls = { version = "0.23.21", default-features = false, features = [
|
||||
"aws_lc_rs",
|
||||
] }
|
||||
|
||||
sd-notify = "^0.4.3"
|
||||
sd-notify = "^0.4.5"
|
||||
selinux = "^0.4.6"
|
||||
serde = "^1.0.215"
|
||||
serde = "^1.0.217"
|
||||
serde_cbor = { version = "0.12.0-dev", package = "serde_cbor_2" }
|
||||
serde_json = "^1.0.133"
|
||||
serde_json = "^1.0.137"
|
||||
serde_urlencoded = "^0.7.1"
|
||||
serde_with = "3.11.0"
|
||||
serde_with = "3.12.0"
|
||||
sha-crypt = "0.5.0"
|
||||
sha2 = "0.10.8"
|
||||
shellexpand = "^2.1.2"
|
||||
|
@ -258,14 +261,14 @@ smolset = "^1.3.1"
|
|||
sshkey-attest = "^0.5.0"
|
||||
sshkeys = "0.3.3"
|
||||
svg = "0.13.1"
|
||||
syn = { version = "2.0.90", features = ["full"] }
|
||||
tempfile = "3.14.0"
|
||||
syn = { version = "2.0.96", features = ["full"] }
|
||||
tempfile = "3.15.0"
|
||||
testkit-macros = { path = "./server/testkit-macros" }
|
||||
time = { version = "^0.3.34", features = ["formatting", "local-offset"] }
|
||||
time = { version = "^0.3.36", features = ["formatting", "local-offset"] }
|
||||
|
||||
tokio = "^1.41.1"
|
||||
tokio = "^1.43.0"
|
||||
tokio-openssl = "^0.6.5"
|
||||
tokio-util = "^0.7.12"
|
||||
tokio-util = "^0.7.13"
|
||||
|
||||
toml = "^0.5.11"
|
||||
tracing = { version = "^0.1.41", features = [
|
||||
|
@ -279,16 +282,15 @@ url = "^2.5.2"
|
|||
urlencoding = "2.1.3"
|
||||
utoipa = { version = "4.2.0", features = ["url", "uuid"] }
|
||||
utoipa-swagger-ui = "6.0.0"
|
||||
uuid = "^1.11.0"
|
||||
uuid = "^1.12.1"
|
||||
|
||||
webauthn-authenticator-rs = { version = "0.5.0", features = [
|
||||
webauthn-authenticator-rs = { version = "0.5.1", features = [
|
||||
"softpasskey",
|
||||
"softtoken",
|
||||
"mozilla",
|
||||
] }
|
||||
webauthn-rs = { version = "0.5.0", features = ["preview-features"] }
|
||||
webauthn-rs-core = "0.5.0"
|
||||
webauthn-rs-proto = "0.5.0"
|
||||
webauthn-rs = { version = "0.5.1", features = ["preview-features"] }
|
||||
webauthn-rs-core = "0.5.1"
|
||||
webauthn-rs-proto = "0.5.1"
|
||||
|
||||
whoami = "^1.5.2"
|
||||
walkdir = "2"
|
||||
|
@ -298,4 +300,3 @@ x509-cert = "0.2.5"
|
|||
zxcvbn = "^2.2.2"
|
||||
|
||||
nonempty = "0.8.1"
|
||||
|
||||
|
|
65
Makefile
65
Makefile
|
@ -178,9 +178,8 @@ codespell:
|
|||
--skip='*.svg' \
|
||||
--skip='*.br' \
|
||||
--skip='./rlm_python/mods-available/eap' \
|
||||
--skip='./server/lib/src/constants/system_config.rs'
|
||||
--skip='./pykanidm/site' \
|
||||
--skip='./server/lib/src/constants/*.json'
|
||||
--skip='./server/lib/src/constants/system_config.rs' \
|
||||
--skip='./pykanidm/site'
|
||||
|
||||
.PHONY: test/pykanidm/pytest
|
||||
test/pykanidm/pytest: ## python library testing
|
||||
|
@ -314,26 +313,44 @@ cert/clean:
|
|||
rm -f /tmp/kanidm/ca.txt*
|
||||
rm -f /tmp/kanidm/ca.{cnf,srl,srl.old}
|
||||
|
||||
.PHONY: rust/coverage
|
||||
coverage/test: ## Run coverage tests
|
||||
coverage/test:
|
||||
LLVM_PROFILE_FILE="$(PWD)/target/profile/coverage-%p-%m.profraw" RUSTFLAGS="-C instrument-coverage" cargo test $(TESTS)
|
||||
|
||||
.PHONY: coverage/grcov
|
||||
coverage/grcov: ## Run grcov
|
||||
coverage/grcov:
|
||||
rm -rf ./target/coverage/html
|
||||
grcov . --binary-path ./target/debug/deps/ \
|
||||
-s . \
|
||||
-t html \
|
||||
--branch \
|
||||
--ignore-not-existing \
|
||||
--ignore '../*' \
|
||||
--ignore "/*" \
|
||||
--ignore "target/*" \
|
||||
-o target/coverage/html
|
||||
|
||||
.PHONY: coverage
|
||||
coverage: ## Run all the coverage tests
|
||||
coverage: coverage/test coverage/grcov
|
||||
echo "Coverage report is in ./target/coverage/html/index.html"
|
||||
coverage: ## Run the coverage tests using cargo-tarpaulin
|
||||
cargo tarpaulin --out Html
|
||||
@echo "Coverage file at file://$(PWD)/tarpaulin-report.html"
|
||||
|
||||
|
||||
.PHONY: coveralls
|
||||
coveralls: ## Run cargo tarpaulin and upload to coveralls
|
||||
coveralls:
|
||||
cargo tarpaulin --coveralls $(COVERALLS_REPO_TOKEN)
|
||||
@echo "Coveralls repo information is at https://coveralls.io/github/kanidm/kanidm"
|
||||
|
||||
|
||||
.PHONY: eslint
|
||||
eslint: ## Run eslint on the UI javascript things
|
||||
eslint: eslint/setup
|
||||
@echo "################################"
|
||||
@echo " Running eslint..."
|
||||
@echo "################################"
|
||||
cd server/core && find ./static -name '*js' -not -path '*/external/*' -exec eslint "{}" \;
|
||||
@echo "################################"
|
||||
@echo "Done!"
|
||||
|
||||
.PHONY: eslint/setup
|
||||
eslint/setup: ## Install eslint for the UI javascript things
|
||||
cd server/core && npm ci
|
||||
|
||||
.PHONY: prettier
|
||||
prettier: ## Run prettier on the UI javascript things
|
||||
prettier: eslint/setup
|
||||
@echo " Running prettier..."
|
||||
cd server/core && npm run prettier
|
||||
@echo "Done!"
|
||||
|
||||
.PHONY: prettier/fix
|
||||
prettier/fix: ## Run prettier on the UI javascript things and write back changes
|
||||
prettier/fix: eslint/setup
|
||||
@echo " Running prettier..."
|
||||
cd server/core && npm run prettier:fix
|
||||
@echo "Done!"
|
|
@ -1,8 +1,6 @@
|
|||
# Kanidm - Simple and Secure Identity Management
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/kanidm/kanidm/master/artwork/logo-small.png" width="20%" height="auto" />
|
||||
</p>
|
||||

|
||||
|
||||
## About
|
||||
|
||||
|
|
157
RELEASE_NOTES.md
157
RELEASE_NOTES.md
|
@ -1,42 +1,93 @@
|
|||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/kanidm/kanidm/master/artwork/logo-small.png" width="20%" height="auto" />
|
||||
</p>
|
||||
# Kanidm Release Notes
|
||||
|
||||
# Getting Started
|
||||

|
||||
|
||||
## Getting Started
|
||||
|
||||
To get started, see the [kanidm book]
|
||||
|
||||
# Feedback
|
||||
## Feedback
|
||||
|
||||
We value your feedback! First, please see our [code of conduct]. If you have questions please join
|
||||
our [gitter community channel] so that we can help. If you find a bug or issue, we'd love you to
|
||||
report it to our [issue tracker].
|
||||
|
||||
# Release Notes
|
||||
## Release Notes
|
||||
|
||||
## 2024-11-01 - Kanidm 1.4.0
|
||||
### 2025-02-09 - Kanidm 1.5.0
|
||||
|
||||
This is the latest stable release of the Kanidm Identity Management project. Every release is the
|
||||
combined effort of our community and we appreciate their invaluable contributions, comments,
|
||||
questions, feedback and support.
|
||||
|
||||
You should review our
|
||||
[support documentation](https://github.com/kanidm/kanidm/blob/master/book/src/support.md) as this
|
||||
[support documentation] as this
|
||||
may have important effects on your distribution or upgrades in future.
|
||||
|
||||
Before upgrading you should review
|
||||
[our upgrade documentation](https://github.com/kanidm/kanidm/blob/master/book/src/server_updates.md#general-update-notes)
|
||||
[our upgrade documentation]
|
||||
|
||||
### 1.4.0 Important Changes
|
||||
#### 1.5.0 Important Changes
|
||||
|
||||
- There has been a lot of tweaks to how cookies are handled in this release, if you're having issues with the login flow please clear all cookies as an initial troubleshooting step.
|
||||
|
||||
#### 1.5.0 Release Highlights
|
||||
|
||||
- Many updates to the UI!
|
||||
- SSH Keys in Credentials Update (#3027)
|
||||
- Improved error message when PassKey is missing PIN (mainly for Firefox) (#3403)
|
||||
- Fix the password reset form and possible resolver issue (#3398)
|
||||
- Fixed unrecoverable error page doesn't include logo or domain name (#3352)
|
||||
- Add support for prefers-color-scheme using Bootstrap classes. Dark mode! (#3327)
|
||||
- Automatically trigger passkeys on login view (#3307)
|
||||
- Two new operating systems!
|
||||
- Initial OpenBSD support (#3381)
|
||||
- FreeBSD client (#3333)
|
||||
- Many SCIM-related improvements
|
||||
- SCIM access control (#3359)
|
||||
- SCIM put (#3151)
|
||||
- OAuth2 Things
|
||||
- Allow OAuth2 with empty `state` parameter (#3396)
|
||||
- Allow POST on oauth userinfo (#3395)
|
||||
- Add OAuth2 `response_mode=fragment` (#3335)
|
||||
- Add CORS headers to jwks and userinfo (#3283)
|
||||
- Allowing SPN query with non-SPN structured data in LDAP (#3400)
|
||||
- Correctly return that uuid2spn changed on domain rename (#3402)
|
||||
- RADIUS startup fixing (#3388)
|
||||
- Repaired systemd reload notifications (#3355)
|
||||
- Add `ssh_publickeys` as a claim for OAuth2 (#3346)
|
||||
- Allow modification of password minimum length (#3345)
|
||||
- PAM on Debian, enable use_first_pass by default (#3326)
|
||||
- Allow opt-in of easter eggs (#3308)
|
||||
- Allow reseting account policy values to defaults (#3306)
|
||||
- Ignore system users for UPG synthesiseation (#3297)
|
||||
- Allow group managers to modify entry-managed-by (#3272)
|
||||
|
||||
And many more!
|
||||
|
||||
### 2024-11-01 - Kanidm 1.4.0
|
||||
|
||||
This is the latest stable release of the Kanidm Identity Management project. Every release is the
|
||||
combined effort of our community and we appreciate their invaluable contributions, comments,
|
||||
questions, feedback and support.
|
||||
|
||||
You should review our
|
||||
[support documentation] as this
|
||||
may have important effects on your distribution or upgrades in future.
|
||||
|
||||
Before upgrading you should review
|
||||
[our upgrade documentation]
|
||||
|
||||
#### 1.4.0 Important Changes
|
||||
|
||||
- The web user interface has been rewritten and now supports theming. You will notice that your
|
||||
domain displayname is included in a number of locations on upgrade, and that you can set
|
||||
your own domain and OAuth2 client icons.
|
||||
- OAuth2 strict redirect uri is now required. Ensure you have read
|
||||
[our upgrade documentation](https://github.com/kanidm/kanidm/blob/master/book/src/server_updates.md#general-update-notes).
|
||||
[our upgrade documentation].
|
||||
and taken the needed steps before upgrading.
|
||||
|
||||
### 1.4.0 Release Highlights
|
||||
#### 1.4.0 Release Highlights
|
||||
|
||||
- Improve handling of client timeouts when the server is under high load
|
||||
- Resolve a minor issue preventing some credential updates from saving
|
||||
|
@ -65,20 +116,20 @@ and taken the needed steps before upgrading.
|
|||
- Rewrite the entire web frontend to be simpler and faster, allowing more features to be added
|
||||
in the future. Greatly improves user experience as the pages are now very fast to load!
|
||||
|
||||
## 2024-08-07 - Kanidm 1.3.0
|
||||
### 2024-08-07 - Kanidm 1.3.0
|
||||
|
||||
This is the latest stable release of the Kanidm Identity Management project. Every release is the
|
||||
combined effort of our community and we appreciate their invaluable contributions, comments,
|
||||
questions, feedback and support.
|
||||
|
||||
You should review our
|
||||
[support documentation](https://github.com/kanidm/kanidm/blob/master/book/src/support.md) as this
|
||||
[support documentation] as this
|
||||
may have important effects on your distribution or upgrades in future.
|
||||
|
||||
Before upgrading you should review
|
||||
[our upgrade documentation](https://github.com/kanidm/kanidm/blob/master/book/src/server_updates.md#general-update-notes)
|
||||
[our upgrade documentation]
|
||||
|
||||
### 1.3.0 Important Changes
|
||||
#### 1.3.0 Important Changes
|
||||
|
||||
- New GID number constraints are now enforced in this version. To upgrade from 1.2.0 all accounts
|
||||
and groups must adhere to these rules. See [our upgrade documentation]. about tools to help you
|
||||
|
@ -89,7 +140,7 @@ Before upgrading you should review
|
|||
by PassKeys which give a better user experience.
|
||||
- Kanidm now supports FreeBSD and Illumos in addition to Linux
|
||||
|
||||
### 1.3.0 Release Highlights
|
||||
#### 1.3.0 Release Highlights
|
||||
|
||||
- TOTP update user interface improvements
|
||||
- Improved error messages when a load balancer is failing
|
||||
|
@ -112,24 +163,24 @@ Before upgrading you should review
|
|||
- Strict redirect URI enforcement in OAuth2
|
||||
- Substring indexing for improved search performance
|
||||
|
||||
## 2024-05-01 - Kanidm 1.2.0
|
||||
### 2024-05-01 - Kanidm 1.2.0
|
||||
|
||||
This is the first stable release of the Kanidm Identity Management project. We want to thank every
|
||||
one in our community who has supported to the project to this point with their invaluable
|
||||
contributions, comments, questions, feedback and support.
|
||||
|
||||
Importantly this release makes a number of changes to our project's support processes. You should
|
||||
review our [support documentation](https://github.com/kanidm/kanidm/blob/master/book/src/support.md)
|
||||
review our [support documentation]
|
||||
as this may have important effects on your distribution or upgrades in future.
|
||||
|
||||
### 1.2.0 Important Changes
|
||||
#### 1.2.0 Important Changes
|
||||
|
||||
- On upgrade all OAuth2 sessions and user sessions will be reset due to changes in cryptographic key
|
||||
handling. This does not affect api tokens.
|
||||
- There is a maximum limit of 48 interactive sessions for persons where older sessions are
|
||||
automatically removed.
|
||||
|
||||
### 1.2.0 Release Highlights
|
||||
#### 1.2.0 Release Highlights
|
||||
|
||||
- The book now contains a list of supported RFCs and standards
|
||||
- Add code challenge methods to OIDC discovery
|
||||
|
@ -154,7 +205,7 @@ as this may have important effects on your distribution or upgrades in future.
|
|||
- Migrate cryptographic key handling to an object model with future HSM support
|
||||
- Limit maximum active sessions on an account to 48
|
||||
|
||||
## 2024-02-07 - Kanidm 1.1.0-rc.16
|
||||
### 2024-02-07 - Kanidm 1.1.0-rc.16
|
||||
|
||||
This is the sixteenth pre-release of the Kanidm Identity Management project. Pre-releases are to
|
||||
help get feedback and ideas from the community on how we can continue to make this project better.
|
||||
|
@ -163,7 +214,7 @@ This is the final release candidate before we publish a release version. We beli
|
|||
server interfaces are stable and reliable enough for people to depend on, and to develop external
|
||||
tools to interact with Kanidm.
|
||||
|
||||
### 1.1.0-rc.16 Release Highlights
|
||||
#### 1.1.0-rc.16 Release Highlights
|
||||
|
||||
- Replication for two node environments is now supported
|
||||
- Account policy supports password minimum length
|
||||
|
@ -182,7 +233,7 @@ tools to interact with Kanidm.
|
|||
- Support RFC6749 Client Credentials Grant
|
||||
- Support custom claim maps in OIDC
|
||||
|
||||
## 2023-10-31 - Kanidm 1.1.0-beta14
|
||||
### 2023-10-31 - Kanidm 1.1.0-beta14
|
||||
|
||||
This is the fourteenth pre-release of the Kanidm Identity Management project. Pre-releases are to
|
||||
help get feedback and ideas from the community on how we can continue to make this project better.
|
||||
|
@ -191,7 +242,7 @@ At this point we believe we are on the final stretch to making something we cons
|
|||
ready". After this we will start to ship release candidates as our focus will now be changing to
|
||||
finish our production components and the stability of the API's for longer term support.
|
||||
|
||||
### 1.1.0-beta14 Release Highlights
|
||||
#### 1.1.0-beta14 Release Highlights
|
||||
|
||||
- Replication is in Beta! Please test carefully!
|
||||
- Web UI WASM has been split up, significantly improving the responsiveness.
|
||||
|
@ -205,7 +256,7 @@ finish our production components and the stability of the API's for longer term
|
|||
- Removed a lot of uses of `unwrap` and `expect` to improve reliability.
|
||||
- Account policy framework is now in place.
|
||||
|
||||
## 2023-05-01 - Kanidm 1.1.0-beta13
|
||||
### 2023-05-01 - Kanidm 1.1.0-beta13
|
||||
|
||||
This is the thirteenth pre-release of the Kanidm Identity Management project. Pre-releases are to
|
||||
help get feedback and ideas from the community on how we can continue to make this project better.
|
||||
|
@ -214,7 +265,7 @@ At this point we believe we are on the final stretch to making something we cons
|
|||
ready". After this we will start to ship release candidates as our focus will now be changing to
|
||||
finish our production components and the stability of the API's for longer term support.
|
||||
|
||||
### 1.1.0-beta13 Release Highlights
|
||||
#### 1.1.0-beta13 Release Highlights
|
||||
|
||||
- Replication foundations
|
||||
- Full implementation of replication refresh
|
||||
|
@ -255,7 +306,7 @@ finish our production components and the stability of the API's for longer term
|
|||
- Improve create-reset-token user experience
|
||||
- Improve self-healing for some reference issues
|
||||
|
||||
## 2023-05-01 - Kanidm 1.1.0-alpha12
|
||||
### 2023-05-01 - Kanidm 1.1.0-alpha12
|
||||
|
||||
This is the twelfth alpha series release of the Kanidm Identity Management project. Alpha releases
|
||||
are to help get feedback and ideas from the community on how we can continue to make this project
|
||||
|
@ -266,7 +317,7 @@ done so yet is we haven't decided if we want to commit to the current API layout
|
|||
There are still things we want to change there. Otherwise the server is stable and reliable for
|
||||
production usage.
|
||||
|
||||
### Release Highlights
|
||||
#### 1.1.0-alpha12 Release Highlights
|
||||
|
||||
- Allow full server content replication in testing (yes we're finally working on replication!)
|
||||
- Improve OAuth2 to allow scoped members to see RS they can access for UI flows
|
||||
|
@ -286,7 +337,7 @@ production usage.
|
|||
- Add exclusive process lock to daemon
|
||||
- Allow dns/rdns in ldap search contexts
|
||||
|
||||
## 2023-02-01 - Kanidm 1.1.0-alpha11
|
||||
### 2023-02-01 - Kanidm 1.1.0-alpha11
|
||||
|
||||
This is the eleventh alpha series release of the Kanidm Identity Management project. Alpha releases
|
||||
are to help get feedback and ideas from the community on how we can continue to make this project
|
||||
|
@ -296,7 +347,7 @@ The project is shaping up very nicely, and a beta will be coming soon! The main
|
|||
done so yet is we haven't decided if we want to commit to the current API layout and freeze it yet.
|
||||
There are still things we want to change there. Otherwise the server is stable and reliable.
|
||||
|
||||
### Release Highlights
|
||||
#### 1.1.0-alpha11 Release Highlights
|
||||
|
||||
- Support /etc/skel home dir templates in kanidm-unixd
|
||||
- Improve warning messages for openssl when a cryptographic routine is not supported
|
||||
|
@ -317,7 +368,7 @@ There are still things we want to change there. Otherwise the server is stable a
|
|||
- Improve the access control module to evaluate access in a clearer way
|
||||
- Allow synced users to correct modify their local sessions
|
||||
|
||||
## 2022-11-01 - Kanidm 1.1.0-alpha10
|
||||
### 2022-11-01 - Kanidm 1.1.0-alpha10
|
||||
|
||||
This is the tenth alpha series release of the Kanidm Identity Management project. Alpha releases are
|
||||
to help get feedback and ideas from the community on how we can continue to make this project better
|
||||
|
@ -325,12 +376,12 @@ for a future supported release.
|
|||
|
||||
The project is shaping up very nicely, and a beta will be coming soon!
|
||||
|
||||
### Upgrade Note
|
||||
#### 1.1.0-alpha10 Upgrade Note
|
||||
|
||||
This version will _require_ TLS on all servers, even if behind a load balancer or TLS terminating
|
||||
proxy. You should be ready for this change when you upgrade to the latest version.
|
||||
|
||||
### Release Highlights
|
||||
#### 1.1.0-alpha10 Release Highlights
|
||||
|
||||
- Management and tracking of authenticated sessions
|
||||
- Make upgrade migrations more robust when upgrading over multiple versions
|
||||
|
@ -352,7 +403,7 @@ proxy. You should be ready for this change when you upgrade to the latest versio
|
|||
- Cleanup of expired authentication sessions
|
||||
- Improved administration of password badlists
|
||||
|
||||
## 2022-08-02 - Kanidm 1.1.0-alpha9
|
||||
### 2022-08-02 - Kanidm 1.1.0-alpha9
|
||||
|
||||
This is the ninth alpha series release of the Kanidm Identity Management project. Alpha releases are
|
||||
to help get feedback and ideas from the community on how we can continue to make this project better
|
||||
|
@ -360,7 +411,7 @@ for a future supported release.
|
|||
|
||||
The project is shaping up very nicely, and a beta will be coming soon!
|
||||
|
||||
### Release Highlights
|
||||
#### 1.1.0-alpha9 Release Highlights
|
||||
|
||||
- Inclusion of a Python3 API library
|
||||
- Improve orca usability
|
||||
|
@ -376,13 +427,13 @@ The project is shaping up very nicely, and a beta will be coming soon!
|
|||
- CTAP2+ support in Webauthn via CLI
|
||||
- Radius supports EAP TLS identities in addition to EAP PEAP
|
||||
|
||||
## 2022-05-01 - Kanidm 1.1.0-alpha8
|
||||
### 2022-05-01 - Kanidm 1.1.0-alpha8
|
||||
|
||||
This is the eighth alpha series release of the Kanidm Identity Management project. Alpha releases
|
||||
are to help get feedback and ideas from the community on how we can continue to make this project
|
||||
better for a future supported release.
|
||||
|
||||
### Release Highlights
|
||||
#### 1.1.0-alpha8 Release Highlights
|
||||
|
||||
- Foundations for cryptographic trusted device authentication
|
||||
- Foundations for new user onboarding and credential reset
|
||||
|
@ -398,13 +449,13 @@ better for a future supported release.
|
|||
- Highlight that the WebUI is in alpha to prevent confusion
|
||||
- Remove sync only client paths
|
||||
|
||||
## 2022-01-01 - Kanidm 1.1.0-alpha7
|
||||
### 2022-01-01 - Kanidm 1.1.0-alpha7
|
||||
|
||||
This is the seventh alpha series release of the Kanidm Identity Management project. Alpha releases
|
||||
are to help get feedback and ideas from the community on how we can continue to make this project
|
||||
better for a future supported release.
|
||||
|
||||
### Release Highlights
|
||||
#### 1.1.0-alpha7 Release Highlights
|
||||
|
||||
- OAuth2 scope to group mappings
|
||||
- Webauthn subdomain support
|
||||
|
@ -415,7 +466,7 @@ better for a future supported release.
|
|||
- Addition of email address attributes
|
||||
- Web UI improvements for OAuth2
|
||||
|
||||
## 2021-10-01 - Kanidm 1.1.0-alpha6
|
||||
### 2021-10-01 - Kanidm 1.1.0-alpha6
|
||||
|
||||
This is the sixth alpha series release of the Kanidm Identity Management project. Alpha releases are
|
||||
to help get feedback and ideas from the community on how we can continue to make this project better
|
||||
|
@ -424,7 +475,7 @@ for a future supported release.
|
|||
It's also a special release as Kanidm has just turned 3 years old! Thank you all for helping to
|
||||
bring the project this far! 🎉 🦀
|
||||
|
||||
### Release Highlights
|
||||
#### 1.1.0-alpha6 Release Highlights
|
||||
|
||||
- Support backup codes as MFA in case of lost TOTP/Webauthn
|
||||
- Dynamic menus on CLI for usernames when multiple sessions exist
|
||||
|
@ -444,13 +495,13 @@ bring the project this far! 🎉 🦀
|
|||
- Improvements to performance with high cache sizes
|
||||
- Session tokens persist over a session restart
|
||||
|
||||
## 2021-07-07 - Kanidm 1.1.0-alpha5
|
||||
### 2021-07-07 - Kanidm 1.1.0-alpha5
|
||||
|
||||
This is the fifth alpha series release of the Kanidm Identity Management project. Alpha releases are
|
||||
to help get feedback and ideas from the community on how we can continue to make this project better
|
||||
for a future supported release.
|
||||
|
||||
### Release Highlights
|
||||
#### 1.1.0-alpha5 Release Highlights
|
||||
|
||||
- Fix a major defect in how backup/restore worked
|
||||
- Improve query performance by caching partial queries
|
||||
|
@ -465,13 +516,13 @@ for a future supported release.
|
|||
- Statistical analysis of indexes to improve query optimisation
|
||||
- Handle broken TOTP authenticator apps
|
||||
|
||||
## 2021-04-01 - Kanidm 1.1.0-alpha4
|
||||
### 2021-04-01 - Kanidm 1.1.0-alpha4
|
||||
|
||||
This is the fourth alpha series release of the Kanidm Identity Management project. Alpha releases
|
||||
are to help get feedback and ideas from the community on how we can continue to make this project
|
||||
better for a future supported release.
|
||||
|
||||
### Release Highlights
|
||||
#### 1.1.0-alpha4 Release Highlights
|
||||
|
||||
- Performance Improvements
|
||||
- TOTP CLI enrollment
|
||||
|
@ -485,13 +536,13 @@ better for a future supported release.
|
|||
- Badlist checked at login to determine account compromise
|
||||
- Minor Fixes for attribute display
|
||||
|
||||
## 2021-01-01 - Kanidm 1.1.0-alpha3
|
||||
### 2021-01-01 - Kanidm 1.1.0-alpha3
|
||||
|
||||
This is the third alpha series release of the Kanidm Identity Management project. Alpha releases are
|
||||
to help get feedback and ideas from the community on how we can continue to make this project better
|
||||
for a future supported release.
|
||||
|
||||
### Release Highlights
|
||||
#### 1.1.0-alpha3 Release Highlights
|
||||
|
||||
- Account "valid from" and "expiry" times.
|
||||
- Rate limiting and softlocking of account credentials to prevent bruteforcing.
|
||||
|
@ -499,13 +550,13 @@ for a future supported release.
|
|||
- Rewrite of json authentication protocol components.
|
||||
- Unixd will cache "non-existent" items to improve nss/pam latency.
|
||||
|
||||
## 2020-10-01 - Kanidm 1.1.0-alpha2
|
||||
### 2020-10-01 - Kanidm 1.1.0-alpha2
|
||||
|
||||
This is the second alpha series release of the Kanidm Identity Management project. Alpha releases
|
||||
are to help get feedback and ideas from the community on how we can continue to make this project
|
||||
better for a future supported release.
|
||||
|
||||
### Release Highlights
|
||||
#### 1.1.0-alpha2 Release Highlights
|
||||
|
||||
- SIMD key lookups in container builds for datastructures
|
||||
- Server and Client hardening warnings for running users and file permissions
|
||||
|
@ -517,7 +568,7 @@ better for a future supported release.
|
|||
- Reduction in memory footprint during searches
|
||||
- Change authentication from cookies to auth-bearer tokens
|
||||
|
||||
## 2020-07-01 - Kanidm 1.1.0-alpha1
|
||||
### 2020-07-01 - Kanidm 1.1.0-alpha1
|
||||
|
||||
This is the first alpha series release of the Kanidm Identity Management project. Alpha releases are
|
||||
to help get feedback and ideas from the community on how we can continue to make this project better
|
||||
|
@ -536,7 +587,7 @@ people. I would especially like to thank:
|
|||
- Samuel Cabrero (scabrero)
|
||||
- Jim McDonough
|
||||
|
||||
### Release Highlights
|
||||
#### 1.1.0-alpha1 Release Highlights
|
||||
|
||||
- A working identity management server, including database
|
||||
- RADIUS authentication and docker images
|
||||
|
@ -552,3 +603,5 @@ people. I would especially like to thank:
|
|||
[gitter community channel]: https://gitter.im/kanidm/community
|
||||
[code of conduct]: https://github.com/kanidm/kanidm/blob/master/CODE_OF_CONDUCT.md
|
||||
[kanidm book]: https://kanidm.github.io/kanidm/stable/
|
||||
[our upgrade documentation]: https://github.com/kanidm/kanidm/blob/master/book/src/server_updates.md#general-update-notes
|
||||
[support documentation]: https://github.com/kanidm/kanidm/blob/master/book/src/support.md
|
||||
|
|
|
@ -40,9 +40,9 @@
|
|||
- [Service Integrations](integrations/readme.md)
|
||||
- [LDAP](integrations/ldap.md)
|
||||
- [OAuth2](integrations/oauth2.md)
|
||||
- [How does OAuth2 work?](integrations/oauth2/how_does_oauth2_work.md)
|
||||
- [Custom Claims](integrations/oauth2/custom_claims.md)
|
||||
- [Example Configurations](integrations/oauth2/examples.md)
|
||||
- [How does OAuth2 work?](integrations/oauth2/how_does_oauth2_work.md)
|
||||
- [PAM and nsswitch](integrations/pam_and_nsswitch.md)
|
||||
- [SUSE / OpenSUSE](integrations/pam_and_nsswitch/suse.md)
|
||||
- [Fedora](integrations/pam_and_nsswitch/fedora.md)
|
||||
|
|
|
@ -31,6 +31,8 @@ weakest to strongest:
|
|||
- `passkey`
|
||||
- `attested_passkey`
|
||||
|
||||
`attested_passkey` requires [configuring an allowlist of trusted authenticators](#setting-webauthn-attestation-ca-lists).
|
||||
|
||||
### Password Minimum Length
|
||||
|
||||
The minimum length for passwords (if they are allowed).
|
||||
|
@ -45,7 +47,7 @@ read/write session.
|
|||
The list of certificate authorities and device aaguids that must be used by members of this policy.
|
||||
This allows limiting devices to specific models.
|
||||
|
||||
To generate this list you should use `fido-mds-tool`.
|
||||
To generate this list you should [use `fido-mds-tool`](#setting-webauthn-attestation-ca-lists).
|
||||
|
||||
## Policy Resolution
|
||||
|
||||
|
@ -149,15 +151,59 @@ kanidm group account-policy privilege-expiry my_admin_group 86400 # NB: will be
|
|||
|
||||
### Setting Webauthn Attestation CA Lists
|
||||
|
||||
The list should be generated with `fido-mds-tool`. This will emit JSON that can be directly used
|
||||
with Kanidm.
|
||||
To verify Webauthn authenticators with attestation, Kanidm needs an allowlist of
|
||||
authenticators to trust. Generate this list with the `fido-mds-tool` from
|
||||
the [webauthn-rs project](https://github.com/kanidm/webauthn-rs). If you have a
|
||||
Rust toolchain installed, it can built and installed from source with
|
||||
|
||||
```bash
|
||||
kanidm group account-policy webauthn-attestation-ca-list <group name> <attestation ca list json>
|
||||
kanidm group account-policy webauthn-attestation-ca-list idm_all_persons '{"cas":{"D6E4b4Drh .... }'
|
||||
cargo install fido-mds-tool
|
||||
```
|
||||
|
||||
> NOTE: `fido-mds-tool` is available in the `kanidm:tools` container.
|
||||
Alternatively, `fido-mds-tool` is available in the
|
||||
[tools container](../installing_client_tools.md#tools-container).
|
||||
|
||||
First, fetch the MDS data provided by the FIDO Alliance:
|
||||
```bash
|
||||
fido-mds-tool fetch
|
||||
```
|
||||
|
||||
Then, query the MDS data to generate your allowlist of authenticators.
|
||||
For example, to trust all authenticators made by Yubico, run
|
||||
|
||||
```bash
|
||||
fido-mds-tool query --output-cert-roots "desc cnt yubikey" > trusted-authenticators
|
||||
```
|
||||
|
||||
For details of how to query the MDS data, run
|
||||
|
||||
```bash
|
||||
fido-mds-tool query --help
|
||||
```
|
||||
|
||||
Once you have generated the authenticator allowlist, use it to configure Kanidm's
|
||||
account policy for a group. For example, to set the allowlist for all persons, run
|
||||
|
||||
```bash
|
||||
kanidm group account-policy webauthn-attestation-ca-list idm_all_persons trusted-authenticators
|
||||
```
|
||||
|
||||
### Setting Primary Credential Fallback
|
||||
|
||||
The primary credential fallback enables behavior which allows authenticating
|
||||
using the primary account password when logging in via LDAP.
|
||||
|
||||
If both an LDAP and primary password are specified, Kanidm will only accept the LDAP password.
|
||||
|
||||
```bash
|
||||
kanidm group account-policy allow-primary-cred-fallback <group name> <enabled>
|
||||
```
|
||||
|
||||
to disable it for a group you would run:
|
||||
|
||||
```bash
|
||||
kanidm group account-policy allow-primary-cred-fallback <group name> false
|
||||
```
|
||||
|
||||
## Global Settings
|
||||
|
||||
|
|
|
@ -114,3 +114,7 @@ When a service like sudo, sshd, su, etc. wants to authenticate someone, it opens
|
|||
that service, then performs authentication according to the modules defined in the pam.d config. For
|
||||
example, if you run `ls -al /etc/pam.d /usr/etc/pam.d` in SUSE, you can see the services and their
|
||||
respective pam.d config.
|
||||
|
||||
## Test coverage
|
||||
|
||||
We're trying to regularly get coverage reports into [Coveralls](https://coveralls.io/github/kanidm/kanidm), you can run the local testing with `make coverage` once you've installed [cargo-tarpaulin](https://crates.io/crates/cargo-tarpaulin).
|
||||
|
|
|
@ -103,7 +103,7 @@ You will need [rustup](https://rustup.rs/) to install a Rust toolchain.
|
|||
You will need to install rustup and our build dependencies with:
|
||||
|
||||
```bash
|
||||
zypper in rustup git libudev-devel sqlite3-devel libopenssl-3-devel libselinux-devel pam-devel tpm2-0-tss-devel
|
||||
zypper in rustup git libudev-devel sqlite3-devel libopenssl-3-devel libselinux-devel pam-devel systemd-devel tpm2-0-tss-devel
|
||||
```
|
||||
|
||||
You can then use rustup to complete the setup of the toolchain.
|
||||
|
@ -157,7 +157,7 @@ You need [rustup](https://rustup.rs/) to install a Rust toolchain.
|
|||
You will also need some system libraries to build this, which can be installed by running:
|
||||
|
||||
```bash
|
||||
sudo apt-get install libudev-dev libssl-dev pkg-config libpam0g-dev
|
||||
sudo apt-get install libudev-dev libssl-dev libsystemd-dev pkg-config libpam0g-dev
|
||||
```
|
||||
|
||||
Tested with Ubuntu 20.04 and 22.04.
|
||||
|
|
|
@ -3,57 +3,58 @@
|
|||
## Pre-Reqs
|
||||
|
||||
```bash
|
||||
cargo install cargo-audit
|
||||
cargo install cargo-outdated
|
||||
cargo install cargo-udeps
|
||||
cargo install cargo-machete
|
||||
cargo install --force \
|
||||
cargo-audit \
|
||||
cargo-outdated \
|
||||
cargo-udeps \
|
||||
cargo-machete
|
||||
```
|
||||
|
||||
## Pre Release Check List
|
||||
|
||||
### Start a release
|
||||
|
||||
- [ ] git checkout -b YYYYMMDD-pre-release
|
||||
- [ ] `git checkout -b "$(date +%Y%m%d)-pre-release"`
|
||||
|
||||
### Cargo Tasks
|
||||
|
||||
- [ ] Update MSRV if applicable
|
||||
- [ ] cargo update
|
||||
- [ ] `cargo update`
|
||||
- [ ] `RUSTC_BOOTSTRAP=1 cargo udeps`
|
||||
- [ ] `cargo machete`
|
||||
- [ ] cargo outdated -R
|
||||
- [ ] cargo audit
|
||||
- [ ] cargo test
|
||||
- [ ] `cargo machete --with-metadata`
|
||||
- [ ] `cargo outdated -R`
|
||||
- [ ] `cargo audit`
|
||||
- [ ] `cargo test`
|
||||
|
||||
- [ ] setup a local instance and run orca (TBD)
|
||||
- [ ] store a copy an an example db (TBD)
|
||||
|
||||
### Code Changes
|
||||
|
||||
- [ ] upgrade crypto policy values if required
|
||||
- [ ] upgrade crypto policy values if required (see `libs/crypto/src/lib.rs` -> `CryptoPolicy`)
|
||||
- [ ] check for breaking db entry changes.
|
||||
|
||||
### Administration
|
||||
|
||||
- [ ] Update `RELEASE_NOTES.md`
|
||||
- [ ] Update `README.md`
|
||||
- [ ] cargo test
|
||||
- [ ] git commit -a -m "Release Notes"
|
||||
- [ ] git push origin YYYYMMDD-pre-release
|
||||
- [ ] `cargo test`
|
||||
- [ ] `git commit -a -m 'chore: Release Notes'`
|
||||
- [ ] `git push origin "$(date +%Y%m%d)-pre-release"`
|
||||
- [ ] Merge PR
|
||||
|
||||
### Git Management
|
||||
|
||||
- [ ] git checkout master
|
||||
- [ ] git pull
|
||||
- [ ] `git checkout master`
|
||||
- [ ] `git pull`
|
||||
- [ ] git checkout -b 1.x.0 (Note no v to prevent ref conflict)
|
||||
- [ ] update version to set pre tag in ./Cargo.toml
|
||||
- [ ] git commit -m "Release 1.x.0-pre"
|
||||
- [ ] git tag v1.x.0-pre
|
||||
- [ ] `git commit -m "Release $(cargo metadata --format-version 1 | jq '.packages[] | select(.name=="kanidm_proto") | .version')-pre"`
|
||||
- [ ] `git tag v$(cargo metadata --format-version 1 | jq '.packages[] | select(.name=="kanidm_proto") | .version')-pre`
|
||||
|
||||
- [ ] Final inspect of the branch
|
||||
|
||||
- [ ] git push origin 1.x.0 --tags
|
||||
- [ ] `git push origin "$(cargo metadata --format-version 1 | jq '.packages[] | select(.name=="kanidm_proto") | .version')" --tags`
|
||||
|
||||
- [ ] github -> Ensure release branch is protected
|
||||
|
||||
|
@ -106,4 +107,3 @@ cargo install cargo-machete
|
|||
### Distro
|
||||
|
||||
- [ ] vendor and release to build.opensuse.org
|
||||
|
||||
|
|
|
@ -145,7 +145,8 @@ with a dn of `dn=token` and provide the api token in the password.
|
|||
> [!NOTE]
|
||||
>
|
||||
> The `dn=token` keyword is guaranteed to not be used by any other entry, which is why it was chosen
|
||||
> as the keyword to initiate api token binds.
|
||||
> as the keyword to initiate api token binds. Additionally it is not required, leaving the field empty
|
||||
> will fall back to the service-account if a "password" is provided
|
||||
|
||||
```bash
|
||||
ldapwhoami -H ldaps://URL -x -D "dn=token" -w "TOKEN"
|
||||
|
@ -234,6 +235,7 @@ ldapwhoami ... -x -D '22a65b6c-80c8-4e1a-9b76-3f3afdff8400'
|
|||
ldapwhoami ... -x -D 'spn=test1@idm.example.com,dc=idm,dc=example,dc=com'
|
||||
ldapwhoami ... -x -D 'name=test1,dc=idm,dc=example,dc=com'
|
||||
```
|
||||
<sub>in fact, the key of the bind isn't used at all so `googoogaaga=test1` is entirely valid</sub> ;)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
|
|
@ -70,6 +70,31 @@ anything special for Kanidm (or another provider).
|
|||
**Note:** some apps automatically append `/.well-known/openid-configuration` to
|
||||
the end of an OIDC Discovery URL, so you may need to omit that.
|
||||
|
||||
|
||||
<dl>
|
||||
<dt>[Webfinger](https://datatracker.ietf.org/doc/html/rfc7033) URL</dt>
|
||||
|
||||
<dd>
|
||||
|
||||
`https://idm.example.com/oauth2/openid/:client_id:/.well-known/webfinger`
|
||||
|
||||
The webfinger URL is implemented for each OpenID client, under its specific endpoint, giving full control to the administrator regarding which to use.
|
||||
|
||||
To make this compliant with the standard, it must be made available under the correct [well-known endpoint](https://datatracker.ietf.org/doc/html/rfc7033#section-10.1) (e.g `example.com/.well-known/webfinger`), typically via a reverse proxy or similar. Kanidm doesn't currently provide a mechanism for this URI rewrite.
|
||||
|
||||
One example would be dedicating one client as the "primary" or "default" and redirecting all requests to that. Alternatively, source IP or other request metadata could be used to decide which client to forward the request to.
|
||||
|
||||
### Caddy
|
||||
`Caddyfile`
|
||||
```caddy
|
||||
# assuming a kanidm service with domain "example.com"
|
||||
example.com {
|
||||
redir /.well-known/webfinger https://idm.example.com/oauth2/openid/:client_id:{uri} 307
|
||||
}
|
||||
```
|
||||
**Note:** the `{uri}` is important as it preserves the original request past the redirect.
|
||||
|
||||
|
||||
</dd>
|
||||
|
||||
<dt>
|
||||
|
@ -229,6 +254,10 @@ kanidm system oauth2 update-scope-map nextcloud nextcloud_users email profile op
|
|||
> * **address** - address
|
||||
> * **phone** - phone_number, phone_number_verified
|
||||
> * **groups** - groups
|
||||
>
|
||||
> In addition Kanidm supports some vendor specific scopes that can include additional claims.
|
||||
>
|
||||
> * **ssh_publickeys** - array of ssh_publickey of the user
|
||||
|
||||
<!-- this is just to split the templates up -->
|
||||
|
||||
|
|
|
@ -54,6 +54,99 @@ In the virtual host, to protect a location/directory
|
|||
</Directory>
|
||||
```
|
||||
|
||||
## Gitea
|
||||
|
||||
[Gitea](https://docs.gitea.com/) is a painless, self-hosted, all-in-one software
|
||||
development service. It has built in support for
|
||||
[external authentication](https://docs.gitea.com/administration/authentication)
|
||||
including OAuth2.
|
||||
|
||||
To set up a Gitea instance to authenticate with Kanidm:
|
||||
|
||||
1. Add an email address to your regular Kanidm account, if it doesn't have one
|
||||
already:
|
||||
|
||||
```sh
|
||||
kanidm person update your_username -m your_username@example.com
|
||||
```
|
||||
|
||||
2. Create a new Kanidm group for your Gitea users (`gitea_users`), and add your
|
||||
regular account to it:
|
||||
|
||||
```sh
|
||||
kanidm group create gitea_users
|
||||
kanidm group add-members gitea_users your_username
|
||||
```
|
||||
|
||||
3. Create a new OAuth2 application configuration in Kanidm (`gitea`), configure
|
||||
the redirect URL, and scope access to the `gitea_users` group:
|
||||
|
||||
```sh
|
||||
kanidm system oauth2 create gitea Gitea https://gitea.example.com/user/login
|
||||
kanidm system oauth2 add-redirect-url gitea https://gitea.example.com/user/oauth2/kanidm/callback
|
||||
kanidm system oauth2 update-scope-map gitea gitea_users email openid profile groups
|
||||
```
|
||||
|
||||
4. Gitea currently [does not support PKCE](https://github.com/go-gitea/gitea/issues/21376)
|
||||
in their OIDC implementation. If you do not perform this step, you will see an error like
|
||||
`No PKCE code challenge was provided with client in enforced PKCE mode.`
|
||||
in your Kanidm server logs. Therefore, we have to disable PKCE for Gitea:
|
||||
|
||||
```sh
|
||||
kanidm system oauth2 warning-insecure-client-disable-pkce gitea
|
||||
```
|
||||
|
||||
5. Get the `gitea` OAuth2 client secret from Kanidm:
|
||||
|
||||
```sh
|
||||
kanidm system oauth2 show-basic-secret gitea
|
||||
```
|
||||
|
||||
6. Log in to Gitea with an administrator account and go to Site Administration
|
||||
-> Identity & Access -> Authentication Sources, and "Add Authentication Source",
|
||||
then provide the following details:
|
||||
* **Type**: `OAuth2`
|
||||
* **Name**: `kanidm`, in case you want to choose a different name, make sure
|
||||
to update `kanidm` in the redirect URL in step 3. The full redirect URL is
|
||||
provided at the bottom of the current configuration page in Gitea.
|
||||
* **OAuth2 Provider**: `OpenID Connect`
|
||||
* **Client ID (key)**: `gitea`
|
||||
* **Client Secret**: [from show-basic-secret above]
|
||||
* **OpenID Connect Auto Discovery URL**: `https://kanidm.example.com/oauth2/openid/gitea/.well-known/openid-configuration`
|
||||
|
||||
Alternatively, you can provide the configuration via the CLI:
|
||||
|
||||
```sh
|
||||
gitea admin auth add-oauth \
|
||||
--provider=openidConnect \
|
||||
--name=kanidm \
|
||||
--key=gitea \
|
||||
--secret=[from show-basic-secret above] \
|
||||
--auto-discover-url=https://kanidm.example.com/oauth2/openid/gitea/.well-known/openid-configuration \
|
||||
```
|
||||
|
||||
You should now see a "Sign in with Kanidm" button on your Gitea login page.
|
||||
|
||||
You may additionally want to configure:
|
||||
|
||||
* A Gitea themed icon in Kanidm for the `gitea` OAuth2 application:
|
||||
```sh
|
||||
curl -LO https://gitea.example.com/assets/img/logo.svg
|
||||
kanidm system oauth2 set-image gitea logo.svg svg
|
||||
rm logo.svg
|
||||
```
|
||||
|
||||
* To disable password authentication in Gitea, add the following
|
||||
[configuration](https://docs.gitea.com/next/administration/config-cheat-sheet)
|
||||
to `app.ini`:
|
||||
|
||||
```ini
|
||||
[service]
|
||||
ALLOW_ONLY_EXTERNAL_REGISTRATION = true
|
||||
SHOW_REGISTRATION_BUTTON = false
|
||||
ENABLE_PASSWORD_SIGNIN_FORM = false
|
||||
```
|
||||
|
||||
## GitLab
|
||||
|
||||
[GitLab](https://gitlab.com) is a Git-based software development platform, which
|
||||
|
|
|
@ -103,82 +103,26 @@ kanidm service-account credential generate --name admin radius_service_account
|
|||
## Deploying a RADIUS Container
|
||||
|
||||
We provide a RADIUS container that has all the needed integrations. This container requires some
|
||||
cryptographic material, with the following files being in `/etc/raddb/certs`. (Modifiable in the
|
||||
cryptographic material, with the following files mounted in `/data`. (Modifiable in the
|
||||
configuration)
|
||||
|
||||
| filename | description |
|
||||
| -------- | ------------------------------------------------------------- |
|
||||
| ca.pem | The signing CA of the RADIUS certificate |
|
||||
| dh.pem | The output of `openssl dhparam -in ca.pem -out ./dh.pem 2048` |
|
||||
| cert.pem | The certificate for the RADIUS server |
|
||||
| key.pem | The signing key for the RADIUS certificate |
|
||||
| filename | description |
|
||||
| -------- | ------------------------------------------------------------- |
|
||||
| ca.pem | The signing CA of the RADIUS certificate |
|
||||
| cert.pem | The certificate for the RADIUS server |
|
||||
| key.pem | The private key for the RADIUS certificate |
|
||||
| radius.toml | The configuration file |
|
||||
|
||||
The configuration file (`/data/kanidm`) has the following template:
|
||||
The configuration file (which you should mount at `/data/radius.toml`, or specify its path with the environment variable `KANIDM_RLM_CONFIG`) has the following template:
|
||||
|
||||
```toml
|
||||
uri = "https://example.com" # URL to the Kanidm server
|
||||
verify_hostnames = true # verify the hostname of the Kanidm server
|
||||
|
||||
verify_ca = false # Strict CA verification
|
||||
ca = /data/ca.pem # Path to the kanidm ca
|
||||
|
||||
auth_token = "ABC..." # Auth token for the service account
|
||||
# See: kanidm service-account api-token generate
|
||||
|
||||
# Default vlans for groups that don't specify one.
|
||||
radius_default_vlan = 1
|
||||
|
||||
# A list of Kanidm groups which must be a member
|
||||
# before they can authenticate via RADIUS.
|
||||
radius_required_groups = [
|
||||
"radius_access_allowed@idm.example.com",
|
||||
]
|
||||
|
||||
# A mapping between Kanidm groups and VLANS
|
||||
radius_groups = [
|
||||
{ spn = "radius_access_allowed@idm.example.com", vlan = 10 },
|
||||
]
|
||||
|
||||
# A mapping of clients and their authentication tokens
|
||||
radius_clients = [
|
||||
{ name = "test", ipaddr = "127.0.0.1", secret = "testing123" },
|
||||
{ name = "docker" , ipaddr = "172.17.0.0/16", secret = "testing123" },
|
||||
]
|
||||
|
||||
# radius_cert_path = "/etc/raddb/certs/cert.pem"
|
||||
# the signing key for radius TLS
|
||||
# radius_key_path = "/etc/raddb/certs/key.pem"
|
||||
# the diffie-hellman output
|
||||
# radius_dh_path = "/etc/raddb/certs/dh.pem"
|
||||
# the CA certificate
|
||||
# radius_ca_path = "/etc/raddb/certs/ca.pem"
|
||||
{{#rustdoc_include ../../../examples/radius.toml}}
|
||||
```
|
||||
|
||||
## A fully configured example
|
||||
|
||||
```toml
|
||||
url = "https://example.com"
|
||||
|
||||
# The auth token for the service account
|
||||
auth_token = "ABC..."
|
||||
|
||||
# default vlan for groups that don't specify one.
|
||||
radius_default_vlan = 99
|
||||
|
||||
# if the user is in one of these Kanidm groups,
|
||||
# then they're allowed to authenticate
|
||||
radius_required_groups = [
|
||||
"radius_access_allowed@idm.example.com",
|
||||
]
|
||||
|
||||
radius_groups = [
|
||||
{ spn = "radius_access_allowed@idm.example.com", vlan = 10 }
|
||||
]
|
||||
|
||||
radius_clients = [
|
||||
{ name = "localhost", ipaddr = "127.0.0.1", secret = "testing123" },
|
||||
{ name = "docker" , ipaddr = "172.17.0.0/16", secret = "testing123" },
|
||||
]
|
||||
{{#rustdoc_include ../../../examples/radius_full.toml}}
|
||||
```
|
||||
|
||||
## Moving to Production
|
||||
|
@ -200,14 +144,17 @@ the problem. To increase the logging level you can re-run your environment with
|
|||
```bash
|
||||
docker rm radiusd
|
||||
docker run --name radiusd \
|
||||
-e DEBUG=True \
|
||||
--rm -e DEBUG=True \
|
||||
-p 1812:1812 \
|
||||
-p 1812:1812/udp
|
||||
-p 1812:1812/udp \
|
||||
--interactive --tty \
|
||||
--volume /tmp/kanidm:/etc/raddb/certs \
|
||||
--mount "type=bind,src=$(pwd)/examples/radius.toml,target=/data/kanidm" \
|
||||
--mount "type=bind,src=/tmp/kanidm,target=/data" \
|
||||
kanidm/radius:latest
|
||||
```
|
||||
|
||||
In this example we're running it from the root of the repository and loading an example config, and using the certificates generated in dev-mode. You'll need to adjust your mounts to suit!
|
||||
|
||||
Note: the RADIUS container _is_ configured to provide
|
||||
[Tunnel-Private-Group-ID](https://freeradius.org/rfc/rfc2868.html#Tunnel-Private-Group-ID), so if
|
||||
you wish to use Wi-Fi-assigned VLANs on your infrastructure, you can assign these by groups in the
|
||||
|
|
|
@ -33,7 +33,8 @@ You can find the name of your 389 Directory Server instance with:
|
|||
|
||||
```bash
|
||||
# Run on the FreeIPA server
|
||||
dsconf --list
|
||||
dsctl --list
|
||||
> slapd-DEV-KANIDM-COM
|
||||
```
|
||||
|
||||
Using this you can show the current status of the retro changelog plugin to see if you need to
|
||||
|
@ -83,6 +84,20 @@ kanidm-ipa-sync [-c /path/to/kanidm/config] -i /path/to/kanidm-ipa-sync -n
|
|||
kanidm-ipa-sync -i /etc/kanidm/ipa-sync -n
|
||||
```
|
||||
|
||||
As the sync tool is part of the tools container, you can run this with:
|
||||
|
||||
```bash
|
||||
docker run --rm -i -t \
|
||||
--user uid:gid \
|
||||
-p 12345:12345 \
|
||||
-v /etc/kanidm/config:/etc/kanidm/config:ro \
|
||||
-v /path/to/kanidm.ca.pem:/path/to/kanidm.ca.pem:ro
|
||||
-v /path/to/ipa-ca.pem:/etc/kanidm/ipa-ca.pem:ro \
|
||||
-v /path/to/ipa-sync:/etc/kanidm/ipa-sync:ro \
|
||||
kanidm/tools:latest \
|
||||
kanidm-ipa-sync -i /etc/kanidm/ipa-sync -
|
||||
```
|
||||
|
||||
## Running the Sync Tool Automatically
|
||||
|
||||
The sync tool can be run on a schedule if you configure the `schedule` parameter, and provide the
|
||||
|
@ -96,11 +111,14 @@ kanidm-ipa-sync -i /etc/kanidm/ipa-sync --schedule
|
|||
As the sync tool is part of the tools container, you can run this with:
|
||||
|
||||
```bash
|
||||
docker create --name kanidm-ipa-sync \
|
||||
docker run --name kanidm-ipa-sync \
|
||||
--user uid:gid \
|
||||
-p 12345:12345 \
|
||||
-v /etc/kanidm/config:/etc/kanidm/config:ro \
|
||||
-v /path/to/kanidm.ca.pem:/path/to/kanidm.ca.pem:ro
|
||||
-v /path/to/ipa-ca.pem:/etc/kanidm/ipa-ca.pem:ro \
|
||||
-v /path/to/ipa-sync:/etc/kanidm/ipa-sync:ro \
|
||||
kanidm/tools:latest \
|
||||
kanidm-ipa-sync -i /etc/kanidm/ipa-sync --schedule
|
||||
```
|
||||
|
||||
|
|
Binary file not shown.
|
@ -17,8 +17,9 @@ sync_token = "eyJhb..."
|
|||
# server in the IPA topology rather than via a load balancer or dns srv records. This
|
||||
# is to prevent replication conflicts and issues due to how 389-ds content sync works.
|
||||
ipa_uri = "ldaps://specific-server.ipa.dev.kanidm.com"
|
||||
# Path to the IPA CA certificate in PEM format.
|
||||
ipa_ca = "/path/to/kanidm-ipa-ca.pem"
|
||||
# Path to the IPA CA certificate in PEM format. This can be found on an IPA server
|
||||
# in the file `/etc/ipa/ca.crt`
|
||||
ipa_ca = "/path/to/ipa-ca.pem"
|
||||
# The DN of an account with content sync rights. By default cn=Directory Manager has
|
||||
# this access.
|
||||
ipa_sync_dn = "cn=Directory Manager"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
## Kanidm minimal Service Configuration - /etc/kanidm/config
|
||||
# Kanidm minimal Service Configuration - /etc/kanidm/config
|
||||
# For a full example and documentation, see /usr/share/kanidm/kanidm
|
||||
# or `example/kanidm` in the source repository.
|
||||
|
||||
# Replace this with your kanidmd URI and uncomment the line
|
||||
#uri = "https://idm.example.com"
|
||||
# uri = "https://idm.example.com"
|
||||
verify_ca = true
|
||||
|
|
18
examples/radius.toml
Normal file
18
examples/radius.toml
Normal file
|
@ -0,0 +1,18 @@
|
|||
uri = "https://example.com"
|
||||
|
||||
# The auth token for the service account
|
||||
auth_token = "ABC..."
|
||||
|
||||
# default vlan for groups that don't specify one.
|
||||
radius_default_vlan = 99
|
||||
|
||||
# if the user is in one of these Kanidm groups,
|
||||
# then they're allowed to authenticate
|
||||
radius_required_groups = ["radius_access_allowed@idm.example.com"]
|
||||
|
||||
radius_groups = [{ spn = "radius_access_allowed@idm.example.com", vlan = 10 }]
|
||||
|
||||
radius_clients = [
|
||||
{ name = "localhost", ipaddr = "127.0.0.1", secret = "testing123" },
|
||||
{ name = "docker", ipaddr = "172.17.0.0/16", secret = "testing123" },
|
||||
]
|
28
examples/radius_full.toml
Normal file
28
examples/radius_full.toml
Normal file
|
@ -0,0 +1,28 @@
|
|||
uri = "https://example.com" # URL to the Kanidm server
|
||||
verify_hostnames = true # verify the hostname of the Kanidm server
|
||||
verify_ca = true # Strict CA verification
|
||||
|
||||
auth_token = "ABC..." # Auth token for the service account
|
||||
# See: kanidm service-account api-token generate
|
||||
|
||||
# Default vlans for groups that don't specify one.
|
||||
radius_default_vlan = 1
|
||||
|
||||
# A list of Kanidm groups which must be a member
|
||||
# before they can authenticate via RADIUS.
|
||||
radius_required_groups = ["radius_access_allowed@idm.example.com"]
|
||||
|
||||
# A mapping between Kanidm groups and VLANS
|
||||
radius_groups = [{ spn = "radius_access_allowed@idm.example.com", vlan = 10 }]
|
||||
|
||||
# A mapping of clients and their authentication tokens
|
||||
radius_clients = [
|
||||
{ name = "test", ipaddr = "127.0.0.1", secret = "testing123" },
|
||||
{ name = "docker", ipaddr = "172.17.0.0/16", secret = "testing123" },
|
||||
]
|
||||
|
||||
# radius_cert_path = "/etc/raddb/certs/cert.pem"
|
||||
# the signing key for radius TLS
|
||||
# radius_key_path = "/etc/raddb/certs/key.pem"
|
||||
radius_ca_path = "/data/ca.pem" # Path to the kanidm ca
|
||||
# radius_ca_dir = "/data/ca"
|
|
@ -1,17 +1,19 @@
|
|||
## Kanidm Unixd minimal Service Configuration - /etc/kanidm/unixd
|
||||
# Kanidm Unixd minimal Service Configuration - /etc/kanidm/unixd
|
||||
# For a full example and documentation, see /usr/share/kanidm-unixd/unixd
|
||||
# or `example/unixd` in the source repository.
|
||||
# or `example/unixd` in the source repository
|
||||
|
||||
version = '2'
|
||||
|
||||
[kanidm]
|
||||
# default_shell = "/bin/sh"
|
||||
|
||||
# home_attr = "uuid"
|
||||
# home_alias = "spn"
|
||||
# use_etc_skel = false
|
||||
|
||||
|
||||
# Defines a set of POSIX groups where membership of any of these groups
|
||||
# will be allowed to login via PAM.
|
||||
# Replace your group below and uncomment this line:
|
||||
#pam_allowed_login_groups = ["your_posix_login_group"]
|
||||
# will be allowed to login via PAM
|
||||
#
|
||||
# WITHOUT THIS SET, NOBODY WILL BE ABLE TO LOG IN VIA PAM
|
||||
#
|
||||
# Replace your group below and uncomment this line
|
||||
# pam_allowed_login_groups = ["your_posix_login_group"]
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::{ClientError, KanidmClient};
|
||||
use kanidm_proto::constants::ATTR_DOMAIN_ALLOW_EASTER_EGGS;
|
||||
use kanidm_proto::internal::ImageValue;
|
||||
use reqwest::multipart;
|
||||
|
||||
|
@ -8,6 +9,14 @@ impl KanidmClient {
|
|||
self.perform_delete_request("/v1/domain/_image").await
|
||||
}
|
||||
|
||||
pub async fn idm_set_domain_allow_easter_eggs(&self, enable: bool) -> Result<(), ClientError> {
|
||||
self.perform_put_request(
|
||||
&format!("{}{}", "/v1/domain/_attr/", ATTR_DOMAIN_ALLOW_EASTER_EGGS),
|
||||
vec![enable.to_string()],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Add or update the domain logo/image
|
||||
pub async fn idm_domain_update_image(&self, image: ImageValue) -> Result<(), ClientError> {
|
||||
let file_content_type = image.filetype.as_content_type_str();
|
||||
|
|
|
@ -37,6 +37,14 @@ impl KanidmClient {
|
|||
.await
|
||||
}
|
||||
|
||||
pub async fn group_account_policy_authsession_expiry_reset(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
self.perform_delete_request(&format!("/v1/group/{}/_attr/authsession_expiry", id))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn group_account_policy_credential_type_minimum_set(
|
||||
&self,
|
||||
id: &str,
|
||||
|
@ -61,6 +69,17 @@ impl KanidmClient {
|
|||
.await
|
||||
}
|
||||
|
||||
pub async fn group_account_policy_password_minimum_length_reset(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
self.perform_delete_request(&format!(
|
||||
"/v1/group/{}/_attr/auth_password_minimum_length",
|
||||
id
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn group_account_policy_privilege_expiry_set(
|
||||
&self,
|
||||
id: &str,
|
||||
|
@ -73,6 +92,14 @@ impl KanidmClient {
|
|||
.await
|
||||
}
|
||||
|
||||
pub async fn group_account_policy_privilege_expiry_reset(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
self.perform_delete_request(&format!("/v1/group/{}/_attr/privilege_expiry", id))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn group_account_policy_webauthn_attestation_set(
|
||||
&self,
|
||||
id: &str,
|
||||
|
@ -85,6 +112,17 @@ impl KanidmClient {
|
|||
.await
|
||||
}
|
||||
|
||||
pub async fn group_account_policy_webauthn_attestation_reset(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
self.perform_delete_request(&format!(
|
||||
"/v1/group/{}/_attr/webauthn_attestation_ca_list",
|
||||
id
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn group_account_policy_limit_search_max_results(
|
||||
&self,
|
||||
id: &str,
|
||||
|
@ -97,6 +135,14 @@ impl KanidmClient {
|
|||
.await
|
||||
}
|
||||
|
||||
pub async fn group_account_policy_limit_search_max_results_reset(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
self.perform_delete_request(&format!("/v1/group/{}/_attr/limit_search_max_results", id))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn group_account_policy_limit_search_max_filter_test(
|
||||
&self,
|
||||
id: &str,
|
||||
|
@ -109,6 +155,17 @@ impl KanidmClient {
|
|||
.await
|
||||
}
|
||||
|
||||
pub async fn group_account_policy_limit_search_max_filter_test_reset(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
self.perform_delete_request(&format!(
|
||||
"/v1/group/{}/_attr/limit_search_max_filter_test",
|
||||
id
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn group_account_policy_allow_primary_cred_fallback(
|
||||
&self,
|
||||
id: &str,
|
||||
|
|
|
@ -35,3 +35,7 @@ x509-cert = { workspace = true, features = ["pem"] }
|
|||
|
||||
[dev-dependencies]
|
||||
sketching = { workspace = true }
|
||||
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = ["openssl-sys"]
|
||||
|
|
|
@ -16,8 +16,5 @@ doctest = false
|
|||
|
||||
[dependencies]
|
||||
|
||||
[target.'cfg(target_family = "windows")'.dependencies]
|
||||
whoami = { workspace = true }
|
||||
|
||||
[target.'cfg(not(target_family = "windows"))'.dependencies]
|
||||
kanidm_utils_users = { workspace = true }
|
||||
|
|
|
@ -3,6 +3,9 @@ use std::fs::Metadata;
|
|||
#[cfg(target_os = "freebsd")]
|
||||
use std::os::freebsd::fs::MetadataExt;
|
||||
|
||||
#[cfg(target_os = "openbsd")]
|
||||
use std::os::openbsd::fs::MetadataExt;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::os::linux::fs::MetadataExt;
|
||||
|
||||
|
|
|
@ -28,3 +28,7 @@ toml = { workspace = true }
|
|||
[build-dependencies]
|
||||
base64 = { workspace = true }
|
||||
gix = { workspace = true, default-features = false }
|
||||
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = ["gix"]
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
htmx_ui_pkg_path = "/hpkg"
|
||||
# Don't set the cpu_flags to autodetect for this platform
|
||||
# cpu_flags = "none"
|
||||
admin_bind_path = "/data/kanidmd.sock"
|
||||
default_config_path = "/data/server.toml"
|
||||
default_unix_shell_path = "/bin/false"
|
||||
server_admin_bind_path = "/data/kanidmd.sock"
|
||||
server_ui_pkg_path = "/hpkg"
|
||||
server_config_path = "/data/server.toml"
|
||||
client_config_path = "/data/config"
|
||||
resolver_config_path = "/data/unixd"
|
||||
resolver_unix_shell_path = "/bin/false"
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
htmx_ui_pkg_path = "../core/static"
|
||||
# Set to native for developer machines.
|
||||
cpu_flags = "native"
|
||||
admin_bind_path = "/tmp/kanidmd.sock"
|
||||
default_config_path = "../../examples/insecure_server.toml"
|
||||
default_unix_shell_path = "/bin/bash"
|
||||
server_admin_bind_path = "/tmp/kanidmd.sock"
|
||||
server_ui_pkg_path = "../core/static"
|
||||
server_config_path = "../../examples/insecure_server.toml"
|
||||
client_config_path = "/etc/kanidm/config"
|
||||
resolver_config_path = "/tmp/unixd"
|
||||
resolver_unix_shell_path = "/bin/bash"
|
||||
|
|
8
libs/profiles/release_freebsd.toml
Normal file
8
libs/profiles/release_freebsd.toml
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Don't set the value for autodetect
|
||||
# cpu_flags = "none"
|
||||
server_admin_bind_path = "/var/run/kanidmd/sock"
|
||||
server_ui_pkg_path = "/usr/local/share/kanidm/ui/hpkg"
|
||||
server_config_path = "/usr/local/etc/kanidm/server.toml"
|
||||
client_config_path = "/usr/local/etc/kanidm/config"
|
||||
resolver_config_path = "/usr/local/etc/kanidm/unixd"
|
||||
resolver_unix_shell_path = "/bin/sh"
|
|
@ -1,6 +1,8 @@
|
|||
htmx_ui_pkg_path = "/usr/share/kanidm/ui/hpkg"
|
||||
# Don't set the value for autodetect
|
||||
# cpu_flags = "none"
|
||||
admin_bind_path = "/var/run/kanidmd/sock"
|
||||
default_config_path = "/etc/kanidm/server.toml"
|
||||
default_unix_shell_path = "/bin/bash"
|
||||
server_admin_bind_path = "/var/run/kanidmd/sock"
|
||||
server_ui_pkg_path = "/usr/share/kanidm/ui/hpkg"
|
||||
server_config_path = "/etc/kanidm/server.toml"
|
||||
client_config_path = "/etc/kanidm/config"
|
||||
resolver_config_path = "/etc/kanidm/unixd"
|
||||
resolver_unix_shell_path = "/bin/bash"
|
||||
|
|
|
@ -54,12 +54,14 @@ impl std::fmt::Display for CpuOptLevel {
|
|||
#[derive(Debug, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct ProfileConfig {
|
||||
htmx_ui_pkg_path: String,
|
||||
#[serde(default)]
|
||||
cpu_flags: CpuOptLevel,
|
||||
admin_bind_path: String,
|
||||
default_config_path: String,
|
||||
default_unix_shell_path: String,
|
||||
server_admin_bind_path: String,
|
||||
server_config_path: String,
|
||||
server_ui_pkg_path: String,
|
||||
client_config_path: String,
|
||||
resolver_config_path: String,
|
||||
resolver_unix_shell_path: String,
|
||||
}
|
||||
|
||||
pub fn apply_profile() {
|
||||
|
@ -127,19 +129,27 @@ pub fn apply_profile() {
|
|||
println!("cargo:rustc-env=KANIDM_PROFILE_NAME={}", profile);
|
||||
println!("cargo:rustc-env=KANIDM_CPU_FLAGS={}", profile_cfg.cpu_flags);
|
||||
println!(
|
||||
"cargo:rustc-env=KANIDM_HTMX_UI_PKG_PATH={}",
|
||||
profile_cfg.htmx_ui_pkg_path
|
||||
"cargo:rustc-env=KANIDM_SERVER_UI_PKG_PATH={}",
|
||||
profile_cfg.server_ui_pkg_path
|
||||
);
|
||||
println!(
|
||||
"cargo:rustc-env=KANIDM_ADMIN_BIND_PATH={}",
|
||||
profile_cfg.admin_bind_path
|
||||
"cargo:rustc-env=KANIDM_SERVER_ADMIN_BIND_PATH={}",
|
||||
profile_cfg.server_admin_bind_path
|
||||
);
|
||||
println!(
|
||||
"cargo:rustc-env=KANIDM_DEFAULT_CONFIG_PATH={}",
|
||||
profile_cfg.default_config_path
|
||||
"cargo:rustc-env=KANIDM_SERVER_CONFIG_PATH={}",
|
||||
profile_cfg.server_config_path
|
||||
);
|
||||
println!(
|
||||
"cargo:rustc-env=KANIDM_DEFAULT_UNIX_SHELL_PATH={}",
|
||||
profile_cfg.default_unix_shell_path
|
||||
"cargo:rustc-env=KANIDM_CLIENT_CONFIG_PATH={}",
|
||||
profile_cfg.client_config_path
|
||||
);
|
||||
println!(
|
||||
"cargo:rustc-env=KANIDM_RESOLVER_CONFIG_PATH={}",
|
||||
profile_cfg.resolver_config_path
|
||||
);
|
||||
println!(
|
||||
"cargo:rustc-env=KANIDM_RESOLVER_UNIX_SHELL_PATH={}",
|
||||
profile_cfg.resolver_unix_shell_path
|
||||
);
|
||||
}
|
||||
|
|
|
@ -17,9 +17,8 @@ test = false
|
|||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
gethostname = "0.5.0"
|
||||
num_enum = { workspace = true }
|
||||
opentelemetry = { workspace = true, features = ["metrics", "rt-tokio"] }
|
||||
opentelemetry = { workspace = true, features = ["metrics"] }
|
||||
opentelemetry-otlp = { workspace = true, default-features = false, features = [
|
||||
"serde",
|
||||
"logs",
|
||||
|
@ -27,9 +26,12 @@ opentelemetry-otlp = { workspace = true, default-features = false, features = [
|
|||
"http-proto",
|
||||
"grpc-tonic",
|
||||
] }
|
||||
opentelemetry_sdk = { workspace = true }
|
||||
opentelemetry_sdk = { workspace = true, features = ["rt-tokio"] }
|
||||
opentelemetry-semantic-conventions = { workspace = true }
|
||||
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
tracing = { workspace = true, features = ["attributes"] }
|
||||
tracing-core = { workspace = true }
|
||||
tracing-forest = { workspace = true, features = [
|
||||
"uuid",
|
||||
"smallvec",
|
||||
|
|
|
@ -1,16 +1,26 @@
|
|||
use gethostname::gethostname;
|
||||
use opentelemetry::KeyValue;
|
||||
use std::{str::FromStr, time::Duration};
|
||||
|
||||
use opentelemetry_otlp::{Protocol, WithExportConfig};
|
||||
use opentelemetry_sdk::trace::{self, Sampler};
|
||||
use opentelemetry_sdk::Resource;
|
||||
use std::time::Duration;
|
||||
|
||||
use opentelemetry::{global, trace::TracerProvider as _, KeyValue};
|
||||
|
||||
use opentelemetry_sdk::{
|
||||
trace::{Sampler, TracerProvider},
|
||||
Resource,
|
||||
};
|
||||
use tracing::Subscriber;
|
||||
use tracing_subscriber::Registry;
|
||||
use tracing_subscriber::{prelude::*, EnvFilter};
|
||||
use tracing_core::Level;
|
||||
|
||||
use tracing_subscriber::{filter::Directive, prelude::*, EnvFilter, Registry};
|
||||
|
||||
pub const MAX_EVENTS_PER_SPAN: u32 = 64 * 1024;
|
||||
pub const MAX_ATTRIBUTES_PER_SPAN: u32 = 128;
|
||||
|
||||
use opentelemetry_semantic_conventions::{
|
||||
attribute::{SERVICE_NAME, SERVICE_VERSION},
|
||||
SCHEMA_URL,
|
||||
};
|
||||
|
||||
// TODO: this is coming back later
|
||||
// #[allow(dead_code)]
|
||||
// pub fn init_metrics() -> metrics::Result<MeterProvider> {
|
||||
|
@ -44,28 +54,26 @@ pub fn start_logging_pipeline(
|
|||
// adding these filters because when you close out the process the OTLP comms layer is NOISY
|
||||
let forest_filter = forest_filter
|
||||
.add_directive(
|
||||
"tonic=info"
|
||||
.parse()
|
||||
.expect("Failed to set tonic logging to info"),
|
||||
Directive::from_str("tonic=info").expect("Failed to set tonic logging to info"),
|
||||
)
|
||||
.add_directive("h2=info".parse().expect("Failed to set h2 logging to info"))
|
||||
.add_directive(
|
||||
"hyper=info"
|
||||
.parse()
|
||||
.expect("Failed to set hyper logging to info"),
|
||||
Directive::from_str("h2=info").expect("Failed to set h2 logging to info"),
|
||||
)
|
||||
.add_directive(
|
||||
Directive::from_str("hyper=info").expect("Failed to set hyper logging to info"),
|
||||
);
|
||||
let forest_layer = tracing_forest::ForestLayer::default().with_filter(forest_filter);
|
||||
let t_filter: EnvFilter = EnvFilter::builder()
|
||||
.with_default_directive(log_filter.into())
|
||||
.from_env_lossy();
|
||||
|
||||
let tracer = opentelemetry_otlp::new_pipeline().tracing().with_exporter(
|
||||
opentelemetry_otlp::new_exporter()
|
||||
.tonic()
|
||||
.with_endpoint(endpoint)
|
||||
.with_timeout(Duration::from_secs(5))
|
||||
.with_protocol(Protocol::HttpBinary),
|
||||
);
|
||||
let otlp_exporter = opentelemetry_otlp::SpanExporter::builder()
|
||||
.with_tonic()
|
||||
.with_endpoint(endpoint)
|
||||
.with_protocol(Protocol::HttpBinary)
|
||||
.with_timeout(Duration::from_secs(5))
|
||||
.build()
|
||||
.map_err(|err| err.to_string())?;
|
||||
|
||||
// this env var gets set at build time, if we can pull it, add it to the metadata
|
||||
let git_rev = match option_env!("KANIDM_PKG_COMMIT_REV") {
|
||||
|
@ -74,39 +82,47 @@ pub fn start_logging_pipeline(
|
|||
};
|
||||
|
||||
let version = format!("{}{}", env!("CARGO_PKG_VERSION"), git_rev);
|
||||
let hostname = gethostname();
|
||||
let hostname = hostname.to_string_lossy();
|
||||
let hostname = hostname.to_lowercase();
|
||||
// let hostname = gethostname::gethostname();
|
||||
// let hostname = hostname.to_string_lossy();
|
||||
// let hostname = hostname.to_lowercase();
|
||||
|
||||
let tracer = tracer
|
||||
.with_trace_config(
|
||||
trace::config()
|
||||
// we want *everything!*
|
||||
.with_sampler(Sampler::AlwaysOn)
|
||||
.with_max_events_per_span(MAX_EVENTS_PER_SPAN)
|
||||
.with_max_attributes_per_span(MAX_ATTRIBUTES_PER_SPAN)
|
||||
.with_resource(Resource::new(vec![
|
||||
KeyValue::new("service.name", service_name),
|
||||
KeyValue::new("service.version", version),
|
||||
KeyValue::new("host.name", hostname),
|
||||
// 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
|
||||
])),
|
||||
let resource = Resource::from_schema_url(
|
||||
[
|
||||
// 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)
|
||||
// we want *everything!*
|
||||
.with_sampler(Sampler::AlwaysOn)
|
||||
.with_max_events_per_span(MAX_EVENTS_PER_SPAN)
|
||||
.with_max_attributes_per_span(MAX_ATTRIBUTES_PER_SPAN)
|
||||
.with_resource(resource)
|
||||
.build();
|
||||
|
||||
global::set_tracer_provider(provider.clone());
|
||||
provider.tracer("tracing-otel-subscriber");
|
||||
use tracing_opentelemetry::OpenTelemetryLayer;
|
||||
|
||||
let registry = tracing_subscriber::registry()
|
||||
.with(
|
||||
tracing_subscriber::filter::LevelFilter::from_level(Level::INFO)
|
||||
.with_filter(t_filter),
|
||||
)
|
||||
.install_batch(opentelemetry::runtime::Tokio)
|
||||
.map_err(|err| {
|
||||
let err = format!("Failed to start OTLP pipeline: {:?}", err);
|
||||
eprintln!("{}", err);
|
||||
err
|
||||
})?;
|
||||
// Create a tracing layer with the configured tracer;
|
||||
let telemetry = tracing_opentelemetry::layer()
|
||||
.with_tracer(tracer)
|
||||
.with_threads(true)
|
||||
.with_filter(t_filter);
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
// .with(MetricsLayer::new(meter_provider.clone()))
|
||||
.with(forest_layer)
|
||||
.with(OpenTelemetryLayer::new(
|
||||
provider.tracer("tracing-otel-subscriber"),
|
||||
));
|
||||
|
||||
Ok(Box::new(
|
||||
Registry::default().with(forest_layer).with(telemetry),
|
||||
))
|
||||
Ok(Box::new(registry))
|
||||
}
|
||||
None => {
|
||||
let forest_layer = tracing_forest::ForestLayer::default().with_filter(forest_filter);
|
||||
|
@ -122,7 +138,6 @@ pub struct TracingPipelineGuard {}
|
|||
impl Drop for TracingPipelineGuard {
|
||||
fn drop(&mut self) {
|
||||
opentelemetry::global::shutdown_tracer_provider();
|
||||
opentelemetry::global::shutdown_logger_provider();
|
||||
eprintln!("Logging pipeline completed shutdown");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
[package]
|
||||
name = "kanidm_utils_users"
|
||||
description = "Kanidm utility crate"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
version = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
license = { workspace = true }
|
||||
homepage = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
|
||||
[lib]
|
||||
test = true
|
||||
|
|
4
platform/freebsd/README.md
Normal file
4
platform/freebsd/README.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
These are the FreeBSD port Makefiles and other supporting files. In the future we will submit
|
||||
these to FreeBSD ports rather than maintaining them here.
|
||||
|
||||
|
49
platform/freebsd/client/Makefile
Normal file
49
platform/freebsd/client/Makefile
Normal file
|
@ -0,0 +1,49 @@
|
|||
|
||||
PORTNAME= kanidm
|
||||
# DISTVERSION= 1.5.0-dev
|
||||
# DISTVERSIONPREFIX= v
|
||||
|
||||
DISTVERSION= g20250102
|
||||
GH_TAGNAME= edb8cccc84e9dacd2ac31ea1162dd24c0c454c55
|
||||
GH_ACCOUNT= Firstyear
|
||||
|
||||
CATEGORIES= security net databases
|
||||
|
||||
LICENSE= MPL20
|
||||
LICENSE_FILE= ${WRKSRC}/LICENSE.md
|
||||
MAINTAINER= william@blackhats.net.au
|
||||
COMMENT= Simple and secure identity management platform
|
||||
WWW= https://github.com/kanidm/kanidm/
|
||||
|
||||
USES= cargo ssl
|
||||
USE_GITHUB= yes
|
||||
|
||||
ONLY_FOR_ARCHS= aarch64 amd64
|
||||
|
||||
CARGO_ENV= KANIDM_BUILD_PROFILE=release_freebsd
|
||||
|
||||
CARGO_BUILD_ARGS = -p kanidm_tools -p kanidm_unix_int -p nss_kanidm -p pam_kanidm
|
||||
|
||||
CARGO_INSTALL= no
|
||||
|
||||
USE_RC_SUBR= kanidm_unixd kanidm_unixd_tasks
|
||||
|
||||
USERS= _kanidm_unixd
|
||||
GROUPS= _kanidm_unixd
|
||||
|
||||
do-install:
|
||||
${INSTALL_PROGRAM} ${WRKDIR}/target/release/kanidm ${STAGEDIR}${PREFIX}/bin
|
||||
${INSTALL_PROGRAM} ${WRKDIR}/target/release/kanidm-unix ${STAGEDIR}${PREFIX}/bin
|
||||
${INSTALL_PROGRAM} ${WRKDIR}/target/release/kanidm_ssh_authorizedkeys ${STAGEDIR}${PREFIX}/bin
|
||||
${INSTALL_PROGRAM} ${WRKDIR}/target/release/kanidm_ssh_authorizedkeys_direct ${STAGEDIR}${PREFIX}/bin
|
||||
${INSTALL_PROGRAM} ${WRKDIR}/target/release/kanidm_unixd ${STAGEDIR}${PREFIX}/libexec
|
||||
${INSTALL_PROGRAM} ${WRKDIR}/target/release/kanidm_unixd_tasks ${STAGEDIR}${PREFIX}/libexec
|
||||
${INSTALL_LIB} ${WRKDIR}/target/release/libnss_kanidm.so ${STAGEDIR}${PREFIX}/lib/nss_kanidm.so.1
|
||||
${INSTALL_LIB} ${WRKDIR}/target/release/libpam_kanidm.so ${STAGEDIR}${PREFIX}/lib
|
||||
${MKDIR} ${STAGEDIR}${PREFIX}/etc
|
||||
${MKDIR} ${STAGEDIR}${PREFIX}/etc/kanidm
|
||||
${MKDIR} ${STAGEDIR}/var/run/kanidm-unixd
|
||||
${MKDIR} ${STAGEDIR}/var/lib/kanidm-unixd
|
||||
${MKDIR} ${STAGEDIR}/var/cache/kanidm-unixd
|
||||
|
||||
.include <bsd.port.mk>
|
27
platform/freebsd/client/files/kanidm_unixd.in
Normal file
27
platform/freebsd/client/files/kanidm_unixd.in
Normal file
|
@ -0,0 +1,27 @@
|
|||
#!/bin/sh
|
||||
|
||||
# PROVIDE: kanidm_unixd
|
||||
# REQUIRE: LOGIN
|
||||
# KEYWORD: shutdown
|
||||
#
|
||||
# Add these lines to /etc/rc.conf.local or /etc/rc.conf
|
||||
# to enable this service:
|
||||
#
|
||||
# kanidm_unixd_enable (bool): Set to NO by default.
|
||||
# Set it to YES to enable kanidm_unixd.
|
||||
|
||||
. /etc/rc.subr
|
||||
|
||||
name=kanidm_unixd
|
||||
rcvar=kanidm_unixd_enable
|
||||
|
||||
load_rc_config $name
|
||||
|
||||
: ${kanidm_unixd_enable:="NO"}
|
||||
|
||||
pidfile="/var/run/kanidm-unixd.pid"
|
||||
command=/usr/sbin/daemon
|
||||
command_args="-u _kanidm_unixd -p /var/run/kanidm-unixd.pid -T kanidm_unixd /usr/local/libexec/${name}"
|
||||
procname=/usr/local/libexec/${name}
|
||||
|
||||
run_rc_command "$1"
|
27
platform/freebsd/client/files/kanidm_unixd_tasks.in
Normal file
27
platform/freebsd/client/files/kanidm_unixd_tasks.in
Normal file
|
@ -0,0 +1,27 @@
|
|||
#!/bin/sh
|
||||
|
||||
# PROVIDE: kanidm_unixd_tasks
|
||||
# REQUIRE: LOGIN
|
||||
# KEYWORD: shutdown
|
||||
#
|
||||
# Add these lines to /etc/rc.conf.local or /etc/rc.conf
|
||||
# to enable this service:
|
||||
#
|
||||
# kanidm_unixd_tasks_enable (bool): Set to NO by default.
|
||||
# Set it to YES to enable kanidm_unixd_tasks.
|
||||
|
||||
. /etc/rc.subr
|
||||
|
||||
name=kanidm_unixd_tasks
|
||||
rcvar=kanidm_unixd_tasks_enable
|
||||
|
||||
load_rc_config $name
|
||||
|
||||
: ${kanidm_unixd_tasks_enable:="NO"}
|
||||
|
||||
pidfile="/var/run/kanidm-unixd-tasks.pid"
|
||||
command=/usr/sbin/daemon
|
||||
command_args="-u root -p /var/run/kanidm-unixd-tasks.pid -T kanidm_unixd_tasks /usr/local/libexec/${name}"
|
||||
procname=/usr/local/libexec/${name}
|
||||
|
||||
run_rc_command "$1"
|
1
platform/freebsd/client/pkg-descr
Normal file
1
platform/freebsd/client/pkg-descr
Normal file
|
@ -0,0 +1 @@
|
|||
Kanidm is a simple and secure identity provider and client for UNIX systems
|
13
platform/freebsd/client/pkg-plist
Normal file
13
platform/freebsd/client/pkg-plist
Normal file
|
@ -0,0 +1,13 @@
|
|||
bin/kanidm
|
||||
bin/kanidm-unix
|
||||
bin/kanidm_ssh_authorizedkeys
|
||||
bin/kanidm_ssh_authorizedkeys_direct
|
||||
lib/nss_kanidm.so.1
|
||||
lib/libpam_kanidm.so
|
||||
libexec/kanidm_unixd
|
||||
libexec/kanidm_unixd_tasks
|
||||
@dir %%ETCDIR%%
|
||||
@dir /var/lib
|
||||
@dir(_kanidm_unixd,_kanidm_unixd,750) /var/cache/kanidm-unixd
|
||||
@dir(_kanidm_unixd,_kanidm_unixd,750) /var/lib/kanidm-unixd
|
||||
@dir(_kanidm_unixd,_kanidm_unixd,755) /var/run/kanidm-unixd
|
|
@ -42,3 +42,6 @@ sshkeys = { workspace = true }
|
|||
[dev-dependencies]
|
||||
enum-iterator = { workspace = true }
|
||||
serde_urlencoded = { workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
kanidm_build_profiles = { workspace = true }
|
||||
|
|
3
proto/build.rs
Normal file
3
proto/build.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
profiles::apply_profile();
|
||||
}
|
|
@ -53,6 +53,7 @@ pub enum Attribute {
|
|||
DisplayName,
|
||||
Dn,
|
||||
Domain,
|
||||
DomainAllowEasterEggs,
|
||||
DomainDevelopmentTaint,
|
||||
DomainDisplayName,
|
||||
DomainLdapBasedn,
|
||||
|
@ -282,6 +283,7 @@ impl Attribute {
|
|||
Attribute::DisplayName => ATTR_DISPLAYNAME,
|
||||
Attribute::Dn => ATTR_DN,
|
||||
Attribute::Domain => ATTR_DOMAIN,
|
||||
Attribute::DomainAllowEasterEggs => ATTR_DOMAIN_ALLOW_EASTER_EGGS,
|
||||
Attribute::DomainDevelopmentTaint => ATTR_DOMAIN_DEVELOPMENT_TAINT,
|
||||
Attribute::DomainDisplayName => ATTR_DOMAIN_DISPLAY_NAME,
|
||||
Attribute::DomainLdapBasedn => ATTR_DOMAIN_LDAP_BASEDN,
|
||||
|
@ -464,6 +466,7 @@ impl Attribute {
|
|||
ATTR_DISPLAYNAME => Attribute::DisplayName,
|
||||
ATTR_DN => Attribute::Dn,
|
||||
ATTR_DOMAIN => Attribute::Domain,
|
||||
ATTR_DOMAIN_ALLOW_EASTER_EGGS => Attribute::DomainAllowEasterEggs,
|
||||
ATTR_DOMAIN_DISPLAY_NAME => Attribute::DomainDisplayName,
|
||||
ATTR_DOMAIN_DEVELOPMENT_TAINT => Attribute::DomainDevelopmentTaint,
|
||||
ATTR_DOMAIN_LDAP_BASEDN => Attribute::DomainLdapBasedn,
|
||||
|
|
|
@ -30,7 +30,7 @@ pub const VALID_IMAGE_UPLOAD_CONTENT_TYPES: [&str; 5] = [
|
|||
pub const APPLICATION_JSON: &str = "application/json";
|
||||
|
||||
/// The "system" path for Kanidm client config
|
||||
pub const DEFAULT_CLIENT_CONFIG_PATH: &str = "/etc/kanidm/config";
|
||||
pub const DEFAULT_CLIENT_CONFIG_PATH: &str = env!("KANIDM_CLIENT_CONFIG_PATH");
|
||||
/// The user-owned path for Kanidm client config
|
||||
pub const DEFAULT_CLIENT_CONFIG_PATH_HOME: &str = "~/.config/kanidm";
|
||||
|
||||
|
@ -89,6 +89,7 @@ pub const ATTR_DESCRIPTION: &str = "description";
|
|||
pub const ATTR_DIRECTMEMBEROF: &str = "directmemberof";
|
||||
pub const ATTR_DISPLAYNAME: &str = "displayname";
|
||||
pub const ATTR_DN: &str = "dn";
|
||||
pub const ATTR_DOMAIN_ALLOW_EASTER_EGGS: &str = "domain_allow_easter_eggs";
|
||||
pub const ATTR_DOMAIN_DEVELOPMENT_TAINT: &str = "domain_development_taint";
|
||||
pub const ATTR_DOMAIN_DISPLAY_NAME: &str = "domain_display_name";
|
||||
pub const ATTR_DOMAIN_LDAP_BASEDN: &str = "domain_ldap_basedn";
|
||||
|
@ -218,6 +219,7 @@ pub const ATTR_ALLOW_PRIMARY_CRED_FALLBACK: &str = "allow_primary_cred_fallback"
|
|||
|
||||
pub const OAUTH2_SCOPE_EMAIL: &str = ATTR_EMAIL;
|
||||
pub const OAUTH2_SCOPE_GROUPS: &str = "groups";
|
||||
pub const OAUTH2_SCOPE_SSH_PUBLICKEYS: &str = "ssh_publickeys";
|
||||
pub const OAUTH2_SCOPE_OPENID: &str = "openid";
|
||||
pub const OAUTH2_SCOPE_READ: &str = "read";
|
||||
pub const OAUTH2_SCOPE_SUPPLEMENT: &str = "supplement";
|
||||
|
|
|
@ -160,6 +160,7 @@ pub enum CURegWarning {
|
|||
AttestedResidentKeyRequired,
|
||||
Unsatisfiable,
|
||||
WebauthnAttestationUnsatisfiable,
|
||||
WebauthnUserVerificationRequired,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
|
|
|
@ -142,6 +142,13 @@ pub enum OperationError {
|
|||
DatabaseLockAcquisitionTimeout,
|
||||
|
||||
// Specific internal errors.
|
||||
AU0001InvalidState,
|
||||
AU0002JwsSerialisation,
|
||||
AU0003JwsSignature,
|
||||
AU0004UserAuthTokenInvalid,
|
||||
AU0005DelayedProcessFailure,
|
||||
AU0006CredentialMayNotReauthenticate,
|
||||
AU0007UserAuthTokenInvalid,
|
||||
|
||||
// Kanidm Generic Errors
|
||||
KG001TaskTimeout,
|
||||
|
@ -152,6 +159,15 @@ pub enum OperationError {
|
|||
CU0001WebauthnAttestationNotTrusted,
|
||||
CU0002WebauthnRegistrationError,
|
||||
CU0003WebauthnUserNotVerified,
|
||||
|
||||
// The session is inconsistent and can't be committed, but the errors
|
||||
// can be resolved.
|
||||
CU0004SessionInconsistent,
|
||||
// Another session used this intent token, and so it can't be committed.
|
||||
CU0005IntentTokenConflict,
|
||||
// The intent token was invalidated before we could commit.
|
||||
CU0006IntentTokenInvalidated,
|
||||
|
||||
// ValueSet errors
|
||||
VS0001IncomingReplSshPublicKey,
|
||||
VS0002CertificatePublicKeyDigest,
|
||||
|
@ -259,6 +275,7 @@ pub enum OperationError {
|
|||
// Web UI
|
||||
UI0001ChallengeSerialisation,
|
||||
UI0002InvalidState,
|
||||
UI0003InvalidOauth2Resume,
|
||||
|
||||
// Unixd Things
|
||||
KU001InitWhileSessionActive,
|
||||
|
@ -295,7 +312,7 @@ impl Display for OperationError {
|
|||
|
||||
impl OperationError {
|
||||
/// Return the message associated with the error if there is one.
|
||||
fn message(&self) -> Option<String> {
|
||||
pub fn message(&self) -> Option<String> {
|
||||
match self {
|
||||
Self::SessionExpired => None,
|
||||
Self::EmptyRequest => None,
|
||||
|
@ -361,9 +378,23 @@ impl OperationError {
|
|||
Self::TransactionAlreadyCommitted => None,
|
||||
Self::ValueDenyName => None,
|
||||
Self::DatabaseLockAcquisitionTimeout => Some("Unable to acquire a database lock - the current server may be too busy. Try again later.".into()),
|
||||
|
||||
Self::AU0001InvalidState => Some("Invalid authentication session state for request".into()),
|
||||
Self::AU0002JwsSerialisation => Some("JWS serialisation failed".into()),
|
||||
Self::AU0003JwsSignature => Some("JWS signature failed".into()),
|
||||
Self::AU0004UserAuthTokenInvalid => Some("User auth token was unable to be generated".into()),
|
||||
Self::AU0005DelayedProcessFailure => Some("Delaying processing failure, unable to proceed".into()),
|
||||
Self::AU0006CredentialMayNotReauthenticate => Some("Credential may not reauthenticate".into()),
|
||||
Self::AU0007UserAuthTokenInvalid => Some("User auth token was unable to be generated".into()),
|
||||
|
||||
Self::CU0001WebauthnAttestationNotTrusted => None,
|
||||
Self::CU0002WebauthnRegistrationError => None,
|
||||
Self::CU0003WebauthnUserNotVerified => Some("User Verification bit not set while registering credential, you may need to configure a PIN on this device.".into()),
|
||||
|
||||
Self::CU0004SessionInconsistent => Some("The session is unable to be committed due to unresolved warnings.".into()),
|
||||
Self::CU0005IntentTokenConflict => Some("The intent token used to create this session has been reused in another browser/tab and may not proceed.".into()),
|
||||
Self::CU0006IntentTokenInvalidated => Some("The intent token has been invalidated/revoked before the commit could be accepted. Has it been used in another browser or tab?".into()),
|
||||
|
||||
Self::DB0001MismatchedRestoreVersion => None,
|
||||
Self::DB0002MismatchedRestoreVersion => None,
|
||||
Self::DB0003FilterResolveCacheBuild => None,
|
||||
|
@ -443,25 +474,26 @@ impl OperationError {
|
|||
Self::SC0009IndexTypeSyntaxInvalid => Some("A SCIM IndexType contained invalid syntax".into()),
|
||||
Self::SC0010DateTimeSyntaxInvalid => Some("A SCIM DateTime contained invalid syntax".into()),
|
||||
|
||||
Self::SC0011AddressSyntaxInvalid => Some("A SCIM Address contained invalid syntax".into()),
|
||||
Self::SC0012CertificateSyntaxInvalid => Some("A SCIM Certificate contained invalid binary data".into()),
|
||||
Self::SC0013CertificateInvalidDer => Some("A SCIM Certificate did not contain valid DER".into()),
|
||||
Self::SC0014CertificateInvalidDigest => Some("A SCIM Certificate was unable to be digested".into()),
|
||||
Self::SC0015CredentialTypeSyntaxInvalid => Some("A SCIM CredentialType contained invalid syntax".into()),
|
||||
Self::SC0016InameSyntaxInvalid => Some("A SCIM Iname string contained invalid syntax".into()),
|
||||
Self::SC0017Iutf8SyntaxInvalid => Some("A SCIM Iutf8 string contained invalid syntax".into()),
|
||||
Self::SC0018NsUniqueIdSyntaxInvalid => Some("A SCIM NsUniqueID contained invalid syntax".into()),
|
||||
Self::SC0019Oauth2ScopeSyntaxInvalid => Some("A SCIM Oauth2 Scope contained invalid syntax".into()),
|
||||
Self::SC0020Oauth2ScopeMapSyntaxInvalid => Some("A SCIM Oauth2 Scope Map contained invalid syntax".into()),
|
||||
Self::SC0021Oauth2ScopeMapMissingGroupIdentifier => Some("A SCIM Oauth2 Scope Map was missing a group name or uuid".into()),
|
||||
Self::SC0022Oauth2ClaimMapSyntaxInvalid => Some("A SCIM Oauth2 Claim Map contained invalid syntax".into()),
|
||||
Self::SC0023Oauth2ClaimMapMissingGroupIdentifier => Some("A SCIM Claim Map was missing a group name or uuid".into()),
|
||||
Self::SC0024SshPublicKeySyntaxInvalid => Some("A SCIM Ssh Public Key contained invalid syntax".into()),
|
||||
Self::SC0025UiHintSyntaxInvalid => Some("A SCIM UiHint contained invalid syntax".into()),
|
||||
Self::SC0026Utf8SyntaxInvalid => Some("A SCIM Utf8 String Scope Map contained invalid syntax".into()),
|
||||
Self::SC0011AddressSyntaxInvalid => Some("A SCIM Address contained invalid syntax".into()),
|
||||
Self::SC0012CertificateSyntaxInvalid => Some("A SCIM Certificate contained invalid binary data".into()),
|
||||
Self::SC0013CertificateInvalidDer => Some("A SCIM Certificate did not contain valid DER".into()),
|
||||
Self::SC0014CertificateInvalidDigest => Some("A SCIM Certificate was unable to be digested".into()),
|
||||
Self::SC0015CredentialTypeSyntaxInvalid => Some("A SCIM CredentialType contained invalid syntax".into()),
|
||||
Self::SC0016InameSyntaxInvalid => Some("A SCIM Iname string contained invalid syntax".into()),
|
||||
Self::SC0017Iutf8SyntaxInvalid => Some("A SCIM Iutf8 string contained invalid syntax".into()),
|
||||
Self::SC0018NsUniqueIdSyntaxInvalid => Some("A SCIM NsUniqueID contained invalid syntax".into()),
|
||||
Self::SC0019Oauth2ScopeSyntaxInvalid => Some("A SCIM Oauth2 Scope contained invalid syntax".into()),
|
||||
Self::SC0020Oauth2ScopeMapSyntaxInvalid => Some("A SCIM Oauth2 Scope Map contained invalid syntax".into()),
|
||||
Self::SC0021Oauth2ScopeMapMissingGroupIdentifier => Some("A SCIM Oauth2 Scope Map was missing a group name or uuid".into()),
|
||||
Self::SC0022Oauth2ClaimMapSyntaxInvalid => Some("A SCIM Oauth2 Claim Map contained invalid syntax".into()),
|
||||
Self::SC0023Oauth2ClaimMapMissingGroupIdentifier => Some("A SCIM Claim Map was missing a group name or uuid".into()),
|
||||
Self::SC0024SshPublicKeySyntaxInvalid => Some("A SCIM Ssh Public Key contained invalid syntax".into()),
|
||||
Self::SC0025UiHintSyntaxInvalid => Some("A SCIM UiHint contained invalid syntax".into()),
|
||||
Self::SC0026Utf8SyntaxInvalid => Some("A SCIM Utf8 String Scope Map contained invalid syntax".into()),
|
||||
|
||||
Self::UI0001ChallengeSerialisation => Some("The WebAuthn challenge was unable to be serialised.".into()),
|
||||
Self::UI0002InvalidState => Some("The credential update process returned an invalid state transition.".into()),
|
||||
Self::UI0003InvalidOauth2Resume => Some("The server attemped to resume OAuth2, but no OAuth2 session is in progress.".into()),
|
||||
Self::VL0001ValueSshPublicKeyString => None,
|
||||
Self::VS0001IncomingReplSshPublicKey => None,
|
||||
Self::VS0002CertificatePublicKeyDigest |
|
||||
|
|
|
@ -6,7 +6,9 @@ use base64::{engine::general_purpose::STANDARD, Engine as _};
|
|||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::base64::{Base64, UrlSafe};
|
||||
use serde_with::formats::SpaceSeparator;
|
||||
use serde_with::{formats, serde_as, skip_serializing_none, StringWithSeparator};
|
||||
use serde_with::{
|
||||
formats, serde_as, skip_serializing_none, NoneAsEmptyString, StringWithSeparator,
|
||||
};
|
||||
use url::Url;
|
||||
use uuid::Uuid;
|
||||
|
||||
|
@ -38,9 +40,19 @@ pub struct PkceRequest {
|
|||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct AuthorisationRequest {
|
||||
// Must be "code". (or token, see 4.2.1)
|
||||
pub response_type: String,
|
||||
pub response_type: ResponseType,
|
||||
/// Response mode.
|
||||
///
|
||||
/// Optional; defaults to `query` for `response_type=code` (Auth Code), and
|
||||
/// `fragment` for `response_type=token` (Implicit Grant, which we probably
|
||||
/// won't support).
|
||||
///
|
||||
/// Reference:
|
||||
/// [OAuth 2.0 Multiple Response Type Encoding Practices: Response Modes](https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes)
|
||||
pub response_mode: Option<ResponseMode>,
|
||||
pub client_id: String,
|
||||
pub state: String,
|
||||
#[serde_as(as = "NoneAsEmptyString")]
|
||||
pub state: Option<String>,
|
||||
#[serde(flatten)]
|
||||
pub pkce_request: Option<PkceRequest>,
|
||||
pub redirect_uri: Url,
|
||||
|
@ -57,6 +69,39 @@ pub struct AuthorisationRequest {
|
|||
pub unknown_keys: BTreeMap<String, serde_json::value::Value>,
|
||||
}
|
||||
|
||||
impl AuthorisationRequest {
|
||||
/// Get the `response_mode` appropriate for this request, taking into
|
||||
/// account defaults from the `response_type` parameter.
|
||||
///
|
||||
/// Returns `None` if the selection is invalid.
|
||||
///
|
||||
/// Reference:
|
||||
/// [OAuth 2.0 Multiple Response Type Encoding Practices: Response Modes](https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes)
|
||||
pub const fn get_response_mode(&self) -> Option<ResponseMode> {
|
||||
match (self.response_mode, self.response_type) {
|
||||
// https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#id_token
|
||||
// The default Response Mode for this Response Type is the fragment
|
||||
// encoding and the query encoding MUST NOT be used.
|
||||
(None, ResponseType::IdToken) => Some(ResponseMode::Fragment),
|
||||
(Some(ResponseMode::Query), ResponseType::IdToken) => None,
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2
|
||||
(None, ResponseType::Code) => Some(ResponseMode::Query),
|
||||
// https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.2
|
||||
(None, ResponseType::Token) => Some(ResponseMode::Fragment),
|
||||
|
||||
// https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Security
|
||||
// In no case should a set of Authorization Response parameters
|
||||
// whose default Response Mode is the fragment encoding be encoded
|
||||
// using the query encoding.
|
||||
(Some(ResponseMode::Query), ResponseType::Token) => None,
|
||||
|
||||
// Allow others.
|
||||
(Some(m), _) => Some(m),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An OIDC client redirects to the authorisation server with Authorisation Request
|
||||
/// parameters.
|
||||
#[skip_serializing_none]
|
||||
|
@ -290,15 +335,20 @@ impl AccessTokenIntrospectResponse {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ResponseType {
|
||||
// Auth Code flow
|
||||
// https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1
|
||||
Code,
|
||||
// Implicit Grant flow
|
||||
// https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.1
|
||||
Token,
|
||||
// https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#id_token
|
||||
IdToken,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ResponseMode {
|
||||
Query,
|
||||
|
@ -393,6 +443,21 @@ fn require_request_uri_parameter_supported_default() -> bool {
|
|||
false
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct OidcWebfingerRel {
|
||||
pub rel: String,
|
||||
pub href: String,
|
||||
}
|
||||
|
||||
/// The response to an Webfinger request. Only a subset of the body is defined here.
|
||||
/// <https://datatracker.ietf.org/doc/html/rfc7033#section-4.4>
|
||||
#[skip_serializing_none]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct OidcWebfingerResponse {
|
||||
pub subject: String,
|
||||
pub links: Vec<OidcWebfingerRel>,
|
||||
}
|
||||
|
||||
/// The response to an OpenID connect discovery request
|
||||
/// <https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata>
|
||||
#[skip_serializing_none]
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
//! These are types that a client will send to the server.
|
||||
use super::ScimEntryGetQuery;
|
||||
use super::ScimOauth2ClaimMapJoinChar;
|
||||
use crate::attribute::Attribute;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -89,10 +90,17 @@ pub struct ScimEntryPutKanidm {
|
|||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
pub struct ScimStrings(#[serde_as(as = "OneOrMany<_, PreferMany>")] pub Vec<String>);
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[derive(Debug, Clone, Deserialize, Default)]
|
||||
pub struct ScimEntryPutGeneric {
|
||||
// id is only used to target the entry in question
|
||||
pub id: Uuid,
|
||||
|
||||
#[serde(flatten)]
|
||||
/// Non-standard extension - allow query options to be set in a put request. This
|
||||
/// is because a put request also returns the entry state post put, so we want
|
||||
/// to allow putters to adjust and control what is returned here.
|
||||
pub query: ScimEntryGetQuery,
|
||||
|
||||
// external_id can't be set by put
|
||||
// meta is skipped on put
|
||||
// Schemas are decoded as part of "attrs".
|
||||
|
@ -119,6 +127,10 @@ impl TryFrom<ScimEntryPutKanidm> for ScimEntryPutGeneric {
|
|||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
Ok(ScimEntryPutGeneric { id, attrs })
|
||||
Ok(ScimEntryPutGeneric {
|
||||
id,
|
||||
attrs,
|
||||
query: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ use crate::attribute::Attribute;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use sshkey_attest::proto::PublicKey as SshPublicKey;
|
||||
use std::collections::BTreeMap;
|
||||
use std::ops::Not;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use serde_with::formats::CommaSeparator;
|
||||
|
@ -47,10 +48,12 @@ pub struct ScimEntryGeneric {
|
|||
/// SCIM Query Parameters used during the get of a single entry
|
||||
#[serde_as]
|
||||
#[skip_serializing_none]
|
||||
#[derive(Serialize, Deserialize, Debug, Default)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||
pub struct ScimEntryGetQuery {
|
||||
#[serde_as(as = "Option<StringWithSeparator::<CommaSeparator, Attribute>>")]
|
||||
pub attributes: Option<Vec<Attribute>>,
|
||||
#[serde(default, skip_serializing_if = "<&bool>::not")]
|
||||
pub ext_access_check: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, ToSchema)]
|
||||
|
@ -120,12 +123,15 @@ mod tests {
|
|||
// Group
|
||||
let group_uuid = uuid::uuid!("2d0a9e7c-cc08-4ca2-8d7f-114f9abcfc8a");
|
||||
|
||||
let group = ScimSyncGroup::builder("testgroup".to_string(), group_uuid)
|
||||
.set_description(Some("test desc".to_string()))
|
||||
.set_gidnumber(Some(12345))
|
||||
.set_members(vec!["member_a".to_string(), "member_a".to_string()].into_iter())
|
||||
.set_external_id(Some("cn=testgroup".to_string()))
|
||||
.build();
|
||||
let group = ScimSyncGroup::builder(
|
||||
group_uuid,
|
||||
"cn=testgroup".to_string(),
|
||||
"testgroup".to_string(),
|
||||
)
|
||||
.set_description(Some("test desc".to_string()))
|
||||
.set_gidnumber(Some(12345))
|
||||
.set_members(vec!["member_a".to_string(), "member_a".to_string()].into_iter())
|
||||
.build();
|
||||
|
||||
let entry: Result<ScimEntry, _> = group.try_into();
|
||||
|
||||
|
@ -136,32 +142,35 @@ mod tests {
|
|||
|
||||
let user_sshkey = "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBENubZikrb8hu+HeVRdZ0pp/VAk2qv4JDbuJhvD0yNdWDL2e3cBbERiDeNPkWx58Q4rVnxkbV1fa8E2waRtT91wAAAAEc3NoOg== testuser@fidokey";
|
||||
|
||||
let person =
|
||||
ScimSyncPerson::builder(user_uuid, "testuser".to_string(), "Test User".to_string())
|
||||
.set_password_import(Some("new_password".to_string()))
|
||||
.set_unix_password_import(Some("new_password".to_string()))
|
||||
.set_totp_import(vec![ScimTotp {
|
||||
external_id: "Totp".to_string(),
|
||||
secret: "abcd".to_string(),
|
||||
algo: "SHA3".to_string(),
|
||||
step: 60,
|
||||
digits: 8,
|
||||
}])
|
||||
.set_mail(vec![MultiValueAttr {
|
||||
primary: Some(true),
|
||||
value: "testuser@example.com".to_string(),
|
||||
..Default::default()
|
||||
}])
|
||||
.set_ssh_publickey(vec![ScimSshPubKey {
|
||||
label: "Key McKeyface".to_string(),
|
||||
value: user_sshkey.to_string(),
|
||||
}])
|
||||
.set_login_shell(Some("/bin/false".to_string()))
|
||||
.set_account_valid_from(Some("2023-11-28T04:57:55Z".to_string()))
|
||||
.set_account_expire(Some("2023-11-28T04:57:55Z".to_string()))
|
||||
.set_gidnumber(Some(54321))
|
||||
.set_external_id(Some("cn=testuser".to_string()))
|
||||
.build();
|
||||
let person = ScimSyncPerson::builder(
|
||||
user_uuid,
|
||||
"cn=testuser".to_string(),
|
||||
"testuser".to_string(),
|
||||
"Test User".to_string(),
|
||||
)
|
||||
.set_password_import(Some("new_password".to_string()))
|
||||
.set_unix_password_import(Some("new_password".to_string()))
|
||||
.set_totp_import(vec![ScimTotp {
|
||||
external_id: "Totp".to_string(),
|
||||
secret: "abcd".to_string(),
|
||||
algo: "SHA3".to_string(),
|
||||
step: 60,
|
||||
digits: 8,
|
||||
}])
|
||||
.set_mail(vec![MultiValueAttr {
|
||||
primary: Some(true),
|
||||
value: "testuser@example.com".to_string(),
|
||||
..Default::default()
|
||||
}])
|
||||
.set_ssh_publickey(vec![ScimSshPubKey {
|
||||
label: "Key McKeyface".to_string(),
|
||||
value: user_sshkey.to_string(),
|
||||
}])
|
||||
.set_login_shell(Some("/bin/false".to_string()))
|
||||
.set_account_valid_from(Some("2023-11-28T04:57:55Z".to_string()))
|
||||
.set_account_expire(Some("2023-11-28T04:57:55Z".to_string()))
|
||||
.set_gidnumber(Some(54321))
|
||||
.build();
|
||||
|
||||
let entry: Result<ScimEntry, _> = person.try_into();
|
||||
|
||||
|
@ -172,7 +181,10 @@ mod tests {
|
|||
fn scim_entry_get_query() {
|
||||
use super::*;
|
||||
|
||||
let q = ScimEntryGetQuery { attributes: None };
|
||||
let q = ScimEntryGetQuery {
|
||||
attributes: None,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let txt = serde_urlencoded::to_string(&q).unwrap();
|
||||
|
||||
|
@ -180,6 +192,7 @@ mod tests {
|
|||
|
||||
let q = ScimEntryGetQuery {
|
||||
attributes: Some(vec![Attribute::Name]),
|
||||
ext_access_check: false,
|
||||
};
|
||||
|
||||
let txt = serde_urlencoded::to_string(&q).unwrap();
|
||||
|
@ -187,9 +200,10 @@ mod tests {
|
|||
|
||||
let q = ScimEntryGetQuery {
|
||||
attributes: Some(vec![Attribute::Name, Attribute::Spn]),
|
||||
ext_access_check: true,
|
||||
};
|
||||
|
||||
let txt = serde_urlencoded::to_string(&q).unwrap();
|
||||
assert_eq!(txt, "attributes=name%2Cspn");
|
||||
assert_eq!(txt, "attributes=name%2Cspn&ext_access_check=true");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,14 +16,54 @@ use uuid::Uuid;
|
|||
/// A strongly typed ScimEntry that is for transmission to clients. This uses
|
||||
/// Kanidm internal strong types for values allowing direct serialisation and
|
||||
/// transmission.
|
||||
#[serde_as]
|
||||
#[skip_serializing_none]
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
pub struct ScimEntryKanidm {
|
||||
#[serde(flatten)]
|
||||
pub header: ScimEntryHeader,
|
||||
|
||||
pub ext_access_check: Option<ScimEffectiveAccess>,
|
||||
#[serde(flatten)]
|
||||
pub attrs: BTreeMap<Attribute, ScimValueKanidm>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
pub enum ScimAttributeEffectiveAccess {
|
||||
/// All attributes on the entry have this permission granted
|
||||
Grant,
|
||||
/// All attributes on the entry have this permission denied
|
||||
Denied,
|
||||
/// The following attributes on the entry have this permission granted
|
||||
Allow(BTreeSet<Attribute>),
|
||||
}
|
||||
|
||||
impl ScimAttributeEffectiveAccess {
|
||||
/// Check if the effective access allows or denies this attribute
|
||||
pub fn check(&self, attr: &Attribute) -> bool {
|
||||
match self {
|
||||
Self::Grant => true,
|
||||
Self::Denied => false,
|
||||
Self::Allow(set) => set.contains(attr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimEffectiveAccess {
|
||||
/// The identity that inherits the effective permission
|
||||
pub ident: Uuid,
|
||||
/// If the ident may delete the target entry
|
||||
pub delete: bool,
|
||||
/// The set of effective access over search events
|
||||
pub search: ScimAttributeEffectiveAccess,
|
||||
/// The set of effective access over modify present events
|
||||
pub modify_present: ScimAttributeEffectiveAccess,
|
||||
/// The set of effective access over modify remove events
|
||||
pub modify_remove: ScimAttributeEffectiveAccess,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimAddress {
|
||||
|
|
|
@ -119,7 +119,12 @@ pub struct ScimSyncPersonBuilder {
|
|||
}
|
||||
|
||||
impl ScimSyncPerson {
|
||||
pub fn builder(id: Uuid, name: String, displayname: String) -> ScimSyncPersonBuilder {
|
||||
pub fn builder(
|
||||
id: Uuid,
|
||||
external_id: String,
|
||||
name: String,
|
||||
displayname: String,
|
||||
) -> ScimSyncPersonBuilder {
|
||||
ScimSyncPersonBuilder {
|
||||
inner: ScimSyncPerson {
|
||||
entry: ScimEntryHeader {
|
||||
|
@ -128,7 +133,7 @@ impl ScimSyncPerson {
|
|||
SCIM_SCHEMA_SYNC_PERSON.to_string(),
|
||||
],
|
||||
id,
|
||||
external_id: None,
|
||||
external_id: Some(external_id),
|
||||
meta: None,
|
||||
},
|
||||
name,
|
||||
|
@ -205,11 +210,6 @@ impl ScimSyncPersonBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn set_external_id(mut self, external_id: Option<String>) -> Self {
|
||||
self.inner.entry.external_id = external_id;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> ScimSyncPerson {
|
||||
self.inner
|
||||
}
|
||||
|
@ -220,6 +220,7 @@ pub struct ScimExternalMember {
|
|||
pub external_id: String,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct ScimSyncGroup {
|
||||
|
@ -229,7 +230,8 @@ pub struct ScimSyncGroup {
|
|||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub gidnumber: Option<u32>,
|
||||
pub members: Vec<ScimExternalMember>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub member: Vec<ScimExternalMember>,
|
||||
}
|
||||
|
||||
impl TryInto<ScimEntry> for ScimSyncGroup {
|
||||
|
@ -247,19 +249,19 @@ pub struct ScimSyncGroupBuilder {
|
|||
}
|
||||
|
||||
impl ScimSyncGroup {
|
||||
pub fn builder(name: String, id: Uuid) -> ScimSyncGroupBuilder {
|
||||
pub fn builder(id: Uuid, external_id: String, name: String) -> ScimSyncGroupBuilder {
|
||||
ScimSyncGroupBuilder {
|
||||
inner: ScimSyncGroup {
|
||||
entry: ScimEntryHeader {
|
||||
schemas: vec![SCIM_SCHEMA_SYNC_GROUP.to_string()],
|
||||
id,
|
||||
external_id: None,
|
||||
external_id: Some(external_id),
|
||||
meta: None,
|
||||
},
|
||||
name,
|
||||
description: None,
|
||||
gidnumber: None,
|
||||
members: Vec::with_capacity(0),
|
||||
member: Vec::with_capacity(0),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -288,17 +290,12 @@ impl ScimSyncGroupBuilder {
|
|||
where
|
||||
I: Iterator<Item = String>,
|
||||
{
|
||||
self.inner.members = member_iter
|
||||
self.inner.member = member_iter
|
||||
.map(|external_id| ScimExternalMember { external_id })
|
||||
.collect();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_external_id(mut self, external_id: Option<String>) -> Self {
|
||||
self.inner.entry.external_id = external_id;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> ScimSyncGroup {
|
||||
self.inner
|
||||
}
|
||||
|
|
|
@ -17,7 +17,13 @@ import yarl
|
|||
|
||||
from kanidm.models.group import Group, GroupList, IGroup, RawGroup
|
||||
from kanidm.models.oauth2_rs import IOauth2Rs, OAuth2Rs, Oauth2RsList, RawOAuth2Rs
|
||||
from kanidm.models.person import IPerson, Person, PersonList, RawPerson
|
||||
from kanidm.models.person import (
|
||||
IPerson,
|
||||
Person,
|
||||
PersonList,
|
||||
RawPerson,
|
||||
PersonCredentialResetToken,
|
||||
)
|
||||
from kanidm.models.service_account import (
|
||||
IServiceAccount,
|
||||
ServiceAccount,
|
||||
|
@ -93,7 +99,7 @@ class KanidmClient:
|
|||
"""Constructor for KanidmClient"""
|
||||
|
||||
self.logger = logger or getLogger(__name__)
|
||||
self.instance_name = instance_name # TODO: use this in loaders etc
|
||||
self.instance_name = instance_name # TODO: use this in loaders etc
|
||||
if config is not None:
|
||||
self.config = config
|
||||
else:
|
||||
|
@ -123,7 +129,7 @@ class KanidmClient:
|
|||
|
||||
def _configure_ssl(self) -> None:
|
||||
"""Sets up SSL configuration for the client"""
|
||||
if False in [self.config.verify_certificate, self.config.verify_hostnames ]:
|
||||
if False in [self.config.verify_certificate, self.config.verify_hostnames]:
|
||||
logging.debug("Setting up SSL context with no verification")
|
||||
self._ssl_context = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH)
|
||||
self._ssl_context.hostname_checks_common_name = False
|
||||
|
@ -135,9 +141,8 @@ class KanidmClient:
|
|||
raise FileNotFoundError(f"CA Path not found: {self.config.ca_path}")
|
||||
else:
|
||||
self.logger.debug("Setting up SSL context with CA path=%s", self.config.ca_path)
|
||||
self._ssl_context = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH,cafile=self.config.ca_path)
|
||||
self._ssl_context = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH, cafile=self.config.ca_path)
|
||||
else:
|
||||
|
||||
logging.debug("Setting up default SSL context")
|
||||
self._ssl_context = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH)
|
||||
|
||||
|
@ -521,9 +526,7 @@ class KanidmClient:
|
|||
"""get an OAuth2 client"""
|
||||
endpoint = f"{Endpoints.OAUTH2}/{rs_name}"
|
||||
response: ClientResponse[IOauth2Rs] = await self.call_get(endpoint)
|
||||
if response.status_code != 200:
|
||||
raise ValueError(f"Failed to get oauth2 resource server: {response.content}")
|
||||
if response.data is None:
|
||||
if response.status_code != 200 or response.data is None:
|
||||
raise ValueError(f"Failed to get oauth2 resource server: {response.content}")
|
||||
return RawOAuth2Rs(**response.data).as_oauth2_rs
|
||||
|
||||
|
@ -583,9 +586,7 @@ class KanidmClient:
|
|||
"""Get a service account"""
|
||||
endpoint = f"{Endpoints.SERVICE_ACCOUNT}/{name}"
|
||||
response: ClientResponse[IServiceAccount] = await self.call_get(endpoint)
|
||||
if response.status_code != 200:
|
||||
raise ValueError(f"Failed to get service account: {response.content}")
|
||||
if response.data is None:
|
||||
if response.status_code != 200 or response.data is None:
|
||||
raise ValueError(f"Failed to get service account: {response.content}")
|
||||
return RawServiceAccount(**response.data).as_service_account
|
||||
|
||||
|
@ -672,9 +673,7 @@ class KanidmClient:
|
|||
"""Get a group"""
|
||||
endpoint = f"{Endpoints.GROUP}/{name}"
|
||||
response: ClientResponse[IGroup] = await self.call_get(endpoint)
|
||||
if response.status_code != 200:
|
||||
raise ValueError(f"Failed to get group: {response.content}")
|
||||
if response.data is None:
|
||||
if response.status_code != 200 or response.data is None:
|
||||
raise ValueError(f"Failed to get group: {response.content}")
|
||||
return RawGroup(**response.data).as_group
|
||||
|
||||
|
@ -719,9 +718,7 @@ class KanidmClient:
|
|||
"""Get a person by name"""
|
||||
endpoint = f"{Endpoints.PERSON}/{name}"
|
||||
response: ClientResponse[IPerson] = await self.call_get(endpoint)
|
||||
if response.status_code != 200:
|
||||
raise ValueError(f"Failed to get person: {response.content}")
|
||||
if response.data is None:
|
||||
if response.status_code != 200 or response.data is None:
|
||||
raise ValueError(f"Failed to get person: {response.content}")
|
||||
return RawPerson(**response.data).as_person
|
||||
|
||||
|
@ -765,6 +762,19 @@ class KanidmClient:
|
|||
endpoint = f"{Endpoints.PERSON}/{id}"
|
||||
return await self.call_delete(endpoint)
|
||||
|
||||
async def person_account_credential_update_token(self, id: str, ttl: Optional[int] = None) -> PersonCredentialResetToken:
|
||||
"""Create a password reset token for person with an optional time to live in seconds"""
|
||||
endpoint = f"{Endpoints.PERSON}/{id}/_credential/_update_intent"
|
||||
if ttl:
|
||||
endpoint = f"{endpoint}/{ttl}"
|
||||
|
||||
response: ClientResponse[Any] = await self.call_get(endpoint)
|
||||
if response.status_code != 200 or response.content is None:
|
||||
raise ValueError(f"Failed to get token: {response.content}")
|
||||
token = PersonCredentialResetToken.model_validate(json_lib.loads(response.content))
|
||||
|
||||
return token
|
||||
|
||||
async def person_account_post_ssh_key(self, id: str, tag: str, pubkey: str) -> ClientResponse[None]:
|
||||
"""Create an SSH key for a user"""
|
||||
endpoint = f"{Endpoints.PERSON}/{id}/_ssh_pubkeys"
|
||||
|
|
|
@ -38,8 +38,15 @@ class RawPerson(BaseModel):
|
|||
uuid=UUID(self.attrs["uuid"][0]),
|
||||
)
|
||||
|
||||
|
||||
PersonList = RootModel[List[RawPerson]]
|
||||
|
||||
|
||||
class IPerson(TypedDict):
|
||||
attrs: Dict[str, List[str]]
|
||||
|
||||
|
||||
class PersonCredentialResetToken(BaseModel):
|
||||
token: str
|
||||
expiry_time: int
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
""" kanidm RADIUS module """
|
||||
"""kanidm RADIUS module"""
|
||||
|
||||
import asyncio
|
||||
from aiohttp.client_exceptions import ClientConnectorError
|
||||
from functools import reduce
|
||||
|
@ -16,15 +17,26 @@ from .. import KanidmClient
|
|||
from . import radiusd
|
||||
from .utils import check_vlan
|
||||
|
||||
CONTAINER_CONFIG_FILE_PATH = "/data/radius.toml"
|
||||
|
||||
# the list of places to try
|
||||
CONFIG_PATHS = [
|
||||
os.getenv("KANIDM_RLM_CONFIG", "/data/kanidm"), # container goodness
|
||||
"~/.config/kanidm", # for a user
|
||||
"/etc/kanidm/kanidm", # system-wide
|
||||
"../examples/kanidm", # test mode
|
||||
os.getenv("KANIDM_RLM_CONFIG", CONTAINER_CONFIG_FILE_PATH), # container goodness
|
||||
"~/.config/radius.toml", # for a user
|
||||
"/etc/kanidm/radius.toml", # system-wide
|
||||
"../examples/radius.toml", # test mode
|
||||
"/data/kanidm", # fallback to old path
|
||||
]
|
||||
|
||||
|
||||
def find_radius_config_path() -> Optional[Path]:
|
||||
for config_file_path in CONFIG_PATHS:
|
||||
config_path = Path(config_file_path).expanduser().resolve()
|
||||
if config_path.exists():
|
||||
return config_path
|
||||
return None
|
||||
|
||||
|
||||
def instantiate(_: Any) -> Any:
|
||||
"""start up radiusd"""
|
||||
logging.basicConfig(
|
||||
|
@ -33,16 +45,9 @@ def instantiate(_: Any) -> Any:
|
|||
)
|
||||
logging.info("Starting up!")
|
||||
|
||||
config_path = None
|
||||
for config_file_path in CONFIG_PATHS:
|
||||
config_path = Path(config_file_path).expanduser().resolve()
|
||||
if config_path.exists():
|
||||
break
|
||||
|
||||
if (config_path is None) or (not config_path.exists()):
|
||||
logging.error(
|
||||
"Failed to find configuration file, checked (%s), quitting!", CONFIG_PATHS
|
||||
)
|
||||
config_path = find_radius_config_path()
|
||||
if config_path is None:
|
||||
logging.error("Failed to find configuration file, checked (%s), quitting!", CONFIG_PATHS)
|
||||
sys.exit(1)
|
||||
|
||||
kanidm_client = KanidmClient(config_file=config_path)
|
||||
|
@ -107,9 +112,7 @@ def authorize(
|
|||
tok = None
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
tok = RadiusTokenResponse.model_validate(
|
||||
loop.run_until_complete(_get_radius_token(username=user_id))
|
||||
)
|
||||
tok = RadiusTokenResponse.model_validate(loop.run_until_complete(_get_radius_token(username=user_id)))
|
||||
logging.debug("radius information token: %s", tok)
|
||||
except NoMatchingEntries as error_message:
|
||||
logging.info(
|
||||
|
@ -125,9 +128,7 @@ def authorize(
|
|||
logging.error("kanidm exception: %s, %s", type(error_message), error_message)
|
||||
return radiusd.RLM_MODULE_FAIL
|
||||
if tok is None:
|
||||
logging.info(
|
||||
"kanidm RLM_MODULE_REJECT - unable to retrieve radius information token"
|
||||
)
|
||||
logging.info("kanidm RLM_MODULE_REJECT - unable to retrieve radius information token")
|
||||
return radiusd.RLM_MODULE_REJECT
|
||||
|
||||
# Get values out of the token
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
""" type objects """
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
# ^ disabling this because pydantic models don't have public methods
|
||||
|
||||
|
@ -31,7 +32,7 @@ class ClientResponse(BaseModel, Generic[T]):
|
|||
|
||||
|
||||
class AuthInitResponse(BaseModel):
|
||||
"""Aelps parse the response from the Auth 'init' stage"""
|
||||
"""Helps parse the response from the Auth 'init' stage"""
|
||||
|
||||
class _AuthInitState(BaseModel):
|
||||
"""sub-class for the AuthInitResponse model"""
|
||||
|
@ -146,9 +147,7 @@ class RadiusClient(BaseModel):
|
|||
socket.gethostbyname(value)
|
||||
return value
|
||||
except socket.gaierror as error:
|
||||
raise ValueError(
|
||||
f"ipaddr value ({value}) wasn't an IP Address, Network or valid hostname: {error}"
|
||||
)
|
||||
raise ValueError(f"ipaddr value ({value}) wasn't an IP Address, Network or valid hostname: {error}")
|
||||
|
||||
|
||||
class KanidmClientConfig(BaseModel):
|
||||
|
@ -172,7 +171,6 @@ class KanidmClientConfig(BaseModel):
|
|||
|
||||
radius_cert_path: str = "/data/cert.pem"
|
||||
radius_key_path: str = "/data/key.pem" # the signing key for radius TLS
|
||||
radius_dh_path: str = "/data/dh.pem" # the diffie-hellman output
|
||||
radius_ca_path: Optional[str] = None
|
||||
radius_ca_dir: Optional[str] = None
|
||||
|
||||
|
@ -196,9 +194,7 @@ class KanidmClientConfig(BaseModel):
|
|||
uri = urlparse(value)
|
||||
valid_schemes = ["http", "https"]
|
||||
if uri.scheme not in valid_schemes:
|
||||
raise ValueError(
|
||||
f"Invalid URL Scheme for uri='{value}': '{uri.scheme}' - expected one of {valid_schemes}"
|
||||
)
|
||||
raise ValueError(f"Invalid URL Scheme for uri='{value}': '{uri.scheme}' - expected one of {valid_schemes}")
|
||||
|
||||
# make sure the URI ends with a /
|
||||
if not value.endswith("/"):
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
""" utility functions """
|
||||
"""utility functions"""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Union
|
||||
|
|
761
pykanidm/poetry.lock
generated
761
pykanidm/poetry.lock
generated
|
@ -1,4 +1,4 @@
|
|||
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "aiohappyeyeballs"
|
||||
|
@ -13,87 +13,92 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "aiohttp"
|
||||
version = "3.11.8"
|
||||
version = "3.11.12"
|
||||
description = "Async http client/server framework (asyncio)"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "aiohttp-3.11.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2d2ca685c6a851ce64e511fbcb906e4dd97d13e567ca7ecb5cb30b184e15dc6d"},
|
||||
{file = "aiohttp-3.11.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52913bb8a0a72a57479f54b281300c9d23036aa9aa3ebbc9a32a643484eadfc2"},
|
||||
{file = "aiohttp-3.11.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:35dafc70051b6cbd6dafb533b4e3f0df6225a4896be373ef86367b2987409331"},
|
||||
{file = "aiohttp-3.11.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:561b9596a9f90266673ef0b950c27e04ab597cdb53785e2ac91b83b33c31b509"},
|
||||
{file = "aiohttp-3.11.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d479c1fdcc920056a06d04059db52eb8590ecbbb3acdcaeeea26a88ff782e94a"},
|
||||
{file = "aiohttp-3.11.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9ce8eb6444bb6e862feca664ce365afa8e2e32db24dcf1a502719a8a002f9274"},
|
||||
{file = "aiohttp-3.11.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df9bf08eb93611b1d4d6245b6fecf88728e90eece00e00d554e1b0c445557d83"},
|
||||
{file = "aiohttp-3.11.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a20ddaa58fea717177fac9a4a1fb8b39be868aa4fed2af6de4313b7a08f0f71"},
|
||||
{file = "aiohttp-3.11.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9f4aadfea6b48cfa17aef1a68ba6bee5a0246374f5a588e299a4f4ff5bd1c77b"},
|
||||
{file = "aiohttp-3.11.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:aa7deebb4bc5143745e6282139d7b9de50beb6d06609df64d2c993ef496bc7eb"},
|
||||
{file = "aiohttp-3.11.8-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fe503a76b9e3a13b62e64545693c9463afe9d429e0909120f7bb66de91ed8bc2"},
|
||||
{file = "aiohttp-3.11.8-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1c5838a68e31712354129add1b5fe32b06aa05275f835130edc650e6288af05f"},
|
||||
{file = "aiohttp-3.11.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:832e58d9454fe501b0d092cdf660c0e34e16005f61acd06e1c79b0fc45019c94"},
|
||||
{file = "aiohttp-3.11.8-cp310-cp310-win32.whl", hash = "sha256:00618c37a350884c08e87cf9a6532be274d564227ac49e0b474cf41f27e1f190"},
|
||||
{file = "aiohttp-3.11.8-cp310-cp310-win_amd64.whl", hash = "sha256:8eeaac75203da1a54afe1faea3c855a1973026b54929112aa9b67bceadbcb0ca"},
|
||||
{file = "aiohttp-3.11.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f8dd02b44555893adfe7cc4b3b454fee04f9dcec45cf66ef5bb53ebf393f0505"},
|
||||
{file = "aiohttp-3.11.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:658052941324edea3dee1f681375e70779f55e437e07bdfc4b5bbe65ad53cefb"},
|
||||
{file = "aiohttp-3.11.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6c829471a9e2266da4a0666f8a9e215f19320f79778af379c1c7db324ac24ed2"},
|
||||
{file = "aiohttp-3.11.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d21951756690f5d86d0215da38eb0fd65def03b5e2a1c08a4a39718a6d0d48f2"},
|
||||
{file = "aiohttp-3.11.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2fa50ddc6b21cc1ae23e13524d6f75b27e279fdf5cf905b2df6fd171891ac4e2"},
|
||||
{file = "aiohttp-3.11.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a5afbd805e449048ecebb1a256176e953d4ca9e48bab387d4d1c8524f1c7a95"},
|
||||
{file = "aiohttp-3.11.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea68db69f2a4ddc24b28b8e754fc0b963ed7f9b9a76137f06fe44643d6821fbd"},
|
||||
{file = "aiohttp-3.11.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80b3ac163145660ce660aed2f1005e6d4de840d39728990b7250525eeec4e4a8"},
|
||||
{file = "aiohttp-3.11.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e9ac0cce897904b77e109e5403ed713187dbdf96832bfd061ac07164264be16c"},
|
||||
{file = "aiohttp-3.11.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3260c77cff4e35245bc517658bd54d7a64787f71f3c4f723877c82f22835b032"},
|
||||
{file = "aiohttp-3.11.8-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f7fd9c11ffad6b022bf02a41a70418cb2ab3b33f2c27842a5999e3ab78daf280"},
|
||||
{file = "aiohttp-3.11.8-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:16bda233a7b159ab08107e8858fedca90a9de287057fab54cafde51bd83f9819"},
|
||||
{file = "aiohttp-3.11.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4867008617bbf86e9fb5b00f72dd0e3a00a579b32233caff834320867f9b7cac"},
|
||||
{file = "aiohttp-3.11.8-cp311-cp311-win32.whl", hash = "sha256:17e6b9d8e29e3bfc7f893f327e92c9769d3582cee2fb1652c1431ac3f60115a0"},
|
||||
{file = "aiohttp-3.11.8-cp311-cp311-win_amd64.whl", hash = "sha256:7f3be4961a5c2c670f31caab7641a37ea2a97031f0d8ae15bcfd36b6bf273200"},
|
||||
{file = "aiohttp-3.11.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0e3b5bfef913d6be270c81976fbc0cbf66625cd92663bbb7e03b3adbd6aa4ac6"},
|
||||
{file = "aiohttp-3.11.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cb51a81cb637b9a072c9cfae1839e35c6579638861eb3479eb5d6e6ce8bc6782"},
|
||||
{file = "aiohttp-3.11.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dd2ca84e5f7a35f313a62eb7d6a50bac6760b60bafce34586750712731c0aeff"},
|
||||
{file = "aiohttp-3.11.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47c6663df9446aa848b478413219600da4b54bc0409e1ac4bc80fb1a81501363"},
|
||||
{file = "aiohttp-3.11.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c665ed4b52256614858b20711bbbd2755b0e19ec86870f8ff1645acf9ae9e760"},
|
||||
{file = "aiohttp-3.11.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35d4545e7684da7a954ffc2dce495462cb16a902dffdebe98572408f6aaaee83"},
|
||||
{file = "aiohttp-3.11.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85be3899e6860dd2cd3f4370ded6708e939d00d5ec922a8eb328d114db605a47"},
|
||||
{file = "aiohttp-3.11.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0ed9f1f2697713c48efc9ec483ad5d062e4aa91854f090a3eba0b19c002851d"},
|
||||
{file = "aiohttp-3.11.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c0dbae99737badf3f5e862088a118e28d3b36f03eb608a6382eddfd68178e05b"},
|
||||
{file = "aiohttp-3.11.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:beae08f900b2980af4353a0200eb162b39f276fd8a6e43079a540f83964671f4"},
|
||||
{file = "aiohttp-3.11.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d6f9e5fd1b3ecbaca3e04a15a02d1fa213248608caee99fd5bdddd4759959cf7"},
|
||||
{file = "aiohttp-3.11.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a7def89a41fe32120d89cd4577f5efbab3c52234c5890066ced8a2f7202dff88"},
|
||||
{file = "aiohttp-3.11.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:98f596cf59292e779bc387f22378a3d2c5e052c9fe2bf822ac4f547c6fe57758"},
|
||||
{file = "aiohttp-3.11.8-cp312-cp312-win32.whl", hash = "sha256:b64fa6b76b35b695cd3e5c42a4e568cbea8d41c9e59165e2a43da00976e2027e"},
|
||||
{file = "aiohttp-3.11.8-cp312-cp312-win_amd64.whl", hash = "sha256:afba47981ff73b1794c00dce774334dcfe62664b3b4f78f278b77d21ce9daf43"},
|
||||
{file = "aiohttp-3.11.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a81525430da5ca356fae6e889daeb6f5cc0d5f0cef88e59cdde48e2394ea1365"},
|
||||
{file = "aiohttp-3.11.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7565689e86a88c1d258351ebd14e343337b76a56ca5c0a2c1db96ec28149386f"},
|
||||
{file = "aiohttp-3.11.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d0f9dbe9763c014c408ad51a027dc9582518e992dc63e2ffe359ac1b4840a560"},
|
||||
{file = "aiohttp-3.11.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca580edc3ccd7f6ea76ad9cf59f5a8756d338e770b5eda7be26bcda8fa7ef53"},
|
||||
{file = "aiohttp-3.11.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7d141631a7348038fc7b5d1a81b3c9afa9aa056188ded7902fe754028fdea5c5"},
|
||||
{file = "aiohttp-3.11.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64e6b14608a56a4c76c60daac730b0c0eeaf9d10dfc3231f7fc26521a0d628fd"},
|
||||
{file = "aiohttp-3.11.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0983d0ce329f2f9dbeb355c3744bd6333f34e0dc56025b6b7d4f285b90acb51e"},
|
||||
{file = "aiohttp-3.11.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d96b93a46a3742880fa21bcb35c6c40cf27714ec0fb8ec85fe444d73b95131b9"},
|
||||
{file = "aiohttp-3.11.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f4f1779c3142d913c509c2ed1de8b8f920e07a5cd65ac1f57c61cfb6bfded5a4"},
|
||||
{file = "aiohttp-3.11.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:48be7cff468c9c0d86a02e6a826e1fe159094b16d5aa2c17703e7317f791b0f9"},
|
||||
{file = "aiohttp-3.11.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:daea456b79ca2bacc7f062845bbb1139c3b3231fc83169da5a682cf385416dd1"},
|
||||
{file = "aiohttp-3.11.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:c92e763cf641e10ad9342597d20060ba23de5e411aada96660e679e3f9371189"},
|
||||
{file = "aiohttp-3.11.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a750ee5a177e0f873d6b2d7d0fa6e1e7c658fc0ca8ea56438dcba2ac94bedb09"},
|
||||
{file = "aiohttp-3.11.8-cp313-cp313-win32.whl", hash = "sha256:4448c9c7f77bad48a6569062c0c16deb77fbb7363de1dc71ed087f66fb3b3c96"},
|
||||
{file = "aiohttp-3.11.8-cp313-cp313-win_amd64.whl", hash = "sha256:481075a1949de79a8a6841e0086f2f5f464785c592cf527ed0db2c0cbd0e1ba2"},
|
||||
{file = "aiohttp-3.11.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:72779bfb34d6d6b51e55a7f4901b410e416b5431738b367d49696928c91a2ca8"},
|
||||
{file = "aiohttp-3.11.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e6523f39071a01757048985e4cc22d04aa130bc40d9128503f3a61a3ee98328"},
|
||||
{file = "aiohttp-3.11.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:220bbce18b3046973465be45415430f1cab39d7fdc40cbcf0a8c05485c6902fe"},
|
||||
{file = "aiohttp-3.11.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:336bbf7a33dd8cb4a7afb98c70e9935a81e5e88f7ac595ba2e84b1fb5da190d6"},
|
||||
{file = "aiohttp-3.11.8-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c5e4f1ba5059b85e05c551961a448ce2689c6249ed6a2e2174796842c191d10"},
|
||||
{file = "aiohttp-3.11.8-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9f9fd5c672c962389429abd11ed32c9c93f7932fd58584cae1e43951b141c6b"},
|
||||
{file = "aiohttp-3.11.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58bd94ad48143e1d42e05fc055da41de0a9933f378ad87760595b8aec83d317b"},
|
||||
{file = "aiohttp-3.11.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bf52642b12d70d78c18882915201bc5345f7c8f0f2ab8919d99b886aa6475a7"},
|
||||
{file = "aiohttp-3.11.8-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fee12d8487b0df2b683424cca2a0d8fb7281d5607518d742e98119a74af01026"},
|
||||
{file = "aiohttp-3.11.8-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:65fd04f1fea668ad1af48ac31b752000e222dccffedcad3de8ccf9d34489ccd3"},
|
||||
{file = "aiohttp-3.11.8-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c3f397e0511a0ec4fe331e602fc057dfd336d352062deb9969ebd81e253a149c"},
|
||||
{file = "aiohttp-3.11.8-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:cf8f05f4abe3288fe2e106e1461fd20d8abf6103886ddfb6d746a5b8fb830d2b"},
|
||||
{file = "aiohttp-3.11.8-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7d71d4ac0792ff89541179394d303be846a0b6cd3821ae67286ee69ecec16f9f"},
|
||||
{file = "aiohttp-3.11.8-cp39-cp39-win32.whl", hash = "sha256:2b6f8716044ae5e5f2a3b4e4b6bfee48e97c8b2a92e56f43aadd728c7fd26b7d"},
|
||||
{file = "aiohttp-3.11.8-cp39-cp39-win_amd64.whl", hash = "sha256:da343903214bf9f9d314b913caa499fa19e26d73e6e23a3db7d4898ea6d47028"},
|
||||
{file = "aiohttp-3.11.8.tar.gz", hash = "sha256:7bc9d64a2350cbb29a9732334e1a0743cbb6844de1731cbdf5949b235653f3fd"},
|
||||
{file = "aiohttp-3.11.12-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:aa8a8caca81c0a3e765f19c6953416c58e2f4cc1b84829af01dd1c771bb2f91f"},
|
||||
{file = "aiohttp-3.11.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:84ede78acde96ca57f6cf8ccb8a13fbaf569f6011b9a52f870c662d4dc8cd854"},
|
||||
{file = "aiohttp-3.11.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:584096938a001378484aa4ee54e05dc79c7b9dd933e271c744a97b3b6f644957"},
|
||||
{file = "aiohttp-3.11.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:392432a2dde22b86f70dd4a0e9671a349446c93965f261dbaecfaf28813e5c42"},
|
||||
{file = "aiohttp-3.11.12-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:88d385b8e7f3a870146bf5ea31786ef7463e99eb59e31db56e2315535d811f55"},
|
||||
{file = "aiohttp-3.11.12-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b10a47e5390c4b30a0d58ee12581003be52eedd506862ab7f97da7a66805befb"},
|
||||
{file = "aiohttp-3.11.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b5263dcede17b6b0c41ef0c3ccce847d82a7da98709e75cf7efde3e9e3b5cae"},
|
||||
{file = "aiohttp-3.11.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50c5c7b8aa5443304c55c262c5693b108c35a3b61ef961f1e782dd52a2f559c7"},
|
||||
{file = "aiohttp-3.11.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d1c031a7572f62f66f1257db37ddab4cb98bfaf9b9434a3b4840bf3560f5e788"},
|
||||
{file = "aiohttp-3.11.12-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:7e44eba534381dd2687be50cbd5f2daded21575242ecfdaf86bbeecbc38dae8e"},
|
||||
{file = "aiohttp-3.11.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:145a73850926018ec1681e734cedcf2716d6a8697d90da11284043b745c286d5"},
|
||||
{file = "aiohttp-3.11.12-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:2c311e2f63e42c1bf86361d11e2c4a59f25d9e7aabdbdf53dc38b885c5435cdb"},
|
||||
{file = "aiohttp-3.11.12-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ea756b5a7bac046d202a9a3889b9a92219f885481d78cd318db85b15cc0b7bcf"},
|
||||
{file = "aiohttp-3.11.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:526c900397f3bbc2db9cb360ce9c35134c908961cdd0ac25b1ae6ffcaa2507ff"},
|
||||
{file = "aiohttp-3.11.12-cp310-cp310-win32.whl", hash = "sha256:b8d3bb96c147b39c02d3db086899679f31958c5d81c494ef0fc9ef5bb1359b3d"},
|
||||
{file = "aiohttp-3.11.12-cp310-cp310-win_amd64.whl", hash = "sha256:7fe3d65279bfbee8de0fb4f8c17fc4e893eed2dba21b2f680e930cc2b09075c5"},
|
||||
{file = "aiohttp-3.11.12-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:87a2e00bf17da098d90d4145375f1d985a81605267e7f9377ff94e55c5d769eb"},
|
||||
{file = "aiohttp-3.11.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b34508f1cd928ce915ed09682d11307ba4b37d0708d1f28e5774c07a7674cac9"},
|
||||
{file = "aiohttp-3.11.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:936d8a4f0f7081327014742cd51d320296b56aa6d324461a13724ab05f4b2933"},
|
||||
{file = "aiohttp-3.11.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de1378f72def7dfb5dbd73d86c19eda0ea7b0a6873910cc37d57e80f10d64e1"},
|
||||
{file = "aiohttp-3.11.12-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9d45dbb3aaec05cf01525ee1a7ac72de46a8c425cb75c003acd29f76b1ffe94"},
|
||||
{file = "aiohttp-3.11.12-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:930ffa1925393381e1e0a9b82137fa7b34c92a019b521cf9f41263976666a0d6"},
|
||||
{file = "aiohttp-3.11.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8340def6737118f5429a5df4e88f440746b791f8f1c4ce4ad8a595f42c980bd5"},
|
||||
{file = "aiohttp-3.11.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4016e383f91f2814e48ed61e6bda7d24c4d7f2402c75dd28f7e1027ae44ea204"},
|
||||
{file = "aiohttp-3.11.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3c0600bcc1adfaaac321422d615939ef300df81e165f6522ad096b73439c0f58"},
|
||||
{file = "aiohttp-3.11.12-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:0450ada317a65383b7cce9576096150fdb97396dcfe559109b403c7242faffef"},
|
||||
{file = "aiohttp-3.11.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:850ff6155371fd802a280f8d369d4e15d69434651b844bde566ce97ee2277420"},
|
||||
{file = "aiohttp-3.11.12-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8fd12d0f989c6099e7b0f30dc6e0d1e05499f3337461f0b2b0dadea6c64b89df"},
|
||||
{file = "aiohttp-3.11.12-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:76719dd521c20a58a6c256d058547b3a9595d1d885b830013366e27011ffe804"},
|
||||
{file = "aiohttp-3.11.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:97fe431f2ed646a3b56142fc81d238abcbaff08548d6912acb0b19a0cadc146b"},
|
||||
{file = "aiohttp-3.11.12-cp311-cp311-win32.whl", hash = "sha256:e10c440d142fa8b32cfdb194caf60ceeceb3e49807072e0dc3a8887ea80e8c16"},
|
||||
{file = "aiohttp-3.11.12-cp311-cp311-win_amd64.whl", hash = "sha256:246067ba0cf5560cf42e775069c5d80a8989d14a7ded21af529a4e10e3e0f0e6"},
|
||||
{file = "aiohttp-3.11.12-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e392804a38353900c3fd8b7cacbea5132888f7129f8e241915e90b85f00e3250"},
|
||||
{file = "aiohttp-3.11.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8fa1510b96c08aaad49303ab11f8803787c99222288f310a62f493faf883ede1"},
|
||||
{file = "aiohttp-3.11.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dc065a4285307607df3f3686363e7f8bdd0d8ab35f12226362a847731516e42c"},
|
||||
{file = "aiohttp-3.11.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddb31f8474695cd61fc9455c644fc1606c164b93bff2490390d90464b4655df"},
|
||||
{file = "aiohttp-3.11.12-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9dec0000d2d8621d8015c293e24589d46fa218637d820894cb7356c77eca3259"},
|
||||
{file = "aiohttp-3.11.12-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3552fe98e90fdf5918c04769f338a87fa4f00f3b28830ea9b78b1bdc6140e0d"},
|
||||
{file = "aiohttp-3.11.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dfe7f984f28a8ae94ff3a7953cd9678550dbd2a1f9bda5dd9c5ae627744c78e"},
|
||||
{file = "aiohttp-3.11.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a481a574af914b6e84624412666cbfbe531a05667ca197804ecc19c97b8ab1b0"},
|
||||
{file = "aiohttp-3.11.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1987770fb4887560363b0e1a9b75aa303e447433c41284d3af2840a2f226d6e0"},
|
||||
{file = "aiohttp-3.11.12-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a4ac6a0f0f6402854adca4e3259a623f5c82ec3f0c049374133bcb243132baf9"},
|
||||
{file = "aiohttp-3.11.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c96a43822f1f9f69cc5c3706af33239489a6294be486a0447fb71380070d4d5f"},
|
||||
{file = "aiohttp-3.11.12-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a5e69046f83c0d3cb8f0d5bd9b8838271b1bc898e01562a04398e160953e8eb9"},
|
||||
{file = "aiohttp-3.11.12-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:68d54234c8d76d8ef74744f9f9fc6324f1508129e23da8883771cdbb5818cbef"},
|
||||
{file = "aiohttp-3.11.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c9fd9dcf9c91affe71654ef77426f5cf8489305e1c66ed4816f5a21874b094b9"},
|
||||
{file = "aiohttp-3.11.12-cp312-cp312-win32.whl", hash = "sha256:0ed49efcd0dc1611378beadbd97beb5d9ca8fe48579fc04a6ed0844072261b6a"},
|
||||
{file = "aiohttp-3.11.12-cp312-cp312-win_amd64.whl", hash = "sha256:54775858c7f2f214476773ce785a19ee81d1294a6bedc5cc17225355aab74802"},
|
||||
{file = "aiohttp-3.11.12-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:413ad794dccb19453e2b97c2375f2ca3cdf34dc50d18cc2693bd5aed7d16f4b9"},
|
||||
{file = "aiohttp-3.11.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4a93d28ed4b4b39e6f46fd240896c29b686b75e39cc6992692e3922ff6982b4c"},
|
||||
{file = "aiohttp-3.11.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d589264dbba3b16e8951b6f145d1e6b883094075283dafcab4cdd564a9e353a0"},
|
||||
{file = "aiohttp-3.11.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5148ca8955affdfeb864aca158ecae11030e952b25b3ae15d4e2b5ba299bad2"},
|
||||
{file = "aiohttp-3.11.12-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:525410e0790aab036492eeea913858989c4cb070ff373ec3bc322d700bdf47c1"},
|
||||
{file = "aiohttp-3.11.12-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bd8695be2c80b665ae3f05cb584093a1e59c35ecb7d794d1edd96e8cc9201d7"},
|
||||
{file = "aiohttp-3.11.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0203433121484b32646a5f5ea93ae86f3d9559d7243f07e8c0eab5ff8e3f70e"},
|
||||
{file = "aiohttp-3.11.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40cd36749a1035c34ba8d8aaf221b91ca3d111532e5ccb5fa8c3703ab1b967ed"},
|
||||
{file = "aiohttp-3.11.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a7442662afebbf7b4c6d28cb7aab9e9ce3a5df055fc4116cc7228192ad6cb484"},
|
||||
{file = "aiohttp-3.11.12-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8a2fb742ef378284a50766e985804bd6adb5adb5aa781100b09befdbfa757b65"},
|
||||
{file = "aiohttp-3.11.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2cee3b117a8d13ab98b38d5b6bdcd040cfb4181068d05ce0c474ec9db5f3c5bb"},
|
||||
{file = "aiohttp-3.11.12-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f6a19bcab7fbd8f8649d6595624856635159a6527861b9cdc3447af288a00c00"},
|
||||
{file = "aiohttp-3.11.12-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e4cecdb52aaa9994fbed6b81d4568427b6002f0a91c322697a4bfcc2b2363f5a"},
|
||||
{file = "aiohttp-3.11.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:30f546358dfa0953db92ba620101fefc81574f87b2346556b90b5f3ef16e55ce"},
|
||||
{file = "aiohttp-3.11.12-cp313-cp313-win32.whl", hash = "sha256:ce1bb21fc7d753b5f8a5d5a4bae99566386b15e716ebdb410154c16c91494d7f"},
|
||||
{file = "aiohttp-3.11.12-cp313-cp313-win_amd64.whl", hash = "sha256:f7914ab70d2ee8ab91c13e5402122edbc77821c66d2758abb53aabe87f013287"},
|
||||
{file = "aiohttp-3.11.12-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7c3623053b85b4296cd3925eeb725e386644fd5bc67250b3bb08b0f144803e7b"},
|
||||
{file = "aiohttp-3.11.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:67453e603cea8e85ed566b2700efa1f6916aefbc0c9fcb2e86aaffc08ec38e78"},
|
||||
{file = "aiohttp-3.11.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6130459189e61baac5a88c10019b21e1f0c6d00ebc770e9ce269475650ff7f73"},
|
||||
{file = "aiohttp-3.11.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9060addfa4ff753b09392efe41e6af06ea5dd257829199747b9f15bfad819460"},
|
||||
{file = "aiohttp-3.11.12-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34245498eeb9ae54c687a07ad7f160053911b5745e186afe2d0c0f2898a1ab8a"},
|
||||
{file = "aiohttp-3.11.12-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8dc0fba9a74b471c45ca1a3cb6e6913ebfae416678d90529d188886278e7f3f6"},
|
||||
{file = "aiohttp-3.11.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a478aa11b328983c4444dacb947d4513cb371cd323f3845e53caeda6be5589d5"},
|
||||
{file = "aiohttp-3.11.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c160a04283c8c6f55b5bf6d4cad59bb9c5b9c9cd08903841b25f1f7109ef1259"},
|
||||
{file = "aiohttp-3.11.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:edb69b9589324bdc40961cdf0657815df674f1743a8d5ad9ab56a99e4833cfdd"},
|
||||
{file = "aiohttp-3.11.12-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4ee84c2a22a809c4f868153b178fe59e71423e1f3d6a8cd416134bb231fbf6d3"},
|
||||
{file = "aiohttp-3.11.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:bf4480a5438f80e0f1539e15a7eb8b5f97a26fe087e9828e2c0ec2be119a9f72"},
|
||||
{file = "aiohttp-3.11.12-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:e6b2732ef3bafc759f653a98881b5b9cdef0716d98f013d376ee8dfd7285abf1"},
|
||||
{file = "aiohttp-3.11.12-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f752e80606b132140883bb262a457c475d219d7163d996dc9072434ffb0784c4"},
|
||||
{file = "aiohttp-3.11.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ab3247d58b393bda5b1c8f31c9edece7162fc13265334217785518dd770792b8"},
|
||||
{file = "aiohttp-3.11.12-cp39-cp39-win32.whl", hash = "sha256:0d5176f310a7fe6f65608213cc74f4228e4f4ce9fd10bcb2bb6da8fc66991462"},
|
||||
{file = "aiohttp-3.11.12-cp39-cp39-win_amd64.whl", hash = "sha256:74bd573dde27e58c760d9ca8615c41a57e719bff315c9adb6f2a4281a28e8798"},
|
||||
{file = "aiohttp-3.11.12.tar.gz", hash = "sha256:7603ca26d75b1b86160ce1bbe2787a0b706e592af5b2504e12caa88a217767b0"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -180,13 +185,13 @@ tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"]
|
|||
|
||||
[[package]]
|
||||
name = "authlib"
|
||||
version = "1.3.2"
|
||||
version = "1.4.1"
|
||||
description = "The ultimate Python library in building OAuth and OpenID Connect servers and clients."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "Authlib-1.3.2-py2.py3-none-any.whl", hash = "sha256:ede026a95e9f5cdc2d4364a52103f5405e75aa156357e831ef2bfd0bc5094dfc"},
|
||||
{file = "authlib-1.3.2.tar.gz", hash = "sha256:4b16130117f9eb82aa6eec97f6dd4673c3f960ac0283ccdae2897ee4bc030ba2"},
|
||||
{file = "Authlib-1.4.1-py2.py3-none-any.whl", hash = "sha256:edc29c3f6a3e72cd9e9f45fff67fc663a2c364022eb0371c003f22d5405915c1"},
|
||||
{file = "authlib-1.4.1.tar.gz", hash = "sha256:30ead9ea4993cdbab821dc6e01e818362f92da290c04c7f6a1940f86507a790d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -206,52 +211,6 @@ files = [
|
|||
[package.extras]
|
||||
dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"]
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "24.10.0"
|
||||
description = "The uncompromising code formatter."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"},
|
||||
{file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"},
|
||||
{file = "black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f"},
|
||||
{file = "black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"},
|
||||
{file = "black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad"},
|
||||
{file = "black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50"},
|
||||
{file = "black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392"},
|
||||
{file = "black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175"},
|
||||
{file = "black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3"},
|
||||
{file = "black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65"},
|
||||
{file = "black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f"},
|
||||
{file = "black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8"},
|
||||
{file = "black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981"},
|
||||
{file = "black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b"},
|
||||
{file = "black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2"},
|
||||
{file = "black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b"},
|
||||
{file = "black-24.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd"},
|
||||
{file = "black-24.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f"},
|
||||
{file = "black-24.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800"},
|
||||
{file = "black-24.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7"},
|
||||
{file = "black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d"},
|
||||
{file = "black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=8.0.0"
|
||||
mypy-extensions = ">=0.4.3"
|
||||
packaging = ">=22.0"
|
||||
pathspec = ">=0.9.0"
|
||||
platformdirs = ">=2"
|
||||
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
||||
typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
colorama = ["colorama (>=0.4.3)"]
|
||||
d = ["aiohttp (>=3.10)"]
|
||||
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
||||
uvloop = ["uvloop (>=0.15.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2024.8.30"
|
||||
|
@ -468,73 +427,68 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.6.8"
|
||||
version = "7.6.11"
|
||||
description = "Code coverage measurement for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "coverage-7.6.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b39e6011cd06822eb964d038d5dff5da5d98652b81f5ecd439277b32361a3a50"},
|
||||
{file = "coverage-7.6.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:63c19702db10ad79151a059d2d6336fe0c470f2e18d0d4d1a57f7f9713875dcf"},
|
||||
{file = "coverage-7.6.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3985b9be361d8fb6b2d1adc9924d01dec575a1d7453a14cccd73225cb79243ee"},
|
||||
{file = "coverage-7.6.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:644ec81edec0f4ad17d51c838a7d01e42811054543b76d4ba2c5d6af741ce2a6"},
|
||||
{file = "coverage-7.6.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f188a2402f8359cf0c4b1fe89eea40dc13b52e7b4fd4812450da9fcd210181d"},
|
||||
{file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e19122296822deafce89a0c5e8685704c067ae65d45e79718c92df7b3ec3d331"},
|
||||
{file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:13618bed0c38acc418896005732e565b317aa9e98d855a0e9f211a7ffc2d6638"},
|
||||
{file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:193e3bffca48ad74b8c764fb4492dd875038a2f9925530cb094db92bb5e47bed"},
|
||||
{file = "coverage-7.6.8-cp310-cp310-win32.whl", hash = "sha256:3988665ee376abce49613701336544041f2117de7b7fbfe91b93d8ff8b151c8e"},
|
||||
{file = "coverage-7.6.8-cp310-cp310-win_amd64.whl", hash = "sha256:f56f49b2553d7dd85fd86e029515a221e5c1f8cb3d9c38b470bc38bde7b8445a"},
|
||||
{file = "coverage-7.6.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:86cffe9c6dfcfe22e28027069725c7f57f4b868a3f86e81d1c62462764dc46d4"},
|
||||
{file = "coverage-7.6.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d82ab6816c3277dc962cfcdc85b1efa0e5f50fb2c449432deaf2398a2928ab94"},
|
||||
{file = "coverage-7.6.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13690e923a3932e4fad4c0ebfb9cb5988e03d9dcb4c5150b5fcbf58fd8bddfc4"},
|
||||
{file = "coverage-7.6.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4be32da0c3827ac9132bb488d331cb32e8d9638dd41a0557c5569d57cf22c9c1"},
|
||||
{file = "coverage-7.6.8-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44e6c85bbdc809383b509d732b06419fb4544dca29ebe18480379633623baafb"},
|
||||
{file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:768939f7c4353c0fac2f7c37897e10b1414b571fd85dd9fc49e6a87e37a2e0d8"},
|
||||
{file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e44961e36cb13c495806d4cac67640ac2866cb99044e210895b506c26ee63d3a"},
|
||||
{file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ea8bb1ab9558374c0ab591783808511d135a833c3ca64a18ec927f20c4030f0"},
|
||||
{file = "coverage-7.6.8-cp311-cp311-win32.whl", hash = "sha256:629a1ba2115dce8bf75a5cce9f2486ae483cb89c0145795603d6554bdc83e801"},
|
||||
{file = "coverage-7.6.8-cp311-cp311-win_amd64.whl", hash = "sha256:fb9fc32399dca861584d96eccd6c980b69bbcd7c228d06fb74fe53e007aa8ef9"},
|
||||
{file = "coverage-7.6.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e683e6ecc587643f8cde8f5da6768e9d165cd31edf39ee90ed7034f9ca0eefee"},
|
||||
{file = "coverage-7.6.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1defe91d41ce1bd44b40fabf071e6a01a5aa14de4a31b986aa9dfd1b3e3e414a"},
|
||||
{file = "coverage-7.6.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7ad66e8e50225ebf4236368cc43c37f59d5e6728f15f6e258c8639fa0dd8e6d"},
|
||||
{file = "coverage-7.6.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fe47da3e4fda5f1abb5709c156eca207eacf8007304ce3019eb001e7a7204cb"},
|
||||
{file = "coverage-7.6.8-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:202a2d645c5a46b84992f55b0a3affe4f0ba6b4c611abec32ee88358db4bb649"},
|
||||
{file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4674f0daa1823c295845b6a740d98a840d7a1c11df00d1fd62614545c1583787"},
|
||||
{file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:74610105ebd6f33d7c10f8907afed696e79c59e3043c5f20eaa3a46fddf33b4c"},
|
||||
{file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37cda8712145917105e07aab96388ae76e787270ec04bcb9d5cc786d7cbb8443"},
|
||||
{file = "coverage-7.6.8-cp312-cp312-win32.whl", hash = "sha256:9e89d5c8509fbd6c03d0dd1972925b22f50db0792ce06324ba069f10787429ad"},
|
||||
{file = "coverage-7.6.8-cp312-cp312-win_amd64.whl", hash = "sha256:379c111d3558272a2cae3d8e57e6b6e6f4fe652905692d54bad5ea0ca37c5ad4"},
|
||||
{file = "coverage-7.6.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b0c69f4f724c64dfbfe79f5dfb503b42fe6127b8d479b2677f2b227478db2eb"},
|
||||
{file = "coverage-7.6.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c15b32a7aca8038ed7644f854bf17b663bc38e1671b5d6f43f9a2b2bd0c46f63"},
|
||||
{file = "coverage-7.6.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63068a11171e4276f6ece913bde059e77c713b48c3a848814a6537f35afb8365"},
|
||||
{file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4548c5ead23ad13fb7a2c8ea541357474ec13c2b736feb02e19a3085fac002"},
|
||||
{file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b4b4299dd0d2c67caaaf286d58aef5e75b125b95615dda4542561a5a566a1e3"},
|
||||
{file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9ebfb2507751f7196995142f057d1324afdab56db1d9743aab7f50289abd022"},
|
||||
{file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c1b4474beee02ede1eef86c25ad4600a424fe36cff01a6103cb4533c6bf0169e"},
|
||||
{file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d9fd2547e6decdbf985d579cf3fc78e4c1d662b9b0ff7cc7862baaab71c9cc5b"},
|
||||
{file = "coverage-7.6.8-cp313-cp313-win32.whl", hash = "sha256:8aae5aea53cbfe024919715eca696b1a3201886ce83790537d1c3668459c7146"},
|
||||
{file = "coverage-7.6.8-cp313-cp313-win_amd64.whl", hash = "sha256:ae270e79f7e169ccfe23284ff5ea2d52a6f401dc01b337efb54b3783e2ce3f28"},
|
||||
{file = "coverage-7.6.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de38add67a0af869b0d79c525d3e4588ac1ffa92f39116dbe0ed9753f26eba7d"},
|
||||
{file = "coverage-7.6.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b07c25d52b1c16ce5de088046cd2432b30f9ad5e224ff17c8f496d9cb7d1d451"},
|
||||
{file = "coverage-7.6.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a66ff235e4c2e37ed3b6104d8b478d767ff73838d1222132a7a026aa548764"},
|
||||
{file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09b9f848b28081e7b975a3626e9081574a7b9196cde26604540582da60235fdf"},
|
||||
{file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:093896e530c38c8e9c996901858ac63f3d4171268db2c9c8b373a228f459bbc5"},
|
||||
{file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a7b8ac36fd688c8361cbc7bf1cb5866977ece6e0b17c34aa0df58bda4fa18a4"},
|
||||
{file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:38c51297b35b3ed91670e1e4efb702b790002e3245a28c76e627478aa3c10d83"},
|
||||
{file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2e4e0f60cb4bd7396108823548e82fdab72d4d8a65e58e2c19bbbc2f1e2bfa4b"},
|
||||
{file = "coverage-7.6.8-cp313-cp313t-win32.whl", hash = "sha256:6535d996f6537ecb298b4e287a855f37deaf64ff007162ec0afb9ab8ba3b8b71"},
|
||||
{file = "coverage-7.6.8-cp313-cp313t-win_amd64.whl", hash = "sha256:c79c0685f142ca53256722a384540832420dff4ab15fec1863d7e5bc8691bdcc"},
|
||||
{file = "coverage-7.6.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ac47fa29d8d41059ea3df65bd3ade92f97ee4910ed638e87075b8e8ce69599e"},
|
||||
{file = "coverage-7.6.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:24eda3a24a38157eee639ca9afe45eefa8d2420d49468819ac5f88b10de84f4c"},
|
||||
{file = "coverage-7.6.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4c81ed2820b9023a9a90717020315e63b17b18c274a332e3b6437d7ff70abe0"},
|
||||
{file = "coverage-7.6.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd55f8fc8fa494958772a2a7302b0354ab16e0b9272b3c3d83cdb5bec5bd1779"},
|
||||
{file = "coverage-7.6.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f39e2f3530ed1626c66e7493be7a8423b023ca852aacdc91fb30162c350d2a92"},
|
||||
{file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:716a78a342679cd1177bc8c2fe957e0ab91405bd43a17094324845200b2fddf4"},
|
||||
{file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:177f01eeaa3aee4a5ffb0d1439c5952b53d5010f86e9d2667963e632e30082cc"},
|
||||
{file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:912e95017ff51dc3d7b6e2be158dedc889d9a5cc3382445589ce554f1a34c0ea"},
|
||||
{file = "coverage-7.6.8-cp39-cp39-win32.whl", hash = "sha256:4db3ed6a907b555e57cc2e6f14dc3a4c2458cdad8919e40b5357ab9b6db6c43e"},
|
||||
{file = "coverage-7.6.8-cp39-cp39-win_amd64.whl", hash = "sha256:428ac484592f780e8cd7b6b14eb568f7c85460c92e2a37cb0c0e5186e1a0d076"},
|
||||
{file = "coverage-7.6.8-pp39.pp310-none-any.whl", hash = "sha256:5c52a036535d12590c32c49209e79cabaad9f9ad8aa4cbd875b68c4d67a9cbce"},
|
||||
{file = "coverage-7.6.8.tar.gz", hash = "sha256:8b2b8503edb06822c86d82fa64a4a5cb0760bb8f31f26e138ec743f422f37cfc"},
|
||||
{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"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
|
@ -807,13 +761,13 @@ colors = ["colorama (>=0.4.6)"]
|
|||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.4"
|
||||
version = "3.1.5"
|
||||
description = "A very fast and expressive template engine."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"},
|
||||
{file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"},
|
||||
{file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"},
|
||||
{file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -999,13 +953,13 @@ min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-imp
|
|||
|
||||
[[package]]
|
||||
name = "mkdocs-autorefs"
|
||||
version = "1.2.0"
|
||||
version = "1.3.0"
|
||||
description = "Automatically link across pages in MkDocs."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "mkdocs_autorefs-1.2.0-py3-none-any.whl", hash = "sha256:d588754ae89bd0ced0c70c06f58566a4ee43471eeeee5202427da7de9ef85a2f"},
|
||||
{file = "mkdocs_autorefs-1.2.0.tar.gz", hash = "sha256:a86b93abff653521bda71cf3fc5596342b7a23982093915cb74273f67522190f"},
|
||||
{file = "mkdocs_autorefs-1.3.0-py3-none-any.whl", hash = "sha256:d180f9778a04e78b7134e31418f238bba56f56d6a8af97873946ff661befffb3"},
|
||||
{file = "mkdocs_autorefs-1.3.0.tar.gz", hash = "sha256:6867764c099ace9025d6ac24fd07b85a98335fbd30107ef01053697c8f46db61"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -1032,13 +986,13 @@ pyyaml = ">=5.1"
|
|||
|
||||
[[package]]
|
||||
name = "mkdocs-material"
|
||||
version = "9.5.47"
|
||||
version = "9.6.3"
|
||||
description = "Documentation that simply works"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "mkdocs_material-9.5.47-py3-none-any.whl", hash = "sha256:53fb9c9624e7865da6ec807d116cd7be24b3cb36ab31b1d1d1a9af58c56009a2"},
|
||||
{file = "mkdocs_material-9.5.47.tar.gz", hash = "sha256:fc3b7a8e00ad896660bd3a5cc12ca0cb28bdc2bcbe2a946b5714c23ac91b0ede"},
|
||||
{file = "mkdocs_material-9.6.3-py3-none-any.whl", hash = "sha256:1125622067e26940806701219303b27c0933e04533560725d97ec26fd16a39cf"},
|
||||
{file = "mkdocs_material-9.6.3.tar.gz", hash = "sha256:c87f7d1c39ce6326da5e10e232aed51bae46252e646755900f4b0fc9192fa832"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -1055,7 +1009,7 @@ regex = ">=2022.4"
|
|||
requests = ">=2.26,<3.0"
|
||||
|
||||
[package.extras]
|
||||
git = ["mkdocs-git-committers-plugin-2 (>=1.1,<2.0)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4,<2.0)"]
|
||||
git = ["mkdocs-git-committers-plugin-2 (>=1.1,<3)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4,<2.0)"]
|
||||
imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=10.2,<11.0)"]
|
||||
recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2.0)", "mkdocs-rss-plugin (>=1.6,<2.0)"]
|
||||
|
||||
|
@ -1072,24 +1026,23 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "mkdocstrings"
|
||||
version = "0.27.0"
|
||||
version = "0.28.0"
|
||||
description = "Automatic documentation from sources, for MkDocs."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "mkdocstrings-0.27.0-py3-none-any.whl", hash = "sha256:6ceaa7ea830770959b55a16203ac63da24badd71325b96af950e59fd37366332"},
|
||||
{file = "mkdocstrings-0.27.0.tar.gz", hash = "sha256:16adca6d6b0a1f9e0c07ff0b02ced8e16f228a9d65a37c063ec4c14d7b76a657"},
|
||||
{file = "mkdocstrings-0.28.0-py3-none-any.whl", hash = "sha256:84cf3dc910614781fe0fee46ce8006fde7df6cc7cca2e3f799895fb8a9170b39"},
|
||||
{file = "mkdocstrings-0.28.0.tar.gz", hash = "sha256:df20afef1eafe36ba466ae20732509ecb74237653a585f5061937e54b553b4e0"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=7.0"
|
||||
importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""}
|
||||
Jinja2 = ">=2.11.1"
|
||||
Markdown = ">=3.6"
|
||||
MarkupSafe = ">=1.1"
|
||||
mkdocs = ">=1.4"
|
||||
mkdocs-autorefs = ">=1.2"
|
||||
platformdirs = ">=2.2"
|
||||
mkdocs-autorefs = ">=1.3"
|
||||
mkdocs-get-deps = ">=0.2"
|
||||
pymdown-extensions = ">=6.3"
|
||||
typing-extensions = {version = ">=4.1", markers = "python_version < \"3.10\""}
|
||||
|
||||
|
@ -1100,19 +1053,20 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"]
|
|||
|
||||
[[package]]
|
||||
name = "mkdocstrings-python"
|
||||
version = "1.12.2"
|
||||
version = "1.14.6"
|
||||
description = "A Python handler for mkdocstrings."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "mkdocstrings_python-1.12.2-py3-none-any.whl", hash = "sha256:7f7d40d6db3cb1f5d19dbcd80e3efe4d0ba32b073272c0c0de9de2e604eda62a"},
|
||||
{file = "mkdocstrings_python-1.12.2.tar.gz", hash = "sha256:7a1760941c0b52a2cd87b960a9e21112ffe52e7df9d0b9583d04d47ed2e186f3"},
|
||||
{file = "mkdocstrings_python-1.14.6-py3-none-any.whl", hash = "sha256:e0ca11b49ac0f23070afb566245f4ff80ea1700e03c9dbc143d70dbf1cae074e"},
|
||||
{file = "mkdocstrings_python-1.14.6.tar.gz", hash = "sha256:3fb6589491614422d781dacca085c0c5a53a7063af37a2fea5864e24e378b03e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
griffe = ">=0.49"
|
||||
mkdocs-autorefs = ">=1.2"
|
||||
mkdocstrings = ">=0.26"
|
||||
mkdocstrings = ">=0.28"
|
||||
typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""}
|
||||
|
||||
[[package]]
|
||||
name = "multidict"
|
||||
|
@ -1215,49 +1169,49 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "1.13.0"
|
||||
version = "1.15.0"
|
||||
description = "Optional static typing for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"},
|
||||
{file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"},
|
||||
{file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"},
|
||||
{file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"},
|
||||
{file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"},
|
||||
{file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"},
|
||||
{file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"},
|
||||
{file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"},
|
||||
{file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"},
|
||||
{file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"},
|
||||
{file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"},
|
||||
{file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"},
|
||||
{file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"},
|
||||
{file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"},
|
||||
{file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"},
|
||||
{file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"},
|
||||
{file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"},
|
||||
{file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"},
|
||||
{file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"},
|
||||
{file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"},
|
||||
{file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"},
|
||||
{file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"},
|
||||
{file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"},
|
||||
{file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"},
|
||||
{file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"},
|
||||
{file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"},
|
||||
{file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"},
|
||||
{file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"},
|
||||
{file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"},
|
||||
{file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"},
|
||||
{file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"},
|
||||
{file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"},
|
||||
{file = "mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13"},
|
||||
{file = "mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559"},
|
||||
{file = "mypy-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be68172e9fd9ad8fb876c6389f16d1c1b5f100ffa779f77b1fb2176fcc9ab95b"},
|
||||
{file = "mypy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7be1e46525adfa0d97681432ee9fcd61a3964c2446795714699a998d193f1a3"},
|
||||
{file = "mypy-1.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2e2c2e6d3593f6451b18588848e66260ff62ccca522dd231cd4dd59b0160668b"},
|
||||
{file = "mypy-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:6983aae8b2f653e098edb77f893f7b6aca69f6cffb19b2cc7443f23cce5f4828"},
|
||||
{file = "mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f"},
|
||||
{file = "mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5"},
|
||||
{file = "mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e"},
|
||||
{file = "mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c"},
|
||||
{file = "mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f"},
|
||||
{file = "mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f"},
|
||||
{file = "mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd"},
|
||||
{file = "mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f"},
|
||||
{file = "mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464"},
|
||||
{file = "mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee"},
|
||||
{file = "mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e"},
|
||||
{file = "mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22"},
|
||||
{file = "mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445"},
|
||||
{file = "mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d"},
|
||||
{file = "mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5"},
|
||||
{file = "mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036"},
|
||||
{file = "mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357"},
|
||||
{file = "mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf"},
|
||||
{file = "mypy-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e601a7fa172c2131bff456bb3ee08a88360760d0d2f8cbd7a75a65497e2df078"},
|
||||
{file = "mypy-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:712e962a6357634fef20412699a3655c610110e01cdaa6180acec7fc9f8513ba"},
|
||||
{file = "mypy-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95579473af29ab73a10bada2f9722856792a36ec5af5399b653aa28360290a5"},
|
||||
{file = "mypy-1.15.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f8722560a14cde92fdb1e31597760dc35f9f5524cce17836c0d22841830fd5b"},
|
||||
{file = "mypy-1.15.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fbb8da62dc352133d7d7ca90ed2fb0e9d42bb1a32724c287d3c76c58cbaa9c2"},
|
||||
{file = "mypy-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:d10d994b41fb3497719bbf866f227b3489048ea4bbbb5015357db306249f7980"},
|
||||
{file = "mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e"},
|
||||
{file = "mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
mypy-extensions = ">=1.0.0"
|
||||
mypy_extensions = ">=1.0.0"
|
||||
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
||||
typing-extensions = ">=4.6.0"
|
||||
typing_extensions = ">=4.6.0"
|
||||
|
||||
[package.extras]
|
||||
dmypy = ["psutil (>=4.0)"]
|
||||
|
@ -1361,13 +1315,13 @@ testing = ["pytest", "pytest-benchmark"]
|
|||
|
||||
[[package]]
|
||||
name = "pook"
|
||||
version = "2.1.2"
|
||||
version = "2.1.3"
|
||||
description = "HTTP traffic mocking and expectations made easy"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "pook-2.1.2-py3-none-any.whl", hash = "sha256:ea76784ee1440ee8dde08047c6a1f68be46a07d07a5180d068a047db5620ca1d"},
|
||||
{file = "pook-2.1.2.tar.gz", hash = "sha256:4acbb9d13ac18b807fd3a54b414a22a16b75db6e5048bc88461479da03c4ecbf"},
|
||||
{file = "pook-2.1.3-py3-none-any.whl", hash = "sha256:f8e75e2e41b1f6da37d0bc6b77a0f4da33c4d4de382105046efd644fe5ca2f8e"},
|
||||
{file = "pook-2.1.3.tar.gz", hash = "sha256:441191c0f3d014b141ca71430a0c2bfa6d2369ac24703a3fdfbbf5a25146d8c0"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -1495,18 +1449,18 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.10.2"
|
||||
version = "2.10.6"
|
||||
description = "Data validation using Python type hints"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pydantic-2.10.2-py3-none-any.whl", hash = "sha256:cfb96e45951117c3024e6b67b25cdc33a3cb7b2fa62e239f7af1378358a1d99e"},
|
||||
{file = "pydantic-2.10.2.tar.gz", hash = "sha256:2bc2d7f17232e0841cbba4641e65ba1eb6fafb3a08de3a091ff3ce14a197c4fa"},
|
||||
{file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"},
|
||||
{file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
annotated-types = ">=0.6.0"
|
||||
pydantic-core = "2.27.1"
|
||||
pydantic-core = "2.27.2"
|
||||
typing-extensions = ">=4.12.2"
|
||||
|
||||
[package.extras]
|
||||
|
@ -1515,111 +1469,111 @@ timezone = ["tzdata"]
|
|||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.27.1"
|
||||
version = "2.27.2"
|
||||
description = "Core functionality for Pydantic validation and serialization"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a"},
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b"},
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:121ceb0e822f79163dd4699e4c54f5ad38b157084d97b34de8b232bcaad70278"},
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4603137322c18eaf2e06a4495f426aa8d8388940f3c457e7548145011bb68e05"},
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a33cd6ad9017bbeaa9ed78a2e0752c5e250eafb9534f308e7a5f7849b0b1bfb4"},
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15cc53a3179ba0fcefe1e3ae50beb2784dede4003ad2dfd24f81bba4b23a454f"},
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45d9c5eb9273aa50999ad6adc6be5e0ecea7e09dbd0d31bd0c65a55a2592ca08"},
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bf7b66ce12a2ac52d16f776b31d16d91033150266eb796967a7e4621707e4f6"},
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:655d7dd86f26cb15ce8a431036f66ce0318648f8853d709b4167786ec2fa4807"},
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:5556470f1a2157031e676f776c2bc20acd34c1990ca5f7e56f1ebf938b9ab57c"},
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f69ed81ab24d5a3bd93861c8c4436f54afdf8e8cc421562b0c7504cf3be58206"},
|
||||
{file = "pydantic_core-2.27.1-cp310-none-win32.whl", hash = "sha256:f5a823165e6d04ccea61a9f0576f345f8ce40ed533013580e087bd4d7442b52c"},
|
||||
{file = "pydantic_core-2.27.1-cp310-none-win_amd64.whl", hash = "sha256:57866a76e0b3823e0b56692d1a0bf722bffb324839bb5b7226a7dbd6c9a40b17"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac3b20653bdbe160febbea8aa6c079d3df19310d50ac314911ed8cc4eb7f8cb8"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a5a8e19d7c707c4cadb8c18f5f60c843052ae83c20fa7d44f41594c644a1d330"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f7059ca8d64fea7f238994c97d91f75965216bcbe5f695bb44f354893f11d52"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bed0f8a0eeea9fb72937ba118f9db0cb7e90773462af7962d382445f3005e5a4"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3cb37038123447cf0f3ea4c74751f6a9d7afef0eb71aa07bf5f652b5e6a132c"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84286494f6c5d05243456e04223d5a9417d7f443c3b76065e75001beb26f88de"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acc07b2cfc5b835444b44a9956846b578d27beeacd4b52e45489e93276241025"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fefee876e07a6e9aad7a8c8c9f85b0cdbe7df52b8a9552307b09050f7512c7e"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:258c57abf1188926c774a4c94dd29237e77eda19462e5bb901d88adcab6af919"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:35c14ac45fcfdf7167ca76cc80b2001205a8d5d16d80524e13508371fb8cdd9c"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1b26e1dff225c31897696cab7d4f0a315d4c0d9e8666dbffdb28216f3b17fdc"},
|
||||
{file = "pydantic_core-2.27.1-cp311-none-win32.whl", hash = "sha256:2cdf7d86886bc6982354862204ae3b2f7f96f21a3eb0ba5ca0ac42c7b38598b9"},
|
||||
{file = "pydantic_core-2.27.1-cp311-none-win_amd64.whl", hash = "sha256:3af385b0cee8df3746c3f406f38bcbfdc9041b5c2d5ce3e5fc6637256e60bbc5"},
|
||||
{file = "pydantic_core-2.27.1-cp311-none-win_arm64.whl", hash = "sha256:81f2ec23ddc1b476ff96563f2e8d723830b06dceae348ce02914a37cb4e74b89"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae"},
|
||||
{file = "pydantic_core-2.27.1-cp312-none-win32.whl", hash = "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c"},
|
||||
{file = "pydantic_core-2.27.1-cp312-none-win_amd64.whl", hash = "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16"},
|
||||
{file = "pydantic_core-2.27.1-cp312-none-win_arm64.whl", hash = "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23"},
|
||||
{file = "pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05"},
|
||||
{file = "pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337"},
|
||||
{file = "pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:5897bec80a09b4084aee23f9b73a9477a46c3304ad1d2d07acca19723fb1de62"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d0165ab2914379bd56908c02294ed8405c252250668ebcb438a55494c69f44ab"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b9af86e1d8e4cfc82c2022bfaa6f459381a50b94a29e95dcdda8442d6d83864"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f6c8a66741c5f5447e047ab0ba7a1c61d1e95580d64bce852e3df1f895c4067"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a42d6a8156ff78981f8aa56eb6394114e0dedb217cf8b729f438f643608cbcd"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64c65f40b4cd8b0e049a8edde07e38b476da7e3aaebe63287c899d2cff253fa5"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdcf339322a3fae5cbd504edcefddd5a50d9ee00d968696846f089b4432cf78"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bf99c8404f008750c846cb4ac4667b798a9f7de673ff719d705d9b2d6de49c5f"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8f1edcea27918d748c7e5e4d917297b2a0ab80cad10f86631e488b7cddf76a36"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:159cac0a3d096f79ab6a44d77a961917219707e2a130739c64d4dd46281f5c2a"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:029d9757eb621cc6e1848fa0b0310310de7301057f623985698ed7ebb014391b"},
|
||||
{file = "pydantic_core-2.27.1-cp38-none-win32.whl", hash = "sha256:a28af0695a45f7060e6f9b7092558a928a28553366519f64083c63a44f70e618"},
|
||||
{file = "pydantic_core-2.27.1-cp38-none-win_amd64.whl", hash = "sha256:2d4567c850905d5eaaed2f7a404e61012a51caf288292e016360aa2b96ff38d4"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:e9386266798d64eeb19dd3677051f5705bf873e98e15897ddb7d76f477131967"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4228b5b646caa73f119b1ae756216b59cc6e2267201c27d3912b592c5e323b60"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3dfe500de26c52abe0477dde16192ac39c98f05bf2d80e76102d394bd13854"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aee66be87825cdf72ac64cb03ad4c15ffef4143dbf5c113f64a5ff4f81477bf9"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b748c44bb9f53031c8cbc99a8a061bc181c1000c60a30f55393b6e9c45cc5bd"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ca038c7f6a0afd0b2448941b6ef9d5e1949e999f9e5517692eb6da58e9d44be"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e0bd57539da59a3e4671b90a502da9a28c72322a4f17866ba3ac63a82c4498e"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ac6c2c45c847bbf8f91930d88716a0fb924b51e0c6dad329b793d670ec5db792"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b94d4ba43739bbe8b0ce4262bcc3b7b9f31459ad120fb595627eaeb7f9b9ca01"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:00e6424f4b26fe82d44577b4c842d7df97c20be6439e8e685d0d715feceb9fb9"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:38de0a70160dd97540335b7ad3a74571b24f1dc3ed33f815f0880682e6880131"},
|
||||
{file = "pydantic_core-2.27.1-cp39-none-win32.whl", hash = "sha256:7ccebf51efc61634f6c2344da73e366c75e735960b5654b63d7e6f69a5885fa3"},
|
||||
{file = "pydantic_core-2.27.1-cp39-none-win_amd64.whl", hash = "sha256:a57847b090d7892f123726202b7daa20df6694cbd583b67a592e856bff603d6c"},
|
||||
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3fa80ac2bd5856580e242dbc202db873c60a01b20309c8319b5c5986fbe53ce6"},
|
||||
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d950caa237bb1954f1b8c9227b5065ba6875ac9771bb8ec790d956a699b78676"},
|
||||
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e4216e64d203e39c62df627aa882f02a2438d18a5f21d7f721621f7a5d3611d"},
|
||||
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a3d637bd387c41d46b002f0e49c52642281edacd2740e5a42f7017feea3f2c"},
|
||||
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:161c27ccce13b6b0c8689418da3885d3220ed2eae2ea5e9b2f7f3d48f1d52c27"},
|
||||
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:19910754e4cc9c63bc1c7f6d73aa1cfee82f42007e407c0f413695c2f7ed777f"},
|
||||
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e173486019cc283dc9778315fa29a363579372fe67045e971e89b6365cc035ed"},
|
||||
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:af52d26579b308921b73b956153066481f064875140ccd1dfd4e77db89dbb12f"},
|
||||
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:981fb88516bd1ae8b0cbbd2034678a39dedc98752f264ac9bc5839d3923fa04c"},
|
||||
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5fde892e6c697ce3e30c61b239330fc5d569a71fefd4eb6512fc6caec9dd9e2f"},
|
||||
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:816f5aa087094099fff7edabb5e01cc370eb21aa1a1d44fe2d2aefdfb5599b31"},
|
||||
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c10c309e18e443ddb108f0ef64e8729363adbfd92d6d57beec680f6261556f3"},
|
||||
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98476c98b02c8e9b2eec76ac4156fd006628b1b2d0ef27e548ffa978393fd154"},
|
||||
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3027001c28434e7ca5a6e1e527487051136aa81803ac812be51802150d880dd"},
|
||||
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7699b1df36a48169cdebda7ab5a2bac265204003f153b4bd17276153d997670a"},
|
||||
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1c39b07d90be6b48968ddc8c19e7585052088fd7ec8d568bb31ff64c70ae3c97"},
|
||||
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:46ccfe3032b3915586e469d4972973f893c0a2bb65669194a5bdea9bacc088c2"},
|
||||
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:62ba45e21cf6571d7f716d903b5b7b6d2617e2d5d67c0923dc47b9d41369f840"},
|
||||
{file = "pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"},
|
||||
{file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"},
|
||||
{file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"},
|
||||
{file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"},
|
||||
{file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b"},
|
||||
{file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"},
|
||||
{file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"},
|
||||
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"},
|
||||
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"},
|
||||
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"},
|
||||
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"},
|
||||
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"},
|
||||
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"},
|
||||
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"},
|
||||
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"},
|
||||
{file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"},
|
||||
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"},
|
||||
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"},
|
||||
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"},
|
||||
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"},
|
||||
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"},
|
||||
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"},
|
||||
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"},
|
||||
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"},
|
||||
{file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"},
|
||||
{file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -1685,12 +1639,12 @@ pylint = ">=1.7"
|
|||
|
||||
[[package]]
|
||||
name = "pylint-pydantic"
|
||||
version = "0.3.2"
|
||||
version = "0.3.5"
|
||||
description = "A Pylint plugin to help Pylint understand the Pydantic"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pylint_pydantic-0.3.2-py3-none-any.whl", hash = "sha256:e5cec02370aa68ac8eff138e5d573b0ac049bab864e9a6c3a9057cf043440aa1"},
|
||||
{file = "pylint_pydantic-0.3.5-py3-none-any.whl", hash = "sha256:e7a54f09843b000676633ed02d5985a4a61c8da2560a3b0d46082d2ff171c4a1"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -1698,21 +1652,6 @@ pydantic = "<3.0"
|
|||
pylint = ">2.0,<4.0"
|
||||
pylint-plugin-utils = "*"
|
||||
|
||||
[[package]]
|
||||
name = "pylint-pytest"
|
||||
version = "1.1.7"
|
||||
description = "A Pylint plugin to suppress pytest-related false positives."
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "pylint-pytest-1.1.7.tar.gz", hash = "sha256:7a38be02c014eb6d98791eb978e79ed292f1904d3a518289c6d7ac4fb4122e98"},
|
||||
{file = "pylint_pytest-1.1.7-py3-none-any.whl", hash = "sha256:5d687a2f4b17e85654fc2a8f04944761efb11cb15dc46d008f420c377b149151"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pylint = ">=2"
|
||||
pytest = ">=4.6"
|
||||
|
||||
[[package]]
|
||||
name = "pymdown-extensions"
|
||||
version = "10.9"
|
||||
|
@ -1755,39 +1694,39 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments
|
|||
|
||||
[[package]]
|
||||
name = "pytest-aiohttp"
|
||||
version = "1.0.5"
|
||||
version = "1.1.0"
|
||||
description = "Pytest plugin for aiohttp support"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "pytest-aiohttp-1.0.5.tar.gz", hash = "sha256:880262bc5951e934463b15e3af8bb298f11f7d4d3ebac970aab425aff10a780a"},
|
||||
{file = "pytest_aiohttp-1.0.5-py3-none-any.whl", hash = "sha256:63a5360fd2f34dda4ab8e6baee4c5f5be4cd186a403cabd498fced82ac9c561e"},
|
||||
{file = "pytest_aiohttp-1.1.0-py3-none-any.whl", hash = "sha256:f39a11693a0dce08dd6c542d241e199dd8047a6e6596b2bcfa60d373f143456d"},
|
||||
{file = "pytest_aiohttp-1.1.0.tar.gz", hash = "sha256:147de8cb164f3fc9d7196967f109ab3c0b93ea3463ab50631e56438eab7b5adc"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
aiohttp = ">=3.8.1"
|
||||
aiohttp = ">=3.11.0b0"
|
||||
pytest = ">=6.1.0"
|
||||
pytest-asyncio = ">=0.17.2"
|
||||
|
||||
[package.extras]
|
||||
testing = ["coverage (==6.2)", "mypy (==0.931)"]
|
||||
testing = ["coverage (==6.2)", "mypy (==1.12.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-asyncio"
|
||||
version = "0.24.0"
|
||||
version = "0.25.3"
|
||||
description = "Pytest support for asyncio"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "pytest_asyncio-0.24.0-py3-none-any.whl", hash = "sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b"},
|
||||
{file = "pytest_asyncio-0.24.0.tar.gz", hash = "sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276"},
|
||||
{file = "pytest_asyncio-0.25.3-py3-none-any.whl", hash = "sha256:9e89518e0f9bd08928f97a3482fdc4e244df17529460bc038291ccaf8f85c7c3"},
|
||||
{file = "pytest_asyncio-0.25.3.tar.gz", hash = "sha256:fc1da2cf9f125ada7e710b4ddad05518d4cee187ae9412e9ac9271003497f07a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pytest = ">=8.2,<9"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"]
|
||||
docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"]
|
||||
testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"]
|
||||
|
||||
[[package]]
|
||||
|
@ -2135,29 +2074,29 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.8.1"
|
||||
version = "0.9.5"
|
||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "ruff-0.8.1-py3-none-linux_armv6l.whl", hash = "sha256:fae0805bd514066f20309f6742f6ee7904a773eb9e6c17c45d6b1600ca65c9b5"},
|
||||
{file = "ruff-0.8.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8a4f7385c2285c30f34b200ca5511fcc865f17578383db154e098150ce0a087"},
|
||||
{file = "ruff-0.8.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cd054486da0c53e41e0086e1730eb77d1f698154f910e0cd9e0d64274979a209"},
|
||||
{file = "ruff-0.8.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2029b8c22da147c50ae577e621a5bfbc5d1fed75d86af53643d7a7aee1d23871"},
|
||||
{file = "ruff-0.8.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2666520828dee7dfc7e47ee4ea0d928f40de72056d929a7c5292d95071d881d1"},
|
||||
{file = "ruff-0.8.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:333c57013ef8c97a53892aa56042831c372e0bb1785ab7026187b7abd0135ad5"},
|
||||
{file = "ruff-0.8.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:288326162804f34088ac007139488dcb43de590a5ccfec3166396530b58fb89d"},
|
||||
{file = "ruff-0.8.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b12c39b9448632284561cbf4191aa1b005882acbc81900ffa9f9f471c8ff7e26"},
|
||||
{file = "ruff-0.8.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:364e6674450cbac8e998f7b30639040c99d81dfb5bbc6dfad69bc7a8f916b3d1"},
|
||||
{file = "ruff-0.8.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b22346f845fec132aa39cd29acb94451d030c10874408dbf776af3aaeb53284c"},
|
||||
{file = "ruff-0.8.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b2f2f7a7e7648a2bfe6ead4e0a16745db956da0e3a231ad443d2a66a105c04fa"},
|
||||
{file = "ruff-0.8.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:adf314fc458374c25c5c4a4a9270c3e8a6a807b1bec018cfa2813d6546215540"},
|
||||
{file = "ruff-0.8.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a885d68342a231b5ba4d30b8c6e1b1ee3a65cf37e3d29b3c74069cdf1ee1e3c9"},
|
||||
{file = "ruff-0.8.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d2c16e3508c8cc73e96aa5127d0df8913d2290098f776416a4b157657bee44c5"},
|
||||
{file = "ruff-0.8.1-py3-none-win32.whl", hash = "sha256:93335cd7c0eaedb44882d75a7acb7df4b77cd7cd0d2255c93b28791716e81790"},
|
||||
{file = "ruff-0.8.1-py3-none-win_amd64.whl", hash = "sha256:2954cdbe8dfd8ab359d4a30cd971b589d335a44d444b6ca2cb3d1da21b75e4b6"},
|
||||
{file = "ruff-0.8.1-py3-none-win_arm64.whl", hash = "sha256:55873cc1a473e5ac129d15eccb3c008c096b94809d693fc7053f588b67822737"},
|
||||
{file = "ruff-0.8.1.tar.gz", hash = "sha256:3583db9a6450364ed5ca3f3b4225958b24f78178908d5c4bc0f46251ccca898f"},
|
||||
{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"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2204,6 +2143,20 @@ files = [
|
|||
{file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-requests"
|
||||
version = "2.32.0.20241016"
|
||||
description = "Typing stubs for requests"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "types-requests-2.32.0.20241016.tar.gz", hash = "sha256:0d9cad2f27515d0e3e3da7134a1b6f28fb97129d86b867f24d9c726452634d95"},
|
||||
{file = "types_requests-2.32.0.20241016-py3-none-any.whl", hash = "sha256:4195d62d6d3e043a4eaaf08ff8a62184584d2e8684e9d2aa178c7915a7da3747"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
urllib3 = ">=2"
|
||||
|
||||
[[package]]
|
||||
name = "types-toml"
|
||||
version = "0.10.8.20240310"
|
||||
|
@ -2414,4 +2367,4 @@ type = ["pytest-mypy"]
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "d5fc77baf896f8ff41baa174c20f9070bb9dba6b38ba47c64f667d47272eae76"
|
||||
content-hash = "f614f512e5cfcf28eb72369010b689568f2736563e498bd6cf252292eb8bf08c"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "kanidm"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
description = "Kanidm client library"
|
||||
license = "MPL-2.0"
|
||||
|
||||
|
@ -27,25 +27,22 @@ pydantic = ">=2.0.0,<3.0.0"
|
|||
aiohttp = "^3.8.1"
|
||||
Authlib = "^1.2.0"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
mypy = "^1.13"
|
||||
pytest = "^8.3.4"
|
||||
types-toml = "^0.10.8"
|
||||
pylint-pydantic = "^0.3.2"
|
||||
coverage = "^7.6.8"
|
||||
pylint-pytest = "^1.1.7"
|
||||
pytest-asyncio = "^0.24.0"
|
||||
pytest-mock = "^3.14.0"
|
||||
pytest-aiohttp = "^1.0.5"
|
||||
black = "^24.10.0"
|
||||
mkdocs = "^1.5.3"
|
||||
mkdocs-material = "^9.5.47"
|
||||
mkdocstrings = "^0.27.0"
|
||||
mkdocstrings-python = "^1.12.2"
|
||||
pook = "^2.1.2"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
ruff = ">=0.5.1,<0.8.2"
|
||||
ruff = ">=0.5.1,<0.9.6"
|
||||
pytest = "^8.3.4"
|
||||
mypy = "^1.14.1"
|
||||
types-requests = "^2.32.0.20241016"
|
||||
pytest-aiohttp = "^1.1.0"
|
||||
pytest-mock = "^3.14.0"
|
||||
types-toml = "^0.10.8.20240310"
|
||||
pylint-pydantic = "^0.3.5"
|
||||
coverage = "^7.6.10"
|
||||
mkdocs = "^1.6.1"
|
||||
mkdocs-material = "^9.6.1"
|
||||
mkdocstrings = ">=0.27,<0.29"
|
||||
mkdocstrings-python = "^1.13.0"
|
||||
pook = "^2.1.3"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
|
|
|
@ -63,7 +63,7 @@ RUN python3 -m pip install \
|
|||
|
||||
COPY rlm_python/radius_entrypoint.py /radius_entrypoint.py
|
||||
|
||||
|
||||
RUN mkdir /data && chown radiusd /data
|
||||
RUN chmod a+r /etc/raddb/certs/ -R
|
||||
USER $RADIUS_USER
|
||||
|
||||
|
|
|
@ -75,23 +75,21 @@ eap {
|
|||
#
|
||||
# EAP-pwd -- secure password-based authentication
|
||||
#
|
||||
# pwd {
|
||||
# group = 19
|
||||
#pwd {
|
||||
# group = 19
|
||||
# server_id = theserver@example.com
|
||||
#
|
||||
# This has the same meaning as for TLS.
|
||||
# fragment_size = 1020
|
||||
|
||||
#
|
||||
# server_id = theserver@example.com
|
||||
|
||||
# This has the same meaning as for TLS.
|
||||
# fragment_size = 1020
|
||||
|
||||
# The virtual server which determines the
|
||||
# "known good" password for the user.
|
||||
# Note that unlike TLS, only the "authorize"
|
||||
# section is processed. EAP-PWD requests can be
|
||||
# distinguished by having a User-Name, but
|
||||
# no User-Password, CHAP-Password, EAP-Message, etc.
|
||||
# virtual_server = "inner-tunnel"
|
||||
# }
|
||||
# The virtual server which determines the
|
||||
# "known good" password for the user.
|
||||
# Note that unlike TLS, only the "authorize"
|
||||
# section is processed. EAP-PWD requests can be
|
||||
# distinguished by having a User-Name, but
|
||||
# no User-Password, CHAP-Password, EAP-Message, etc.
|
||||
# virtual_server = "inner-tunnel"
|
||||
# }
|
||||
|
||||
# Cisco LEAP
|
||||
#
|
||||
|
@ -236,7 +234,7 @@ eap {
|
|||
#
|
||||
# openssl dhparam -out certs/dh 2048
|
||||
#
|
||||
dh_file = ${certdir}/dh.pem
|
||||
#dh_file = ${certdir}/dh.pem
|
||||
|
||||
#
|
||||
# If your system doesn't have /dev/urandom,
|
||||
|
|
|
@ -10,6 +10,7 @@ import sys
|
|||
from typing import Any
|
||||
|
||||
# import toml
|
||||
import kanidm.radius
|
||||
from kanidm.types import KanidmClientConfig
|
||||
from kanidm.utils import load_config
|
||||
|
||||
|
@ -17,8 +18,6 @@ DEBUG = True
|
|||
if os.environ.get('DEBUG', False):
|
||||
DEBUG = True
|
||||
|
||||
CONFIG_FILE_PATH = "/data/kanidm"
|
||||
|
||||
CERT_SERVER_DEST = "/etc/raddb/certs/server.pem"
|
||||
CERT_CA_DEST = "/etc/raddb/certs/ca.pem"
|
||||
CERT_CA_DIR = "/etc/raddb/certs/"
|
||||
|
@ -59,7 +58,11 @@ def setup_certs(
|
|||
sys.exit(1)
|
||||
if cert_ca != CERT_CA_DEST:
|
||||
print(f"Copying {cert_ca} to {CERT_CA_DEST}")
|
||||
shutil.copyfile(cert_ca, CERT_CA_DEST)
|
||||
try:
|
||||
shutil.copyfile(cert_ca, CERT_CA_DEST)
|
||||
except shutil.SameFileError:
|
||||
pass
|
||||
|
||||
|
||||
# This dir can also contain crls!
|
||||
if kanidm_config_object.radius_ca_dir:
|
||||
|
@ -75,19 +78,6 @@ def setup_certs(
|
|||
# not hashed as a ca.
|
||||
subprocess.check_call(["openssl", "rehash", CERT_CA_DIR])
|
||||
|
||||
# let's put some dhparams in place
|
||||
if kanidm_config_object.radius_dh_path is not None:
|
||||
cert_dh = Path(kanidm_config_object.radius_dh_path).expanduser().resolve()
|
||||
if not cert_dh.exists():
|
||||
# print(f"Failed to find radiusd dh file ({cert_dh}), quitting!", file=sys.stderr)
|
||||
# sys.exit(1)
|
||||
print(f"Generating dh params in {cert_dh}")
|
||||
subprocess.check_call(["openssl", "dhparam", "-out", cert_dh, "2048"])
|
||||
if cert_dh != CERT_DH_DEST:
|
||||
print(f"Copying {cert_dh} to {CERT_DH_DEST}")
|
||||
shutil.copyfile(cert_dh, CERT_DH_DEST)
|
||||
|
||||
|
||||
server_key = Path(kanidm_config_object.radius_key_path).expanduser().resolve()
|
||||
if not server_key.exists() or not server_key.is_file():
|
||||
print(
|
||||
|
@ -157,15 +147,15 @@ def run_radiusd() -> None:
|
|||
if __name__ == '__main__':
|
||||
signal.signal(signal.SIGCHLD, _sigchild_handler)
|
||||
|
||||
config_file = Path(CONFIG_FILE_PATH).expanduser().resolve()
|
||||
if not config_file.exists:
|
||||
config_file = kanidm.radius.find_radius_config_path()
|
||||
if config_file is None:
|
||||
print(
|
||||
"Failed to find configuration file ({config_file}), quitting!",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
kanidm_config = KanidmClientConfig.parse_obj(load_config(CONFIG_FILE_PATH))
|
||||
kanidm_config = KanidmClientConfig.model_validate(load_config(config_file))
|
||||
setup_certs(kanidm_config)
|
||||
write_clients_conf(kanidm_config)
|
||||
print("Configuration set up, starting...")
|
||||
|
|
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
[toolchain]
|
||||
channel = "stable"
|
|
@ -18,6 +18,7 @@ sudo apt-get install -y \
|
|||
jq \
|
||||
libpam0g-dev \
|
||||
libssl-dev \
|
||||
libsystemd-dev \
|
||||
libudev-dev \
|
||||
pkg-config \
|
||||
ripgrep
|
||||
|
@ -43,4 +44,4 @@ cargo install mdbook-alerts --version 0.6.4
|
|||
cargo install deno --locked
|
||||
|
||||
|
||||
echo "Done!"
|
||||
echo "Done!"
|
||||
|
|
|
@ -13,6 +13,7 @@ ${SUDOCMD} apt-get update &&
|
|||
libpam0g-dev \
|
||||
libudev-dev \
|
||||
libssl-dev \
|
||||
libsystemd-dev \
|
||||
pkg-config \
|
||||
curl \
|
||||
rsync \
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
if [ "$(rustup default | grep -cE '^nightly' )" -eq 0 ]; then
|
||||
echo "You need to switch to rust nightly!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# if [ "$(which rustfilt | wc -l )" -eq 0 ]; then
|
||||
# echo "You need to have rustfilt on the path"
|
||||
# echo "cargo install rustfilt"
|
||||
# exit 1
|
||||
# fi
|
||||
if [ "$(which llvm-cov | wc -l )" -eq 0 ]; then
|
||||
echo "You need to have llvm-cov on the path"
|
||||
exit 1
|
||||
fi
|
||||
export CARGO_INCREMENTAL=0
|
||||
|
||||
|
||||
export LLVM_PROFILE_FILE
|
||||
echo "Profile files going into ${LLVM_PROFILE_FILE}"
|
||||
|
||||
echo "Running tests"
|
||||
#shellcheck disable=SC2068
|
||||
|
||||
LLVM_PROFILE_FILE="$(pwd)/target/profile/coverage-%p-%m.profraw" RUSTFLAGS="-C instrument-coverage" cargo test
|
||||
|
||||
grcov . --binary-path ./target/debug/deps/ \
|
||||
-s . \
|
||||
-t html \
|
||||
--branch \
|
||||
--ignore-not-existing \
|
||||
--ignore '../*' \
|
||||
--ignore "/*" \
|
||||
-o target/coverage/html
|
||||
|
||||
|
||||
# PROFDATA="./target/profile/kanidm.profdata"
|
||||
|
||||
# llvm-profdata merge ./target/profile/*.profraw -o "${PROFDATA}"
|
||||
|
||||
# llvm-cov report --ignore-filename-regex="\.cargo" \
|
||||
# --enable-name-compression \
|
||||
# $( \
|
||||
# for file in \
|
||||
# $( \
|
||||
# RUSTFLAGS="-C instrument-coverage" \
|
||||
# cargo test --tests --no-run --message-format=json \
|
||||
# | jq -r "select(.profile.test == true) | .filenames[]" \
|
||||
# | grep -v dSYM - \
|
||||
# ); \
|
||||
# do \
|
||||
# printf "%s %s " -object $file; \
|
||||
# done \
|
||||
# ) \
|
||||
# --instr-profile="${PROFDATA}" --summary-only
|
||||
|
||||
# llvm-cov show -Xdemangler=rustfilt target/debug/kanidmd \
|
||||
# -instr-profile="${PROFDATA}" \
|
||||
# -show-line-counts-or-regions \
|
||||
# -show-instantiations \
|
||||
# -name-regex="kani.*"
|
|
@ -29,6 +29,7 @@ RUN \
|
|||
libopenssl-3-devel \
|
||||
pam-devel \
|
||||
sqlite3-devel \
|
||||
systemd-devel \
|
||||
rsync \
|
||||
findutils \
|
||||
which \
|
||||
|
|
|
@ -25,7 +25,7 @@ askama_axum = { workspace = true }
|
|||
axum = { workspace = true }
|
||||
axum-htmx = { workspace = true }
|
||||
axum-extra = { version = "0.9.6", features = ["cookie"] }
|
||||
axum-macros = "0.4.1"
|
||||
axum-macros = "0.4.2"
|
||||
axum-server = { version = "0.7.1", default-features = false }
|
||||
bytes = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
|
@ -45,19 +45,20 @@ ldap3_proto = { workspace = true }
|
|||
libc = { workspace = true }
|
||||
openssl = { workspace = true }
|
||||
opentelemetry = { workspace = true, features = ["logs"] }
|
||||
# opentelemetry_api = { workspace = true, features = ["logs"] }
|
||||
qrcode = { workspace = true, features = ["svg"] }
|
||||
regex = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
serde_with = { workspace = true }
|
||||
sketching = { workspace = true }
|
||||
sshkeys = { workspace = true }
|
||||
sshkey-attest = { workspace = true }
|
||||
time = { workspace = true, features = ["serde", "std", "local-offset"] }
|
||||
tokio = { workspace = true, features = ["net", "sync", "io-util", "macros"] }
|
||||
tokio-openssl = { workspace = true }
|
||||
tokio-util = { workspace = true, features = ["codec"] }
|
||||
toml = { workspace = true }
|
||||
tower = { version = "0.5.1", features = ["tokio-stream", "tracing"] }
|
||||
tower = { version = "0.5.2", features = ["tokio-stream", "tracing"] }
|
||||
tower-http = { version = "0.6.2", features = [
|
||||
"compression-gzip",
|
||||
"fs",
|
||||
|
@ -88,6 +89,14 @@ webauthn-rs = { workspace = true, features = [
|
|||
[dev-dependencies]
|
||||
walkdir = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
kanidmd_lib = { workspace = true, features = ["test"] }
|
||||
|
||||
[build-dependencies]
|
||||
kanidm_build_profiles = { workspace = true }
|
||||
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = [
|
||||
"opentelemetry", # feature gated
|
||||
"kanidm_build_profiles",
|
||||
]
|
||||
|
|
16
server/core/eslint.config.mjs
Normal file
16
server/core/eslint.config.mjs
Normal file
|
@ -0,0 +1,16 @@
|
|||
import globals from "globals";
|
||||
import pluginJs from "@eslint/js";
|
||||
|
||||
|
||||
/** @type {import('eslint').Linter.Config[]} */
|
||||
export default [
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
Base64 : "writeable" // to feed the Base64 class into the global scope
|
||||
}
|
||||
}
|
||||
},
|
||||
pluginJs.configs.recommended,
|
||||
];
|
1104
server/core/package-lock.json
generated
Normal file
1104
server/core/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
27
server/core/package.json
Normal file
27
server/core/package.json
Normal file
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"name": "kanidm",
|
||||
"version": "0.0.1-dev",
|
||||
"description": "Kanidm UI Javascript - not a publishable package, just for development",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/kanidm/kanidm.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MPL-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/kanidm/kanidm/issues"
|
||||
},
|
||||
"homepage": "https://kanidm.com",
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.17.0",
|
||||
"eslint": "^9.17.0",
|
||||
"globals": "^15.14.0",
|
||||
"prettier": "^3.4.2"
|
||||
},
|
||||
"scripts" : {
|
||||
"prettier": "npx prettier --ignore-path 'prettier-ignore' ./static --check",
|
||||
"prettier:fix": "npx prettier --ignore-path 'prettier-ignore' ./static --write"
|
||||
}
|
||||
|
||||
}
|
1
server/core/prettier-ignore
Normal file
1
server/core/prettier-ignore
Normal file
|
@ -0,0 +1 @@
|
|||
static/external/
|
|
@ -9,6 +9,7 @@ use kanidm_proto::internal::{
|
|||
IdentifyUserRequest, IdentifyUserResponse, ImageValue, OperationError, RadiusAuthToken,
|
||||
SearchRequest, SearchResponse, UserAuthToken,
|
||||
};
|
||||
use kanidm_proto::oauth2::OidcWebfingerResponse;
|
||||
use kanidm_proto::v1::{
|
||||
AuthIssueSession, AuthRequest, Entry as ProtoEntry, UatStatus, UnixGroupToken, UnixUserToken,
|
||||
WhoamiResponse,
|
||||
|
@ -37,7 +38,7 @@ use kanidmd_lib::{
|
|||
idm::ldap::{LdapBoundToken, LdapResponseState},
|
||||
idm::oauth2::{
|
||||
AccessTokenIntrospectRequest, AccessTokenIntrospectResponse, AuthorisationRequest,
|
||||
AuthoriseResponse, JwkKeySet, Oauth2Error, Oauth2Rfc8414MetadataResponse,
|
||||
AuthoriseReject, AuthoriseResponse, JwkKeySet, Oauth2Error, Oauth2Rfc8414MetadataResponse,
|
||||
OidcDiscoveryResponse, OidcToken,
|
||||
},
|
||||
idm::server::{DomainInfoRead, IdmServerTransaction},
|
||||
|
@ -1441,7 +1442,7 @@ impl QueryServerReadV1 {
|
|||
client_auth_info: ClientAuthInfo,
|
||||
consent_req: String,
|
||||
eventid: Uuid,
|
||||
) -> Result<Url, OperationError> {
|
||||
) -> Result<AuthoriseReject, OperationError> {
|
||||
let ct = duration_from_epoch_now();
|
||||
let mut idms_prox_read = self.idms.proxy_read().await?;
|
||||
let ident = idms_prox_read
|
||||
|
@ -1509,6 +1510,21 @@ impl QueryServerReadV1 {
|
|||
idms_prox_read.oauth2_openid_discovery(&client_id)
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
level = "info",
|
||||
skip_all,
|
||||
fields(uuid = ?eventid)
|
||||
)]
|
||||
pub async fn handle_oauth2_webfinger_discovery(
|
||||
&self,
|
||||
client_id: &str,
|
||||
resource_id: &str,
|
||||
eventid: Uuid,
|
||||
) -> Result<OidcWebfingerResponse, OperationError> {
|
||||
let mut idms_prox_read = self.idms.proxy_read().await?;
|
||||
idms_prox_read.oauth2_openid_webfinger(client_id, resource_id)
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
level = "info",
|
||||
skip_all,
|
||||
|
|
|
@ -250,31 +250,28 @@ impl ServerConfig {
|
|||
/// Updates the ServerConfig from environment variables starting with `KANIDM_`
|
||||
fn try_from_env(mut self) -> Result<Self, String> {
|
||||
for (key, value) in std::env::vars() {
|
||||
if !key.starts_with("KANIDM_") {
|
||||
let Some(key) = key.strip_prefix("KANIDM_") else {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let ignorable_build_fields = [
|
||||
"KANIDM_CPU_FLAGS",
|
||||
"KANIDM_CPU_FLAGS",
|
||||
"KANIDM_DEFAULT_CONFIG_PATH",
|
||||
"KANIDM_DEFAULT_CONFIG_PATH",
|
||||
"KANIDM_DEFAULT_UNIX_SHELL_PATH",
|
||||
"KANIDM_DEFAULT_UNIX_SHELL_PATH",
|
||||
"KANIDM_HTMX_UI_PKG_PATH",
|
||||
"KANIDM_PKG_VERSION_HASH",
|
||||
"KANIDM_PKG_VERSION",
|
||||
"KANIDM_PRE_RELEASE",
|
||||
"KANIDM_PROFILE_NAME",
|
||||
"CPU_FLAGS",
|
||||
"DEFAULT_CONFIG_PATH",
|
||||
"DEFAULT_UNIX_SHELL_PATH",
|
||||
"HTMX_UI_PKG_PATH",
|
||||
"PKG_VERSION",
|
||||
"PKG_VERSION_HASH",
|
||||
"PRE_RELEASE",
|
||||
"PROFILE_NAME",
|
||||
];
|
||||
|
||||
if ignorable_build_fields.contains(&key.as_str()) {
|
||||
if ignorable_build_fields.contains(&key) {
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
eprintln!("-- Ignoring build-time env var {}", key);
|
||||
eprintln!("-- Ignoring build-time env var KANIDM_{key}");
|
||||
continue;
|
||||
}
|
||||
|
||||
match key.replace("KANIDM_", "").as_str() {
|
||||
match key {
|
||||
"DOMAIN" => {
|
||||
self.domain = Some(value.to_string());
|
||||
}
|
||||
|
@ -414,7 +411,7 @@ impl ServerConfig {
|
|||
self.otel_grpc_url = Some(value.to_string());
|
||||
}
|
||||
|
||||
_ => eprintln!("Ignoring env var {}", key),
|
||||
_ => eprintln!("Ignoring env var KANIDM_{key}"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -574,7 +571,7 @@ impl Configuration {
|
|||
Configuration {
|
||||
address: DEFAULT_SERVER_ADDRESS.to_string(),
|
||||
ldapaddress: None,
|
||||
adminbindpath: env!("KANIDM_ADMIN_BIND_PATH").to_string(),
|
||||
adminbindpath: env!("KANIDM_SERVER_ADMIN_BIND_PATH").to_string(),
|
||||
threads: std::thread::available_parallelism()
|
||||
.map(|t| t.get())
|
||||
.unwrap_or_else(|_e| {
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use axum::extract::State;
|
||||
use axum::http::header::CONTENT_TYPE;
|
||||
use axum::response::IntoResponse;
|
||||
use axum::response::{IntoResponse, Redirect};
|
||||
use axum::{Extension, Json};
|
||||
use kanidmd_lib::status::StatusRequestEvent;
|
||||
|
||||
use super::middleware::KOpId;
|
||||
use super::views::constants::Urls;
|
||||
use super::ServerState;
|
||||
|
||||
#[utoipa::path(
|
||||
|
@ -50,3 +51,15 @@ pub async fn robots_txt() -> impl IntoResponse {
|
|||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = Urls::WellKnownChangePassword.as_ref(),
|
||||
responses(
|
||||
(status = 303, description = "See other"),
|
||||
),
|
||||
tag = "ui",
|
||||
)]
|
||||
pub async fn redirect_to_update_credentials() -> impl IntoResponse {
|
||||
Redirect::to(Urls::UpdateCredentials.as_ref())
|
||||
}
|
||||
|
|
|
@ -53,6 +53,7 @@ use tokio::{
|
|||
use tokio_openssl::SslStream;
|
||||
use tower::Service;
|
||||
use tower_http::{services::ServeDir, trace::TraceLayer};
|
||||
use url::Url;
|
||||
use uuid::Uuid;
|
||||
|
||||
use std::io::ErrorKind;
|
||||
|
@ -62,16 +63,17 @@ use std::{net::SocketAddr, str::FromStr};
|
|||
|
||||
#[derive(Clone)]
|
||||
pub struct ServerState {
|
||||
pub status_ref: &'static StatusActor,
|
||||
pub qe_w_ref: &'static QueryServerWriteV1,
|
||||
pub qe_r_ref: &'static QueryServerReadV1,
|
||||
pub(crate) status_ref: &'static StatusActor,
|
||||
pub(crate) qe_w_ref: &'static QueryServerWriteV1,
|
||||
pub(crate) qe_r_ref: &'static QueryServerReadV1,
|
||||
// Store the token management parts.
|
||||
pub jws_signer: JwsHs256Signer,
|
||||
pub(crate) jws_signer: JwsHs256Signer,
|
||||
pub(crate) trust_x_forward_for: bool,
|
||||
pub csp_header: HeaderValue,
|
||||
pub domain: String,
|
||||
pub(crate) csp_header: HeaderValue,
|
||||
pub(crate) origin: Url,
|
||||
pub(crate) domain: String,
|
||||
// This is set to true by default, and is only false on integration tests.
|
||||
pub secure_cookies: bool,
|
||||
pub(crate) secure_cookies: bool,
|
||||
}
|
||||
|
||||
impl ServerState {
|
||||
|
@ -129,7 +131,7 @@ pub(crate) fn get_js_files(role: ServerRole) -> Result<Vec<JavaScriptFile>, ()>
|
|||
|
||||
if !matches!(role, ServerRole::WriteReplicaNoUI) {
|
||||
// let's set up the list of js module hashes
|
||||
let pkg_path = env!("KANIDM_HTMX_UI_PKG_PATH").to_owned();
|
||||
let pkg_path = env!("KANIDM_SERVER_UI_PKG_PATH").to_owned();
|
||||
|
||||
let filelist = [
|
||||
"external/bootstrap.bundle.min.js",
|
||||
|
@ -138,11 +140,13 @@ pub(crate) fn get_js_files(role: ServerRole) -> Result<Vec<JavaScriptFile>, ()>
|
|||
"external/base64.js",
|
||||
"modules/cred_update.mjs",
|
||||
"pkhtml.js",
|
||||
"style.js",
|
||||
];
|
||||
|
||||
for filepath in filelist {
|
||||
match generate_integrity_hash(format!("{}/{}", pkg_path, filepath,)) {
|
||||
Ok(hash) => {
|
||||
debug!("Integrity hash for {}: {}", filepath, hash);
|
||||
let js = JavaScriptFile { hash };
|
||||
all_pages.push(js)
|
||||
}
|
||||
|
@ -209,6 +213,12 @@ pub async fn create_https_server(
|
|||
|
||||
let trust_x_forward_for = config.trust_x_forward_for;
|
||||
|
||||
let origin = Url::parse(&config.origin)
|
||||
// Should be impossible!
|
||||
.map_err(|err| {
|
||||
error!(?err, "Unable to parse origin URL - refusing to start. You must correct the value for origin. {:?}", config.origin);
|
||||
})?;
|
||||
|
||||
let state = ServerState {
|
||||
status_ref,
|
||||
qe_w_ref,
|
||||
|
@ -216,6 +226,7 @@ pub async fn create_https_server(
|
|||
jws_signer,
|
||||
trust_x_forward_for,
|
||||
csp_header,
|
||||
origin,
|
||||
domain: config.domain.clone(),
|
||||
secure_cookies: config.integration_test_config.is_none(),
|
||||
};
|
||||
|
@ -240,16 +251,20 @@ pub async fn create_https_server(
|
|||
.merge(oauth2::route_setup(state.clone()))
|
||||
.merge(v1_scim::route_setup())
|
||||
.merge(v1::route_setup(state.clone()))
|
||||
.route("/robots.txt", get(generic::robots_txt));
|
||||
.route("/robots.txt", get(generic::robots_txt))
|
||||
.route(
|
||||
views::constants::Urls::WellKnownChangePassword.as_ref(),
|
||||
get(generic::redirect_to_update_credentials),
|
||||
);
|
||||
|
||||
let app = match config.role {
|
||||
ServerRole::WriteReplicaNoUI => app,
|
||||
ServerRole::WriteReplica | ServerRole::ReadOnlyReplica => {
|
||||
let pkg_path = PathBuf::from(env!("KANIDM_HTMX_UI_PKG_PATH"));
|
||||
let pkg_path = PathBuf::from(env!("KANIDM_SERVER_UI_PKG_PATH"));
|
||||
if !pkg_path.exists() {
|
||||
eprintln!(
|
||||
"Couldn't find htmx UI package path: ({}), quitting.",
|
||||
env!("KANIDM_HTMX_UI_PKG_PATH")
|
||||
env!("KANIDM_SERVER_UI_PKG_PATH")
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@ use axum::{
|
|||
Extension, Form, Json, Router,
|
||||
};
|
||||
use axum_macros::debug_handler;
|
||||
use compact_jwt::{JwkKeySet, OidcToken};
|
||||
use kanidm_proto::constants::uri::{
|
||||
OAUTH2_AUTHORISE, OAUTH2_AUTHORISE_PERMIT, OAUTH2_AUTHORISE_REJECT,
|
||||
};
|
||||
|
@ -30,8 +29,8 @@ use kanidm_proto::oauth2::AuthorisationResponse;
|
|||
#[cfg(feature = "dev-oauth2-device-flow")]
|
||||
use kanidm_proto::oauth2::DeviceAuthorizationResponse;
|
||||
use kanidmd_lib::idm::oauth2::{
|
||||
AccessTokenIntrospectRequest, AccessTokenRequest, AuthorisationRequest, AuthorisePermitSuccess,
|
||||
AuthoriseResponse, ErrorResponse, Oauth2Error, TokenRevokeRequest,
|
||||
AccessTokenIntrospectRequest, AccessTokenRequest, AuthorisationRequest, AuthoriseResponse,
|
||||
ErrorResponse, Oauth2Error, TokenRevokeRequest,
|
||||
};
|
||||
use kanidmd_lib::prelude::f_eq;
|
||||
use kanidmd_lib::prelude::*;
|
||||
|
@ -258,22 +257,14 @@ async fn oauth2_authorise(
|
|||
.body(body.into())
|
||||
.unwrap()
|
||||
}
|
||||
Ok(AuthoriseResponse::Permitted(AuthorisePermitSuccess {
|
||||
mut redirect_uri,
|
||||
state,
|
||||
code,
|
||||
})) => {
|
||||
Ok(AuthoriseResponse::Permitted(success)) => {
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-4.11
|
||||
// We could consider changing this to 303?
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let body =
|
||||
Body::from(serde_json::to_string(&AuthorisationResponse::Permitted).unwrap());
|
||||
let redirect_uri = success.build_redirect_uri();
|
||||
|
||||
redirect_uri
|
||||
.query_pairs_mut()
|
||||
.clear()
|
||||
.append_pair("state", &state)
|
||||
.append_pair("code", &code);
|
||||
#[allow(clippy::unwrap_used)]
|
||||
Response::builder()
|
||||
.status(StatusCode::FOUND)
|
||||
|
@ -378,18 +369,11 @@ async fn oauth2_authorise_permit(
|
|||
.await;
|
||||
|
||||
match res {
|
||||
Ok(AuthorisePermitSuccess {
|
||||
mut redirect_uri,
|
||||
state,
|
||||
code,
|
||||
}) => {
|
||||
Ok(success) => {
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-4.11
|
||||
// We could consider changing this to 303?
|
||||
redirect_uri
|
||||
.query_pairs_mut()
|
||||
.clear()
|
||||
.append_pair("state", &state)
|
||||
.append_pair("code", &code);
|
||||
let redirect_uri = success.build_redirect_uri();
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
Response::builder()
|
||||
.status(StatusCode::FOUND)
|
||||
|
@ -464,12 +448,9 @@ async fn oauth2_authorise_reject(
|
|||
.await;
|
||||
|
||||
match res {
|
||||
Ok(mut redirect_uri) => {
|
||||
redirect_uri
|
||||
.query_pairs_mut()
|
||||
.clear()
|
||||
.append_pair("error", "access_denied")
|
||||
.append_pair("error_description", "authorisation rejected");
|
||||
Ok(reject) => {
|
||||
let redirect_uri = reject.build_redirect_uri();
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
Response::builder()
|
||||
.header(LOCATION, redirect_uri.as_str())
|
||||
|
@ -532,7 +513,7 @@ pub async fn oauth2_token_post(
|
|||
}
|
||||
}
|
||||
|
||||
// // For future openid integration
|
||||
// For future openid integration
|
||||
pub async fn oauth2_openid_discovery_get(
|
||||
State(state): State<ServerState>,
|
||||
Path(client_id): Path<String>,
|
||||
|
@ -557,6 +538,46 @@ pub async fn oauth2_openid_discovery_get(
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Oauth2OpenIdWebfingerQuery {
|
||||
resource: String,
|
||||
}
|
||||
|
||||
pub async fn oauth2_openid_webfinger_get(
|
||||
State(state): State<ServerState>,
|
||||
Path(client_id): Path<String>,
|
||||
Query(query): Query<Oauth2OpenIdWebfingerQuery>,
|
||||
Extension(kopid): Extension<KOpId>,
|
||||
) -> impl IntoResponse {
|
||||
let Oauth2OpenIdWebfingerQuery { resource } = query;
|
||||
|
||||
let cleaned_resource = resource.strip_prefix("acct:").unwrap_or(&resource);
|
||||
|
||||
let res = state
|
||||
.qe_r_ref
|
||||
.handle_oauth2_webfinger_discovery(&client_id, cleaned_resource, kopid.eventid)
|
||||
.await;
|
||||
|
||||
match res {
|
||||
Ok(mut dsc) => (
|
||||
StatusCode::OK,
|
||||
[
|
||||
(ACCESS_CONTROL_ALLOW_ORIGIN, "*"),
|
||||
(CONTENT_TYPE, "application/jrd+json"),
|
||||
],
|
||||
Json({
|
||||
dsc.subject = resource;
|
||||
dsc
|
||||
}),
|
||||
)
|
||||
.into_response(),
|
||||
Err(e) => {
|
||||
error!(err = ?e, "Unable to access discovery info");
|
||||
WebError::from(e).response_with_access_control_origin_header()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn oauth2_rfc8414_metadata_get(
|
||||
State(state): State<ServerState>,
|
||||
Path(client_id): Path<String>,
|
||||
|
@ -587,13 +608,13 @@ pub async fn oauth2_openid_userinfo_get(
|
|||
Path(client_id): Path<String>,
|
||||
Extension(kopid): Extension<KOpId>,
|
||||
VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
|
||||
) -> Result<Json<OidcToken>, HTTPOauth2Error> {
|
||||
) -> Response {
|
||||
// The token we want to inspect is in the authorisation header.
|
||||
let client_token = match client_auth_info.bearer_token {
|
||||
Some(val) => val,
|
||||
None => {
|
||||
error!("Bearer Authentication Not Provided");
|
||||
return Err(HTTPOauth2Error(Oauth2Error::AuthenticationRequired));
|
||||
return HTTPOauth2Error(Oauth2Error::AuthenticationRequired).into_response();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -603,8 +624,13 @@ pub async fn oauth2_openid_userinfo_get(
|
|||
.await;
|
||||
|
||||
match res {
|
||||
Ok(uir) => Ok(Json(uir)),
|
||||
Err(e) => Err(HTTPOauth2Error(e)),
|
||||
Ok(uir) => (
|
||||
StatusCode::OK,
|
||||
[(ACCESS_CONTROL_ALLOW_ORIGIN, "*")],
|
||||
Json(uir),
|
||||
)
|
||||
.into_response(),
|
||||
Err(e) => HTTPOauth2Error(e).into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -612,13 +638,18 @@ pub async fn oauth2_openid_publickey_get(
|
|||
State(state): State<ServerState>,
|
||||
Path(client_id): Path<String>,
|
||||
Extension(kopid): Extension<KOpId>,
|
||||
) -> Result<Json<JwkKeySet>, WebError> {
|
||||
state
|
||||
) -> Response {
|
||||
let res = state
|
||||
.qe_r_ref
|
||||
.handle_oauth2_openid_publickey(client_id, kopid.eventid)
|
||||
.await
|
||||
.map(Json::from)
|
||||
.map_err(WebError::from)
|
||||
.map_err(WebError::from);
|
||||
|
||||
match res {
|
||||
Ok(jsn) => (StatusCode::OK, [(ACCESS_CONTROL_ALLOW_ORIGIN, "*")], jsn).into_response(),
|
||||
Err(web_err) => web_err.response_with_access_control_origin_header(),
|
||||
}
|
||||
}
|
||||
|
||||
/// This is called directly by the resource server, where we then issue
|
||||
|
@ -779,17 +810,23 @@ pub fn route_setup(state: ServerState) -> Router<ServerState> {
|
|||
"/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",
|
||||
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",
|
||||
get(oauth2_openid_userinfo_get).options(oauth2_preflight_options),
|
||||
get(oauth2_openid_userinfo_get)
|
||||
.post(oauth2_openid_userinfo_get)
|
||||
.options(oauth2_preflight_options),
|
||||
)
|
||||
// // ⚠️ ⚠️ WARNING ⚠️ ⚠️
|
||||
// // IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
|
||||
.route(
|
||||
"/oauth2/openid/:client_id/public_key.jwk",
|
||||
get(oauth2_openid_publickey_get),
|
||||
get(oauth2_openid_publickey_get).options(oauth2_preflight_options),
|
||||
)
|
||||
// // ⚠️ ⚠️ WARNING ⚠️ ⚠️
|
||||
// // IF YOU CHANGE THESE VALUES YOU MUST UPDATE OAUTH2 DISCOVERY URLS
|
||||
|
|
|
@ -200,6 +200,7 @@ pub(crate) async fn oauth2_id_scopemap_post(
|
|||
Json(scopes): Json<Vec<String>>,
|
||||
) -> Result<Json<()>, WebError> {
|
||||
let filter = oauth2_id(&rs_name);
|
||||
|
||||
state
|
||||
.qe_w_ref
|
||||
.handle_oauth2_scopemap_update(client_auth_info, group, scopes, filter, kopid.eventid)
|
||||
|
|
|
@ -7,8 +7,8 @@ use super::v1::{
|
|||
};
|
||||
use super::ServerState;
|
||||
use crate::https::extractors::VerifiedClientInformation;
|
||||
use axum::extract::{Path, Query, State};
|
||||
use axum::response::Html;
|
||||
use axum::extract::{rejection::JsonRejection, DefaultBodyLimit, Path, Query, State};
|
||||
use axum::response::{Html, IntoResponse, Response};
|
||||
use axum::routing::{get, post};
|
||||
use axum::{Extension, Json, Router};
|
||||
use kanidm_proto::scim_v1::{
|
||||
|
@ -17,6 +17,8 @@ use kanidm_proto::scim_v1::{
|
|||
use kanidm_proto::v1::Entry as ProtoEntry;
|
||||
use kanidmd_lib::prelude::*;
|
||||
|
||||
const DEFAULT_SCIM_SYNC_BYTES: usize = 1024 * 1024 * 32;
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/v1/sync_account",
|
||||
|
@ -271,14 +273,25 @@ async fn scim_sync_post(
|
|||
State(state): State<ServerState>,
|
||||
Extension(kopid): Extension<KOpId>,
|
||||
VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
|
||||
Json(changes): Json<ScimSyncRequest>,
|
||||
) -> Result<Json<()>, WebError> {
|
||||
state
|
||||
.qe_w_ref
|
||||
.handle_scim_sync_apply(client_auth_info, changes, kopid.eventid)
|
||||
.await
|
||||
.map(Json::from)
|
||||
.map_err(WebError::from)
|
||||
payload: Result<Json<ScimSyncRequest>, JsonRejection>,
|
||||
) -> Response {
|
||||
match payload {
|
||||
Ok(Json(changes)) => {
|
||||
let res = state
|
||||
.qe_w_ref
|
||||
.handle_scim_sync_apply(client_auth_info, changes, kopid.eventid)
|
||||
.await;
|
||||
|
||||
match res {
|
||||
Ok(data) => Json::from(data).into_response(),
|
||||
Err(err) => WebError::from(err).into_response(),
|
||||
}
|
||||
}
|
||||
Err(rejection) => {
|
||||
error!(?rejection, "Unable to process JSON");
|
||||
rejection.into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
|
@ -473,6 +486,11 @@ pub fn route_setup() -> Router<ServerState> {
|
|||
//
|
||||
// POST Send a sync update
|
||||
//
|
||||
.route("/scim/v1/Sync", post(scim_sync_post).get(scim_sync_get))
|
||||
.route(
|
||||
"/scim/v1/Sync",
|
||||
post(scim_sync_post)
|
||||
.layer(DefaultBodyLimit::max(DEFAULT_SCIM_SYNC_BYTES))
|
||||
.get(scim_sync_get),
|
||||
)
|
||||
.route("/scim/v1/Sink", get(scim_sink_get)) // skip_route_check
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ pub(crate) async fn view_apps_get(
|
|||
.qe_r_ref
|
||||
.handle_list_applinks(client_auth_info, kopid.eventid)
|
||||
.await
|
||||
.map_err(|old| HtmxError::new(&kopid, old))?;
|
||||
.map_err(|old| HtmxError::new(&kopid, old, domain_info.clone()))?;
|
||||
|
||||
Ok({
|
||||
(
|
||||
|
|
|
@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
|
|||
pub(crate) enum ProfileMenuItems {
|
||||
UserProfile,
|
||||
Credentials,
|
||||
EnrolDevice,
|
||||
UnixPassword,
|
||||
}
|
||||
|
||||
|
@ -20,15 +21,16 @@ impl std::fmt::Display for UiMessage {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) enum Urls {
|
||||
Apps,
|
||||
CredReset,
|
||||
EnrolDevice,
|
||||
Profile,
|
||||
UpdateCredentials,
|
||||
Oauth2Resume,
|
||||
Login,
|
||||
Ui,
|
||||
WellKnownChangePassword,
|
||||
}
|
||||
|
||||
impl AsRef<str> for Urls {
|
||||
|
@ -36,11 +38,13 @@ impl AsRef<str> for Urls {
|
|||
match self {
|
||||
Self::Apps => "/ui/apps",
|
||||
Self::CredReset => "/ui/reset",
|
||||
Self::EnrolDevice => "/ui/enrol",
|
||||
Self::Profile => "/ui/profile",
|
||||
Self::UpdateCredentials => "/ui/update_credentials",
|
||||
Self::Oauth2Resume => "/ui/oauth2/resume",
|
||||
Self::Login => "/ui/login",
|
||||
Self::Ui => "/ui",
|
||||
Self::WellKnownChangePassword => "/.well-known/change-password",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,27 +6,7 @@ use compact_jwt::{Jws, JwsSigner};
|
|||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
|
||||
pub fn destroy(jar: CookieJar, ck_id: &str) -> CookieJar {
|
||||
if let Some(ck) = jar.get(ck_id) {
|
||||
let mut ck = ck.clone();
|
||||
ck.make_removal();
|
||||
/*
|
||||
if let Some(path) = ck.path().cloned() {
|
||||
ck.set_path(&path);
|
||||
}
|
||||
*/
|
||||
jar.add(ck)
|
||||
} else {
|
||||
jar
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_unsigned<'a>(
|
||||
state: &'_ ServerState,
|
||||
ck_id: &'a str,
|
||||
value: String,
|
||||
path: &'a str,
|
||||
) -> Cookie<'a> {
|
||||
fn new_cookie<'a>(state: &'_ ServerState, ck_id: &'a str, value: String) -> Cookie<'a> {
|
||||
let mut token_cookie = Cookie::new(ck_id, value);
|
||||
token_cookie.set_secure(state.secure_cookies);
|
||||
token_cookie.set_same_site(SameSite::Lax);
|
||||
|
@ -36,17 +16,37 @@ pub fn make_unsigned<'a>(
|
|||
// of the idm to share the cookie. If domain was incorrect
|
||||
// then webauthn won't work anyway!
|
||||
token_cookie.set_domain(state.domain.clone());
|
||||
token_cookie.set_path(path);
|
||||
// These last forever.
|
||||
token_cookie.make_permanent();
|
||||
token_cookie.set_path("/");
|
||||
token_cookie
|
||||
}
|
||||
|
||||
#[instrument(name = "views::cookies::destroy", level = "debug", skip(jar, state))]
|
||||
pub fn destroy(jar: CookieJar, ck_id: &str, state: &ServerState) -> CookieJar {
|
||||
if let Some(ck) = jar.get(ck_id) {
|
||||
let mut removal_cookie = ck.clone();
|
||||
removal_cookie.make_removal();
|
||||
|
||||
// Need to be set to domain else the cookie isn't removed!
|
||||
removal_cookie.set_domain(state.domain.clone());
|
||||
|
||||
// Need to be set to / to remove on all parent paths.
|
||||
// If you don't set a path, NOTHING IS REMOVED!!!
|
||||
removal_cookie.set_path("/");
|
||||
|
||||
jar.add(removal_cookie)
|
||||
} else {
|
||||
jar
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_unsigned<'a>(state: &'_ ServerState, ck_id: &'a str, value: String) -> Cookie<'a> {
|
||||
new_cookie(state, ck_id, value)
|
||||
}
|
||||
|
||||
pub fn make_signed<'a, T: Serialize>(
|
||||
state: &'_ ServerState,
|
||||
ck_id: &'a str,
|
||||
value: &'_ T,
|
||||
path: &'a str,
|
||||
) -> Option<Cookie<'a>> {
|
||||
let kref = &state.jws_signer;
|
||||
|
||||
|
@ -65,15 +65,7 @@ pub fn make_signed<'a, T: Serialize>(
|
|||
})
|
||||
.ok()?;
|
||||
|
||||
let mut token_cookie = Cookie::new(ck_id, token);
|
||||
token_cookie.set_secure(state.secure_cookies);
|
||||
token_cookie.set_same_site(SameSite::Lax);
|
||||
token_cookie.set_http_only(true);
|
||||
token_cookie.set_path(path);
|
||||
token_cookie.set_domain(state.domain.clone());
|
||||
// These last forever, we have our own internal expiration handling.
|
||||
token_cookie.make_permanent();
|
||||
Some(token_cookie)
|
||||
Some(new_cookie(state, ck_id, token))
|
||||
}
|
||||
|
||||
pub fn get_signed<T: DeserializeOwned>(
|
||||
|
|
116
server/core/src/https/views/enrol.rs
Normal file
116
server/core/src/https/views/enrol.rs
Normal file
|
@ -0,0 +1,116 @@
|
|||
use askama::Template;
|
||||
use askama_axum::IntoResponse;
|
||||
|
||||
use axum::extract::State;
|
||||
use axum::response::Response;
|
||||
use axum::Extension;
|
||||
|
||||
use axum_extra::extract::CookieJar;
|
||||
use kanidm_proto::internal::UserAuthToken;
|
||||
|
||||
use qrcode::render::svg;
|
||||
use qrcode::QrCode;
|
||||
use url::Url;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use super::constants::Urls;
|
||||
use super::navbar::NavbarCtx;
|
||||
use crate::https::extractors::{DomainInfo, VerifiedClientInformation};
|
||||
use crate::https::middleware::KOpId;
|
||||
use crate::https::views::constants::ProfileMenuItems;
|
||||
use crate::https::views::errors::HtmxError;
|
||||
use crate::https::views::login::{LoginDisplayCtx, Reauth, ReauthPurpose};
|
||||
use crate::https::ServerState;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "user_settings.html")]
|
||||
struct ProfileView {
|
||||
navbar_ctx: NavbarCtx,
|
||||
profile_partial: EnrolDeviceView,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "enrol_device.html")]
|
||||
pub(crate) struct EnrolDeviceView {
|
||||
menu_active_item: ProfileMenuItems,
|
||||
secret: String,
|
||||
qr_code_svg: String,
|
||||
uri: Url,
|
||||
}
|
||||
|
||||
pub(crate) async fn view_enrol_get(
|
||||
State(state): State<ServerState>,
|
||||
Extension(kopid): Extension<KOpId>,
|
||||
VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
|
||||
DomainInfo(domain_info): DomainInfo,
|
||||
jar: CookieJar,
|
||||
) -> axum::response::Result<Response> {
|
||||
let uat: UserAuthToken = state
|
||||
.qe_r_ref
|
||||
.handle_whoami_uat(client_auth_info.clone(), kopid.eventid)
|
||||
.await
|
||||
.map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))?;
|
||||
|
||||
let time = time::OffsetDateTime::now_utc() + time::Duration::new(60, 0);
|
||||
let can_rw = uat.purpose_readwrite_active(time);
|
||||
|
||||
// The user lacks an elevated session, request a re-auth.
|
||||
if !can_rw {
|
||||
let display_ctx = LoginDisplayCtx {
|
||||
domain_info,
|
||||
oauth2: None,
|
||||
reauth: Some(Reauth {
|
||||
username: uat.spn,
|
||||
purpose: ReauthPurpose::ProfileSettings,
|
||||
}),
|
||||
error: None,
|
||||
};
|
||||
|
||||
return Ok(super::login::view_reauth_get(
|
||||
state,
|
||||
client_auth_info,
|
||||
kopid,
|
||||
jar,
|
||||
Urls::EnrolDevice.as_ref(),
|
||||
display_ctx,
|
||||
)
|
||||
.await);
|
||||
}
|
||||
|
||||
let cu_intent = state
|
||||
.qe_w_ref
|
||||
.handle_idmcredentialupdateintent(
|
||||
client_auth_info,
|
||||
uat.spn,
|
||||
Some(Duration::from_secs(900)),
|
||||
kopid.eventid,
|
||||
)
|
||||
.await
|
||||
.map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))?;
|
||||
|
||||
let secret = cu_intent.token;
|
||||
|
||||
let mut uri = state.origin.clone();
|
||||
uri.set_path(Urls::CredReset.as_ref());
|
||||
uri.set_query(Some(format!("token={secret}").as_str()));
|
||||
|
||||
let qr_code_svg = match QrCode::new(uri.as_str()) {
|
||||
Ok(qr) => qr.render::<svg::Color>().build(),
|
||||
Err(qr_err) => {
|
||||
error!("Failed to create TOTP QR code: {qr_err}");
|
||||
"QR Code Generation Failed".to_string()
|
||||
}
|
||||
};
|
||||
|
||||
Ok(ProfileView {
|
||||
navbar_ctx: NavbarCtx { domain_info },
|
||||
profile_partial: EnrolDeviceView {
|
||||
menu_active_item: ProfileMenuItems::EnrolDevice,
|
||||
qr_code_svg,
|
||||
secret,
|
||||
uri,
|
||||
},
|
||||
}
|
||||
.into_response())
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
use axum::http::StatusCode;
|
||||
use axum::response::{IntoResponse, Redirect, Response};
|
||||
use axum_htmx::{HxReswap, HxRetarget, SwapOption};
|
||||
use kanidmd_lib::idm::server::DomainInfoRead;
|
||||
use utoipa::ToSchema;
|
||||
use uuid::Uuid;
|
||||
|
||||
|
@ -21,19 +22,19 @@ use crate::https::views::UnrecoverableErrorView;
|
|||
#[derive(Debug, ToSchema)]
|
||||
pub(crate) enum HtmxError {
|
||||
/// Something went wrong when doing things.
|
||||
OperationError(Uuid, OperationError),
|
||||
OperationError(Uuid, OperationError, DomainInfoRead),
|
||||
}
|
||||
|
||||
impl HtmxError {
|
||||
pub(crate) fn new(kopid: &KOpId, operr: OperationError) -> Self {
|
||||
HtmxError::OperationError(kopid.eventid, operr)
|
||||
pub(crate) fn new(kopid: &KOpId, operr: OperationError, domain_info: DomainInfoRead) -> Self {
|
||||
HtmxError::OperationError(kopid.eventid, operr, domain_info)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for HtmxError {
|
||||
fn into_response(self) -> Response {
|
||||
match self {
|
||||
HtmxError::OperationError(kopid, inner) => {
|
||||
HtmxError::OperationError(kopid, inner, domain_info) => {
|
||||
let body = serde_json::to_string(&inner).unwrap_or(inner.to_string());
|
||||
match &inner {
|
||||
OperationError::NotAuthenticated
|
||||
|
@ -58,6 +59,7 @@ impl IntoResponse for HtmxError {
|
|||
UnrecoverableErrorView {
|
||||
err_code: inner,
|
||||
operation_id: kopid,
|
||||
domain_info,
|
||||
},
|
||||
)
|
||||
.into_response(),
|
||||
|
|
|
@ -12,9 +12,10 @@ use axum::{
|
|||
response::{IntoResponse, Redirect, Response},
|
||||
Extension, Form, Json,
|
||||
};
|
||||
use axum_extra::extract::cookie::{Cookie, CookieJar, SameSite};
|
||||
use axum_extra::extract::cookie::{CookieJar, SameSite};
|
||||
use kanidm_proto::internal::{
|
||||
COOKIE_AUTH_SESSION_ID, COOKIE_BEARER_TOKEN, COOKIE_OAUTH2_REQ, COOKIE_USERNAME,
|
||||
COOKIE_AUTH_SESSION_ID, COOKIE_BEARER_TOKEN, COOKIE_CU_SESSION_TOKEN, COOKIE_OAUTH2_REQ,
|
||||
COOKIE_USERNAME,
|
||||
};
|
||||
use kanidm_proto::v1::{
|
||||
AuthAllowed, AuthCredential, AuthIssueSession, AuthMech, AuthRequest, AuthStep,
|
||||
|
@ -47,6 +48,7 @@ struct SessionContext {
|
|||
after_auth_loc: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ReauthPurpose {
|
||||
ProfileSettings,
|
||||
}
|
||||
|
@ -58,7 +60,7 @@ impl fmt::Display for ReauthPurpose {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum LoginError {
|
||||
InvalidUsername,
|
||||
}
|
||||
|
@ -70,16 +72,17 @@ impl fmt::Display for LoginError {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Reauth {
|
||||
pub username: String,
|
||||
pub purpose: ReauthPurpose,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Oauth2Ctx {
|
||||
pub client_name: String,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LoginDisplayCtx {
|
||||
pub domain_info: DomainInfoRead,
|
||||
// We only need this on the first re-auth screen to indicate what we are doing
|
||||
|
@ -159,9 +162,10 @@ pub async fn view_logout_get(
|
|||
State(state): State<ServerState>,
|
||||
VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
|
||||
Extension(kopid): Extension<KOpId>,
|
||||
DomainInfo(domain_info): DomainInfo,
|
||||
mut jar: CookieJar,
|
||||
) -> Response {
|
||||
if let Err(err_code) = state
|
||||
let response = if let Err(err_code) = state
|
||||
.qe_w_ref
|
||||
.handle_logout(client_auth_info, kopid.eventid)
|
||||
.await
|
||||
|
@ -169,15 +173,20 @@ pub async fn view_logout_get(
|
|||
UnrecoverableErrorView {
|
||||
err_code,
|
||||
operation_id: kopid.eventid,
|
||||
domain_info,
|
||||
}
|
||||
.into_response()
|
||||
} else {
|
||||
let response = Redirect::to(Urls::Login.as_ref()).into_response();
|
||||
Redirect::to(Urls::Login.as_ref()).into_response()
|
||||
};
|
||||
|
||||
jar = cookies::destroy(jar, COOKIE_BEARER_TOKEN);
|
||||
// Always clear cookies even on an error.
|
||||
jar = cookies::destroy(jar, COOKIE_BEARER_TOKEN, &state);
|
||||
jar = cookies::destroy(jar, COOKIE_OAUTH2_REQ, &state);
|
||||
jar = cookies::destroy(jar, COOKIE_AUTH_SESSION_ID, &state);
|
||||
jar = cookies::destroy(jar, COOKIE_CU_SESSION_TOKEN, &state);
|
||||
|
||||
(jar, response).into_response()
|
||||
}
|
||||
(jar, response).into_response()
|
||||
}
|
||||
|
||||
pub async fn view_reauth_get(
|
||||
|
@ -190,14 +199,7 @@ pub async fn view_reauth_get(
|
|||
) -> Response {
|
||||
// No matter what, we always clear the stored oauth2 cookie to prevent
|
||||
// ui loops
|
||||
let jar = if let Some(authreq_cookie) = jar.get(COOKIE_OAUTH2_REQ) {
|
||||
let mut authreq_cookie = authreq_cookie.clone();
|
||||
authreq_cookie.make_removal();
|
||||
authreq_cookie.set_path(Urls::Ui.as_ref());
|
||||
jar.add(authreq_cookie)
|
||||
} else {
|
||||
jar
|
||||
};
|
||||
let jar = cookies::destroy(jar, COOKIE_OAUTH2_REQ, &state);
|
||||
|
||||
let session_valid_result = state
|
||||
.qe_r_ref
|
||||
|
@ -234,7 +236,7 @@ pub async fn view_reauth_get(
|
|||
ar,
|
||||
client_auth_info,
|
||||
session_context,
|
||||
display_ctx,
|
||||
display_ctx.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
@ -243,6 +245,7 @@ pub async fn view_reauth_get(
|
|||
Err(err_code) => UnrecoverableErrorView {
|
||||
err_code,
|
||||
operation_id: kopid.eventid,
|
||||
domain_info: display_ctx.clone().domain_info,
|
||||
}
|
||||
.into_response(),
|
||||
}
|
||||
|
@ -251,6 +254,7 @@ pub async fn view_reauth_get(
|
|||
Err(err_code) => UnrecoverableErrorView {
|
||||
err_code,
|
||||
operation_id: kopid.eventid,
|
||||
domain_info: display_ctx.domain_info,
|
||||
}
|
||||
.into_response(),
|
||||
}
|
||||
|
@ -277,6 +281,7 @@ pub async fn view_reauth_get(
|
|||
Err(err_code) => UnrecoverableErrorView {
|
||||
err_code,
|
||||
operation_id: kopid.eventid,
|
||||
domain_info: display_ctx.domain_info,
|
||||
}
|
||||
.into_response(),
|
||||
}
|
||||
|
@ -324,14 +329,7 @@ pub async fn view_index_get(
|
|||
|
||||
// No matter what, we always clear the stored oauth2 cookie to prevent
|
||||
// ui loops
|
||||
let jar = if let Some(authreq_cookie) = jar.get(COOKIE_OAUTH2_REQ) {
|
||||
let mut authreq_cookie = authreq_cookie.clone();
|
||||
authreq_cookie.make_removal();
|
||||
authreq_cookie.set_path(Urls::Ui.as_ref());
|
||||
jar.add(authreq_cookie)
|
||||
} else {
|
||||
jar
|
||||
};
|
||||
let jar = cookies::destroy(jar, COOKIE_OAUTH2_REQ, &state);
|
||||
|
||||
match session_valid_result {
|
||||
Ok(()) => {
|
||||
|
@ -367,6 +365,7 @@ pub async fn view_index_get(
|
|||
Err(err_code) => UnrecoverableErrorView {
|
||||
err_code,
|
||||
operation_id: kopid.eventid,
|
||||
domain_info,
|
||||
}
|
||||
.into_response(),
|
||||
}
|
||||
|
@ -429,7 +428,7 @@ pub async fn view_login_begin_post(
|
|||
};
|
||||
|
||||
let mut display_ctx = LoginDisplayCtx {
|
||||
domain_info,
|
||||
domain_info: domain_info.clone(),
|
||||
oauth2: None,
|
||||
reauth: None,
|
||||
error: None,
|
||||
|
@ -454,6 +453,7 @@ pub async fn view_login_begin_post(
|
|||
Err(err_code) => UnrecoverableErrorView {
|
||||
err_code,
|
||||
operation_id: kopid.eventid,
|
||||
domain_info,
|
||||
}
|
||||
.into_response(),
|
||||
}
|
||||
|
@ -472,6 +472,7 @@ pub async fn view_login_begin_post(
|
|||
_ => UnrecoverableErrorView {
|
||||
err_code,
|
||||
operation_id: kopid.eventid,
|
||||
domain_info,
|
||||
}
|
||||
.into_response(),
|
||||
},
|
||||
|
@ -512,7 +513,7 @@ pub async fn view_login_mech_choose_post(
|
|||
.await;
|
||||
|
||||
let display_ctx = LoginDisplayCtx {
|
||||
domain_info,
|
||||
domain_info: domain_info.clone(),
|
||||
oauth2: None,
|
||||
reauth: None,
|
||||
error: None,
|
||||
|
@ -537,6 +538,7 @@ pub async fn view_login_mech_choose_post(
|
|||
Err(err_code) => UnrecoverableErrorView {
|
||||
err_code,
|
||||
operation_id: kopid.eventid,
|
||||
domain_info,
|
||||
}
|
||||
.into_response(),
|
||||
}
|
||||
|
@ -545,6 +547,7 @@ pub async fn view_login_mech_choose_post(
|
|||
Err(err_code) => UnrecoverableErrorView {
|
||||
err_code,
|
||||
operation_id: kopid.eventid,
|
||||
domain_info,
|
||||
}
|
||||
.into_response(),
|
||||
}
|
||||
|
@ -552,6 +555,8 @@ pub async fn view_login_mech_choose_post(
|
|||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct LoginTotpForm {
|
||||
#[serde(default, deserialize_with = "empty_string_as_none")]
|
||||
password: Option<String>,
|
||||
totp: String,
|
||||
}
|
||||
|
||||
|
@ -560,7 +565,7 @@ pub async fn view_login_totp_post(
|
|||
Extension(kopid): Extension<KOpId>,
|
||||
VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
|
||||
DomainInfo(domain_info): DomainInfo,
|
||||
jar: CookieJar,
|
||||
mut jar: CookieJar,
|
||||
Form(login_totp_form): Form<LoginTotpForm>,
|
||||
) -> Response {
|
||||
// trim leading and trailing white space.
|
||||
|
@ -583,6 +588,31 @@ pub async fn view_login_totp_post(
|
|||
}
|
||||
};
|
||||
|
||||
// In some flows the PW manager may not have autocompleted the pw until
|
||||
// this point. This could be due to a re-auth flow which skips the username
|
||||
// prompt, the use of remember-me+return which then skips the autocomplete.
|
||||
//
|
||||
// In the case the pw *is* bg filled, we need to add it to the session context
|
||||
// here.
|
||||
//
|
||||
// It's probably not "optimal" to be getting the context out and signing it
|
||||
// here to re-add it, but it also helps keep the flow neater in general.
|
||||
|
||||
if let Some(password_autofill) = login_totp_form.password {
|
||||
let mut session_context =
|
||||
cookies::get_signed::<SessionContext>(&state, &jar, COOKIE_AUTH_SESSION_ID)
|
||||
.unwrap_or_default();
|
||||
|
||||
session_context.password = Some(password_autofill);
|
||||
|
||||
// If we can't write this back to the jar, we warn and move on.
|
||||
if let Ok(update_jar) = add_session_cookie(&state, jar.clone(), &session_context) {
|
||||
jar = update_jar;
|
||||
} else {
|
||||
warn!("Unable to update session_context, ignoring...");
|
||||
}
|
||||
}
|
||||
|
||||
let auth_cred = AuthCredential::Totp(totp);
|
||||
credential_step(state, kopid, jar, client_auth_info, auth_cred, domain_info).await
|
||||
}
|
||||
|
@ -644,7 +674,7 @@ pub async fn view_login_passkey_post(
|
|||
}
|
||||
Err(e) => {
|
||||
error!(err = ?e, "Unable to deserialize credential submission");
|
||||
HtmxError::new(&kopid, OperationError::SerdeJsonError).into_response()
|
||||
HtmxError::new(&kopid, OperationError::SerdeJsonError, domain_info).into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -674,7 +704,7 @@ async fn credential_step(
|
|||
.unwrap_or_default();
|
||||
|
||||
let display_ctx = LoginDisplayCtx {
|
||||
domain_info,
|
||||
domain_info: domain_info.clone(),
|
||||
oauth2: None,
|
||||
reauth: None,
|
||||
error: None,
|
||||
|
@ -702,7 +732,7 @@ async fn credential_step(
|
|||
ar,
|
||||
client_auth_info,
|
||||
session_context,
|
||||
display_ctx,
|
||||
display_ctx.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
@ -711,6 +741,7 @@ async fn credential_step(
|
|||
Err(err_code) => UnrecoverableErrorView {
|
||||
err_code,
|
||||
operation_id: kopid.eventid,
|
||||
domain_info: display_ctx.domain_info,
|
||||
}
|
||||
.into_response(),
|
||||
}
|
||||
|
@ -719,6 +750,7 @@ async fn credential_step(
|
|||
Err(err_code) => UnrecoverableErrorView {
|
||||
err_code,
|
||||
operation_id: kopid.eventid,
|
||||
domain_info,
|
||||
}
|
||||
.into_response(),
|
||||
}
|
||||
|
@ -767,6 +799,7 @@ async fn view_login_step(
|
|||
UnrecoverableErrorView {
|
||||
err_code: OperationError::InvalidState,
|
||||
operation_id: kopid.eventid,
|
||||
domain_info: display_ctx.domain_info,
|
||||
}
|
||||
.into_response()
|
||||
}
|
||||
|
@ -817,10 +850,8 @@ async fn view_login_step(
|
|||
break res;
|
||||
}
|
||||
AuthState::Continue(allowed) => {
|
||||
// Reauth inits its session here so we need to be able to add cookie here ig.
|
||||
if jar.get(COOKIE_AUTH_SESSION_ID).is_none() {
|
||||
jar = add_session_cookie(&state, jar, &session_context)?;
|
||||
}
|
||||
// Reauth inits its session here so we need to be able to add it's cookie here.
|
||||
jar = add_session_cookie(&state, jar, &session_context)?;
|
||||
|
||||
let res = match allowed.len() {
|
||||
// Shouldn't be possible.
|
||||
|
@ -829,6 +860,7 @@ async fn view_login_step(
|
|||
UnrecoverableErrorView {
|
||||
err_code: OperationError::InvalidState,
|
||||
operation_id: kopid.eventid,
|
||||
domain_info: display_ctx.domain_info,
|
||||
}
|
||||
.into_response()
|
||||
}
|
||||
|
@ -897,32 +929,30 @@ async fn view_login_step(
|
|||
// Update jar
|
||||
let token_str = token.to_string();
|
||||
|
||||
// Important - this can be make unsigned as token_str has it's own
|
||||
// Important - this can be make unsigned as token_str has its own
|
||||
// signatures.
|
||||
let bearer_cookie = cookies::make_unsigned(
|
||||
&state,
|
||||
COOKIE_BEARER_TOKEN,
|
||||
token_str.clone(),
|
||||
"/",
|
||||
);
|
||||
let mut bearer_cookie =
|
||||
cookies::make_unsigned(&state, COOKIE_BEARER_TOKEN, token_str.clone());
|
||||
// Important - can be permanent as the token has its own expiration time internally
|
||||
bearer_cookie.make_permanent();
|
||||
|
||||
jar = if session_context.remember_me {
|
||||
// Important - can be unsigned as username is just for remember
|
||||
// me and no other purpose.
|
||||
let username_cookie = cookies::make_unsigned(
|
||||
let mut username_cookie = cookies::make_unsigned(
|
||||
&state,
|
||||
COOKIE_USERNAME,
|
||||
session_context.username.clone(),
|
||||
Urls::Login.as_ref(),
|
||||
);
|
||||
username_cookie.make_permanent();
|
||||
jar.add(username_cookie)
|
||||
} else {
|
||||
jar
|
||||
};
|
||||
|
||||
jar = jar
|
||||
.add(bearer_cookie)
|
||||
.remove(Cookie::from(COOKIE_AUTH_SESSION_ID));
|
||||
jar = jar.add(bearer_cookie);
|
||||
|
||||
jar = cookies::destroy(jar, COOKIE_AUTH_SESSION_ID, &state);
|
||||
|
||||
// Now, we need to decided where to go.
|
||||
let res = if jar.get(COOKIE_OAUTH2_REQ).is_some() {
|
||||
|
@ -939,7 +969,7 @@ async fn view_login_step(
|
|||
}
|
||||
AuthState::Denied(reason) => {
|
||||
debug!("🧩 -> AuthState::Denied");
|
||||
jar = jar.remove(Cookie::from(COOKIE_AUTH_SESSION_ID));
|
||||
jar = cookies::destroy(jar, COOKIE_AUTH_SESSION_ID, &state);
|
||||
|
||||
break LoginDeniedView {
|
||||
display_ctx,
|
||||
|
@ -959,16 +989,11 @@ fn add_session_cookie(
|
|||
jar: CookieJar,
|
||||
session_context: &SessionContext,
|
||||
) -> Result<CookieJar, OperationError> {
|
||||
cookies::make_signed(
|
||||
state,
|
||||
COOKIE_AUTH_SESSION_ID,
|
||||
session_context,
|
||||
Urls::Login.as_ref(),
|
||||
)
|
||||
.map(|mut cookie| {
|
||||
// Not needed when redirecting into this site
|
||||
cookie.set_same_site(SameSite::Strict);
|
||||
jar.add(cookie)
|
||||
})
|
||||
.ok_or(OperationError::InvalidSessionState)
|
||||
cookies::make_signed(state, COOKIE_AUTH_SESSION_ID, session_context)
|
||||
.map(|mut cookie| {
|
||||
// Not needed when redirecting into this site
|
||||
cookie.set_same_site(SameSite::Strict);
|
||||
jar.add(cookie)
|
||||
})
|
||||
.ok_or(OperationError::InvalidSessionState)
|
||||
}
|
||||
|
|
|
@ -9,13 +9,17 @@ use axum::{
|
|||
use axum_htmx::HxRequestGuardLayer;
|
||||
|
||||
use constants::Urls;
|
||||
use kanidmd_lib::prelude::{OperationError, Uuid};
|
||||
use kanidmd_lib::{
|
||||
idm::server::DomainInfoRead,
|
||||
prelude::{OperationError, Uuid},
|
||||
};
|
||||
|
||||
use crate::https::ServerState;
|
||||
|
||||
mod apps;
|
||||
mod constants;
|
||||
pub(crate) mod constants;
|
||||
mod cookies;
|
||||
mod enrol;
|
||||
mod errors;
|
||||
mod login;
|
||||
mod navbar;
|
||||
|
@ -28,6 +32,8 @@ mod reset;
|
|||
struct UnrecoverableErrorView {
|
||||
err_code: OperationError,
|
||||
operation_id: Uuid,
|
||||
// This is an option because it's not always present in an "unrecoverable" situation
|
||||
domain_info: DomainInfoRead,
|
||||
}
|
||||
|
||||
pub fn view_router() -> Router<ServerState> {
|
||||
|
@ -37,6 +43,7 @@ pub fn view_router() -> Router<ServerState> {
|
|||
get(|| async { Redirect::permanent(Urls::Login.as_ref()) }),
|
||||
)
|
||||
.route("/apps", get(apps::view_apps_get))
|
||||
.route("/enrol", get(enrol::view_enrol_get))
|
||||
.route("/reset", get(reset::view_reset_get))
|
||||
.route("/update_credentials", get(reset::view_self_reset_get))
|
||||
.route("/profile", get(profile::view_profile_get))
|
||||
|
@ -96,6 +103,10 @@ pub fn view_router() -> Router<ServerState> {
|
|||
.route("/reset/change_password", post(reset::view_new_pwd))
|
||||
.route("/reset/add_passkey", post(reset::view_new_passkey))
|
||||
.route("/reset/set_unixcred", post(reset::view_set_unixcred))
|
||||
.route(
|
||||
"/reset/add_ssh_publickey",
|
||||
post(reset::view_add_ssh_publickey),
|
||||
)
|
||||
.route("/api/delete_alt_creds", post(reset::remove_alt_creds))
|
||||
.route("/api/delete_unixcred", post(reset::remove_unixcred))
|
||||
.route("/api/add_totp", post(reset::add_totp))
|
||||
|
@ -103,6 +114,10 @@ pub fn view_router() -> Router<ServerState> {
|
|||
.route("/api/remove_passkey", post(reset::remove_passkey))
|
||||
.route("/api/finish_passkey", post(reset::finish_passkey))
|
||||
.route("/api/cancel_mfareg", post(reset::cancel_mfareg))
|
||||
.route(
|
||||
"/api/remove_ssh_publickey",
|
||||
post(reset::remove_ssh_publickey),
|
||||
)
|
||||
.route("/api/cu_cancel", post(reset::cancel_cred_update))
|
||||
.route("/api/cu_commit", post(reset::commit))
|
||||
.layer(HxRequestGuardLayer::new("/ui"));
|
||||
|
@ -128,3 +143,29 @@ where
|
|||
.map(Some),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use askama_axum::IntoResponse;
|
||||
|
||||
use super::*;
|
||||
#[tokio::test]
|
||||
async fn test_unrecoverableerrorview() {
|
||||
let domain_info = kanidmd_lib::server::DomainInfo::new_test();
|
||||
|
||||
let view = UnrecoverableErrorView {
|
||||
err_code: OperationError::InvalidState,
|
||||
operation_id: Uuid::new_v4(),
|
||||
domain_info: domain_info.read(),
|
||||
};
|
||||
|
||||
let error_html = view.render().expect("Failed to render");
|
||||
|
||||
assert!(error_html.contains(domain_info.read().display_name()));
|
||||
|
||||
let response = view.into_response();
|
||||
|
||||
// TODO: this really should be an error code :(
|
||||
assert_eq!(response.status(), 200);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,7 @@ use crate::https::{
|
|||
middleware::KOpId,
|
||||
ServerState,
|
||||
};
|
||||
use kanidmd_lib::idm::oauth2::{
|
||||
AuthorisationRequest, AuthorisePermitSuccess, AuthoriseResponse, Oauth2Error,
|
||||
};
|
||||
use kanidmd_lib::idm::oauth2::{AuthorisationRequest, AuthoriseResponse, Oauth2Error};
|
||||
use kanidmd_lib::prelude::*;
|
||||
|
||||
use kanidm_proto::internal::COOKIE_OAUTH2_REQ;
|
||||
|
@ -26,7 +24,6 @@ use axum_extra::extract::cookie::{CookieJar, SameSite};
|
|||
use axum_htmx::HX_REDIRECT;
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::constants::Urls;
|
||||
use super::login::{LoginDisplayCtx, Oauth2Ctx};
|
||||
use super::{cookies, UnrecoverableErrorView};
|
||||
|
||||
|
@ -96,14 +93,7 @@ async fn oauth2_auth_req(
|
|||
) -> Response {
|
||||
// No matter what, we always clear the stored oauth2 cookie to prevent
|
||||
// ui loops
|
||||
let jar = if let Some(authreq_cookie) = jar.get(COOKIE_OAUTH2_REQ) {
|
||||
let mut authreq_cookie = authreq_cookie.clone();
|
||||
authreq_cookie.make_removal();
|
||||
authreq_cookie.set_path(Urls::Ui.as_ref());
|
||||
jar.add(authreq_cookie)
|
||||
} else {
|
||||
jar
|
||||
};
|
||||
let jar = cookies::destroy(jar, COOKIE_OAUTH2_REQ, &state);
|
||||
|
||||
// If the auth_req was cross-signed, old, or just bad, error. But we have *cleared* it
|
||||
// from the cookie which means we won't see it again.
|
||||
|
@ -112,8 +102,9 @@ async fn oauth2_auth_req(
|
|||
return (
|
||||
jar,
|
||||
UnrecoverableErrorView {
|
||||
err_code: OperationError::InvalidState,
|
||||
err_code: OperationError::UI0003InvalidOauth2Resume,
|
||||
operation_id: kopid.eventid,
|
||||
domain_info,
|
||||
},
|
||||
)
|
||||
.into_response();
|
||||
|
@ -125,16 +116,8 @@ async fn oauth2_auth_req(
|
|||
.await;
|
||||
|
||||
match res {
|
||||
Ok(AuthoriseResponse::Permitted(AuthorisePermitSuccess {
|
||||
mut redirect_uri,
|
||||
state,
|
||||
code,
|
||||
})) => {
|
||||
redirect_uri
|
||||
.query_pairs_mut()
|
||||
.clear()
|
||||
.append_pair("state", &state)
|
||||
.append_pair("code", &code);
|
||||
Ok(AuthoriseResponse::Permitted(success)) => {
|
||||
let redirect_uri = success.build_redirect_uri();
|
||||
|
||||
(
|
||||
jar,
|
||||
|
@ -156,14 +139,17 @@ async fn oauth2_auth_req(
|
|||
consent_token,
|
||||
}) => {
|
||||
// We can just render the form now, the consent token has everything we need.
|
||||
ConsentRequestView {
|
||||
client_name,
|
||||
// scopes,
|
||||
pii_scopes,
|
||||
consent_token,
|
||||
redirect: None,
|
||||
}
|
||||
.into_response()
|
||||
(
|
||||
jar,
|
||||
ConsentRequestView {
|
||||
client_name,
|
||||
// scopes,
|
||||
pii_scopes,
|
||||
consent_token,
|
||||
redirect: None,
|
||||
},
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
|
||||
Ok(AuthoriseResponse::AuthenticationRequired {
|
||||
|
@ -172,16 +158,19 @@ async fn oauth2_auth_req(
|
|||
}) => {
|
||||
// Sign the auth req and hide it in our cookie - we'll come back for
|
||||
// you later.
|
||||
let maybe_jar =
|
||||
cookies::make_signed(&state, COOKIE_OAUTH2_REQ, &auth_req, Urls::Ui.as_ref())
|
||||
.map(|mut cookie| {
|
||||
cookie.set_same_site(SameSite::Strict);
|
||||
jar.add(cookie)
|
||||
})
|
||||
.ok_or(OperationError::InvalidSessionState);
|
||||
let maybe_jar = cookies::make_signed(&state, COOKIE_OAUTH2_REQ, &auth_req)
|
||||
.map(|mut cookie| {
|
||||
cookie.set_same_site(SameSite::Strict);
|
||||
// Expire at the end of the session.
|
||||
cookie.set_expires(None);
|
||||
// Could experiment with this to a shorter value, but session should be enough.
|
||||
cookie.set_max_age(time::Duration::minutes(15));
|
||||
jar.clone().add(cookie)
|
||||
})
|
||||
.ok_or(OperationError::InvalidSessionState);
|
||||
|
||||
match maybe_jar {
|
||||
Ok(jar) => {
|
||||
Ok(new_jar) => {
|
||||
let display_ctx = LoginDisplayCtx {
|
||||
domain_info,
|
||||
oauth2: Some(Oauth2Ctx { client_name }),
|
||||
|
@ -189,21 +178,28 @@ async fn oauth2_auth_req(
|
|||
error: None,
|
||||
};
|
||||
|
||||
super::login::view_oauth2_get(jar, display_ctx, login_hint)
|
||||
super::login::view_oauth2_get(new_jar, display_ctx, login_hint)
|
||||
}
|
||||
Err(err_code) => UnrecoverableErrorView {
|
||||
err_code,
|
||||
operation_id: kopid.eventid,
|
||||
}
|
||||
.into_response(),
|
||||
Err(err_code) => (
|
||||
jar,
|
||||
UnrecoverableErrorView {
|
||||
err_code,
|
||||
operation_id: kopid.eventid,
|
||||
domain_info,
|
||||
},
|
||||
)
|
||||
.into_response(),
|
||||
}
|
||||
}
|
||||
Err(Oauth2Error::AccessDenied) => {
|
||||
// If scopes are not available for this account.
|
||||
AccessDeniedView {
|
||||
operation_id: kopid.eventid,
|
||||
}
|
||||
.into_response()
|
||||
(
|
||||
jar,
|
||||
AccessDeniedView {
|
||||
operation_id: kopid.eventid,
|
||||
},
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
/*
|
||||
RFC - If the request fails due to a missing, invalid, or mismatching
|
||||
|
@ -222,11 +218,15 @@ async fn oauth2_auth_req(
|
|||
&err_code.to_string()
|
||||
);
|
||||
|
||||
UnrecoverableErrorView {
|
||||
err_code: OperationError::InvalidState,
|
||||
operation_id: kopid.eventid,
|
||||
}
|
||||
.into_response()
|
||||
(
|
||||
jar,
|
||||
UnrecoverableErrorView {
|
||||
err_code: OperationError::InvalidState,
|
||||
operation_id: kopid.eventid,
|
||||
domain_info,
|
||||
},
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -240,44 +240,37 @@ pub struct ConsentForm {
|
|||
}
|
||||
|
||||
pub async fn view_consent_post(
|
||||
State(state): State<ServerState>,
|
||||
State(server_state): State<ServerState>,
|
||||
Extension(kopid): Extension<KOpId>,
|
||||
VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
|
||||
DomainInfo(domain_info): DomainInfo,
|
||||
jar: CookieJar,
|
||||
Form(consent_form): Form<ConsentForm>,
|
||||
) -> Result<Response, UnrecoverableErrorView> {
|
||||
let res = state
|
||||
let res = server_state
|
||||
.qe_w_ref
|
||||
.handle_oauth2_authorise_permit(client_auth_info, consent_form.consent_token, kopid.eventid)
|
||||
.await;
|
||||
|
||||
match res {
|
||||
Ok(AuthorisePermitSuccess {
|
||||
mut redirect_uri,
|
||||
state,
|
||||
code,
|
||||
}) => {
|
||||
let jar = cookies::destroy(jar, COOKIE_OAUTH2_REQ);
|
||||
Ok(success) => {
|
||||
let jar = cookies::destroy(jar, COOKIE_OAUTH2_REQ, &server_state);
|
||||
|
||||
if let Some(redirect) = consent_form.redirect {
|
||||
Ok((
|
||||
jar,
|
||||
[
|
||||
(HX_REDIRECT, redirect_uri.as_str().to_string()),
|
||||
(HX_REDIRECT, success.redirect_uri.as_str().to_string()),
|
||||
(
|
||||
ACCESS_CONTROL_ALLOW_ORIGIN.as_str(),
|
||||
redirect_uri.origin().ascii_serialization(),
|
||||
success.redirect_uri.origin().ascii_serialization(),
|
||||
),
|
||||
],
|
||||
Redirect::to(&redirect),
|
||||
)
|
||||
.into_response())
|
||||
} else {
|
||||
redirect_uri
|
||||
.query_pairs_mut()
|
||||
.clear()
|
||||
.append_pair("state", &state)
|
||||
.append_pair("code", &code);
|
||||
let redirect_uri = success.build_redirect_uri();
|
||||
Ok((
|
||||
jar,
|
||||
[
|
||||
|
@ -302,6 +295,7 @@ pub async fn view_consent_post(
|
|||
Err(UnrecoverableErrorView {
|
||||
err_code: OperationError::InvalidState,
|
||||
operation_id: kopid.eventid,
|
||||
domain_info,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,7 +69,7 @@ pub(crate) async fn view_profile_unlock_get(
|
|||
.qe_r_ref
|
||||
.handle_whoami_uat(client_auth_info.clone(), kopid.eventid)
|
||||
.await
|
||||
.map_err(|op_err| HtmxError::new(&kopid, op_err))?;
|
||||
.map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))?;
|
||||
|
||||
let display_ctx = LoginDisplayCtx {
|
||||
domain_info,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue