mirror of
https://github.com/kanidm/kanidm.git
synced 2025-06-12 11:07:46 +02:00
Compare commits
3 commits
bb0e759134
...
26cbb2d8c1
Author | SHA1 | Date | |
---|---|---|---|
|
26cbb2d8c1 | ||
|
e2c6190693 | ||
|
a28c4e8cd4 |
.github/workflows
Cargo.lockCargo.tomlbook/src
examples
libs
platform
proto/src
pykanidm
scripts
server
core/src
daemon
lib-macros/src
lib/src
testkit-macros/src
testkit
tools
unix_integration
8
.github/workflows/clippy.yml
vendored
8
.github/workflows/clippy.yml
vendored
|
@ -19,7 +19,9 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Setup sccache
|
- name: Setup sccache
|
||||||
uses: mozilla-actions/sccache-action@v0.0.9
|
uses: mozilla-actions/sccache-action@v0.0.7
|
||||||
|
with:
|
||||||
|
version: "v0.4.2"
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update && \
|
sudo apt-get update && \
|
||||||
|
@ -39,6 +41,8 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Setup sccache
|
- name: Setup sccache
|
||||||
uses: mozilla-actions/sccache-action@v0.0.9
|
uses: mozilla-actions/sccache-action@v0.0.7
|
||||||
|
with:
|
||||||
|
version: "v0.4.2"
|
||||||
- name: "Run cargo fmt"
|
- name: "Run cargo fmt"
|
||||||
run: cargo fmt --check
|
run: cargo fmt --check
|
||||||
|
|
4
.github/workflows/kanidm_individual_book.yml
vendored
4
.github/workflows/kanidm_individual_book.yml
vendored
|
@ -24,7 +24,9 @@ jobs:
|
||||||
with:
|
with:
|
||||||
ref: ${{ inputs.tag }}
|
ref: ${{ inputs.tag }}
|
||||||
- name: Setup sccache
|
- name: Setup sccache
|
||||||
uses: mozilla-actions/sccache-action@v0.0.9
|
uses: mozilla-actions/sccache-action@v0.0.7
|
||||||
|
with:
|
||||||
|
version: "v0.4.2"
|
||||||
- name: Install deps
|
- name: Install deps
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
|
|
15
.github/workflows/rust_build.yml
vendored
15
.github/workflows/rust_build.yml
vendored
|
@ -27,7 +27,10 @@ jobs:
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
- name: Setup sccache
|
- name: Setup sccache
|
||||||
uses: mozilla-actions/sccache-action@v0.0.9
|
uses: mozilla-actions/sccache-action@v0.0.7
|
||||||
|
with:
|
||||||
|
version: "v0.4.2"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update && \
|
sudo apt-get update && \
|
||||||
|
@ -72,7 +75,10 @@ jobs:
|
||||||
with:
|
with:
|
||||||
toolchain: ${{ matrix.rust_version }}
|
toolchain: ${{ matrix.rust_version }}
|
||||||
- name: Setup sccache
|
- name: Setup sccache
|
||||||
uses: mozilla-actions/sccache-action@v0.0.9
|
uses: mozilla-actions/sccache-action@v0.0.7
|
||||||
|
with:
|
||||||
|
version: "v0.4.2"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update && \
|
sudo apt-get update && \
|
||||||
|
@ -112,7 +118,10 @@ jobs:
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
- name: Setup sccache
|
- name: Setup sccache
|
||||||
uses: mozilla-actions/sccache-action@v0.0.9
|
uses: mozilla-actions/sccache-action@v0.0.7
|
||||||
|
with:
|
||||||
|
version: "v0.4.2"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update && \
|
sudo apt-get update && \
|
||||||
|
|
4
.github/workflows/windows_build.yml
vendored
4
.github/workflows/windows_build.yml
vendored
|
@ -28,7 +28,9 @@ jobs:
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
- name: Setup sccache
|
- name: Setup sccache
|
||||||
uses: mozilla-actions/sccache-action@v0.0.9
|
uses: mozilla-actions/sccache-action@v0.0.7
|
||||||
|
with:
|
||||||
|
version: "v0.4.2"
|
||||||
- run: cargo build --locked -p kanidm_client -p kanidm_tools --bin kanidm
|
- run: cargo build --locked -p kanidm_client -p kanidm_tools --bin kanidm
|
||||||
# yamllint disable-line rule:line-length
|
# yamllint disable-line rule:line-length
|
||||||
- run: cargo test -p kanidm_client -p kanidm_tools
|
- run: cargo test -p kanidm_client -p kanidm_tools
|
||||||
|
|
903
Cargo.lock
generated
903
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
12
Cargo.toml
12
Cargo.toml
|
@ -159,12 +159,12 @@ base64 = "^0.22.1"
|
||||||
base64urlsafedata = "0.5.1"
|
base64urlsafedata = "0.5.1"
|
||||||
bitflags = "^2.8.0"
|
bitflags = "^2.8.0"
|
||||||
bytes = "^1.9.0"
|
bytes = "^1.9.0"
|
||||||
clap = { version = "^4.5.34", features = ["derive", "env"] }
|
clap = { version = "^4.5.27", features = ["derive", "env"] }
|
||||||
clap_complete = "^4.5.42"
|
clap_complete = "^4.5.42"
|
||||||
# Forced by saffron/cron
|
# Forced by saffron/cron
|
||||||
chrono = "^0.4.39"
|
chrono = "^0.4.39"
|
||||||
compact_jwt = { version = "^0.4.2", default-features = false }
|
compact_jwt = { version = "^0.4.2", default-features = false }
|
||||||
concread = "^0.5.5"
|
concread = "^0.5.3"
|
||||||
cron = "0.15.0"
|
cron = "0.15.0"
|
||||||
crossbeam = "0.8.4"
|
crossbeam = "0.8.4"
|
||||||
csv = "1.3.1"
|
csv = "1.3.1"
|
||||||
|
@ -190,7 +190,7 @@ image = { version = "0.24.9", default-features = false, features = [
|
||||||
"jpeg",
|
"jpeg",
|
||||||
"webp",
|
"webp",
|
||||||
] }
|
] }
|
||||||
itertools = "0.14.0"
|
itertools = "0.13.0"
|
||||||
enum-iterator = "2.1.0"
|
enum-iterator = "2.1.0"
|
||||||
kanidmd_web_ui_shared = { path = "./server/web_ui/shared" }
|
kanidmd_web_ui_shared = { path = "./server/web_ui/shared" }
|
||||||
# REMOVE this
|
# REMOVE this
|
||||||
|
@ -202,11 +202,11 @@ libc = "^0.2.168"
|
||||||
libnss = "^0.8.0"
|
libnss = "^0.8.0"
|
||||||
libsqlite3-sys = "^0.25.2"
|
libsqlite3-sys = "^0.25.2"
|
||||||
lodepng = "3.11.0"
|
lodepng = "3.11.0"
|
||||||
lru = "^0.13.0"
|
lru = "^0.12.5"
|
||||||
mathru = "^0.13.0"
|
mathru = "^0.13.0"
|
||||||
md-5 = "0.10.6"
|
md-5 = "0.10.6"
|
||||||
mimalloc = "0.1.43"
|
mimalloc = "0.1.43"
|
||||||
notify-debouncer-full = { version = "0.5" }
|
notify-debouncer-full = { version = "0.1" }
|
||||||
num_enum = "^0.5.11"
|
num_enum = "^0.5.11"
|
||||||
oauth2_ext = { version = "^4.4.2", package = "oauth2", default-features = false }
|
oauth2_ext = { version = "^4.4.2", package = "oauth2", default-features = false }
|
||||||
openssl-sys = "^0.9"
|
openssl-sys = "^0.9"
|
||||||
|
@ -294,7 +294,7 @@ webauthn-rs = { version = "0.5.1", features = ["preview-features"] }
|
||||||
webauthn-rs-core = "0.5.1"
|
webauthn-rs-core = "0.5.1"
|
||||||
webauthn-rs-proto = "0.5.1"
|
webauthn-rs-proto = "0.5.1"
|
||||||
|
|
||||||
whoami = "^1.6.0"
|
whoami = "^1.5.2"
|
||||||
walkdir = "2"
|
walkdir = "2"
|
||||||
|
|
||||||
x509-cert = "0.2.5"
|
x509-cert = "0.2.5"
|
||||||
|
|
|
@ -53,6 +53,7 @@
|
||||||
|
|
||||||
- [Service Integration Examples](examples/readme.md)
|
- [Service Integration Examples](examples/readme.md)
|
||||||
- [Kubernetes Ingress](examples/kubernetes_ingress.md)
|
- [Kubernetes Ingress](examples/kubernetes_ingress.md)
|
||||||
|
- [OAuth2 Examples](integrations/oauth2/examples.md)
|
||||||
- [Traefik](examples/traefik.md)
|
- [Traefik](examples/traefik.md)
|
||||||
|
|
||||||
- [Replication](repl/readme.md)
|
- [Replication](repl/readme.md)
|
||||||
|
|
|
@ -58,21 +58,6 @@ can only use the UID range `1879048192` (`0x70000000`) to `2147483647` (`0x7ffff
|
||||||
limitations of the Linux kernel and
|
limitations of the Linux kernel and
|
||||||
[systemd reserving other uids in the range](http://systemd.io/UIDS-GIDS/) for its exclusive use.
|
[systemd reserving other uids in the range](http://systemd.io/UIDS-GIDS/) for its exclusive use.
|
||||||
|
|
||||||
| name | min | max |
|
|
||||||
|------|-----|-----|
|
|
||||||
| system | 0 | 999 |
|
|
||||||
| user | 1000 | 60000 |
|
|
||||||
| systemd homed | 60001 | 60577 |
|
|
||||||
| unused A | 60578 | 61183 |
|
|
||||||
| systemd dynuser | 61184 | 65519 |
|
|
||||||
| unused B | 65520 | 65533 |
|
|
||||||
| nobody | 65534 | 65534 |
|
|
||||||
| 16bit limit | 65535 | 65535 |
|
|
||||||
| unused C | 65536 | 524287 |
|
|
||||||
| systemd nspawn | 524288 | 1879048191 |
|
|
||||||
| kanidm dyn alloc | 1879048192 | 2147483647 |
|
|
||||||
| unusable | 2147483648 | 4294967295 |
|
|
||||||
|
|
||||||
A valid concern is the possibility of duplication in the lower 24 bits. Given the
|
A valid concern is the possibility of duplication in the lower 24 bits. Given the
|
||||||
[birthday problem](https://en.wikipedia.org/wiki/Birthday_problem), if you have ~7700 groups and
|
[birthday problem](https://en.wikipedia.org/wiki/Birthday_problem), if you have ~7700 groups and
|
||||||
accounts, you have a 50% chance of duplication. With ~5000 you have a 25% chance, ~930 you have a 1%
|
accounts, you have a 50% chance of duplication. With ~5000 you have a 25% chance, ~930 you have a 1%
|
||||||
|
@ -82,7 +67,7 @@ We advise that if you have a site with greater than approximately 2,000 users yo
|
||||||
external system to allocate GID numbers serially or consistently to avoid potential duplication
|
external system to allocate GID numbers serially or consistently to avoid potential duplication
|
||||||
events.
|
events.
|
||||||
|
|
||||||
We recommend the use of the range `65536` through `524287` (`unused C`) for manual allocation. This leaves the
|
We recommend the use of the range `65536` through `524287` for manual allocation. This leaves the
|
||||||
range `1000` through `65535` for OS/Distro purposes, and allows Kanidm to continue dynamic
|
range `1000` through `65535` for OS/Distro purposes, and allows Kanidm to continue dynamic
|
||||||
allocation in the range `1879048192` to `2147483647` if you choose a hybrid allocation approach.
|
allocation in the range `1879048192` to `2147483647` if you choose a hybrid allocation approach.
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,6 @@ can take many forms such as.
|
||||||
- firstname firstname lastname
|
- firstname firstname lastname
|
||||||
- firstname lastname lastname
|
- firstname lastname lastname
|
||||||
- firstname
|
- firstname
|
||||||
- middlename lastname
|
|
||||||
- lastname firstname
|
- lastname firstname
|
||||||
|
|
||||||
And many many more that are not listed here. This is why our names are displayName as a freetext
|
And many many more that are not listed here. This is why our names are displayName as a freetext
|
||||||
|
|
|
@ -54,6 +54,7 @@ services:
|
||||||
- traefik.http.routers.kanidm.entrypoints=websecure
|
- traefik.http.routers.kanidm.entrypoints=websecure
|
||||||
- traefik.http.routers.kanidm.rule=Host(`idm.example.com`)
|
- traefik.http.routers.kanidm.rule=Host(`idm.example.com`)
|
||||||
- traefik.http.routers.kanidm.service=kanidm
|
- traefik.http.routers.kanidm.service=kanidm
|
||||||
|
- traefik.http.serversTransports.kanidm.insecureSkipVerify=true
|
||||||
- traefik.http.services.kanidm.loadbalancer.server.port=8443
|
- traefik.http.services.kanidm.loadbalancer.server.port=8443
|
||||||
- traefik.http.services.kanidm.loadbalancer.server.scheme=https
|
- traefik.http.services.kanidm.loadbalancer.server.scheme=https
|
||||||
volumes:
|
volumes:
|
||||||
|
|
|
@ -566,7 +566,7 @@ Due to a [lack of public client support](https://github.com/oauth2-proxy/oauth2-
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
kanidm system oauth2 create webapp 'webapp.example.com' 'https://webapp.example.com'
|
kanidm system oauth2 create webapp 'webapp.example.com' 'https://webapp.example.com'
|
||||||
kanidm system oauth2 add-redirect-url webapp 'https://webapp.example.com/oauth2/callback'
|
kanidm system add-redirect-url webapp 'https://webapp.example.com/oauth2/callback'
|
||||||
kanidm system oauth2 update-scope-map webapp email openid
|
kanidm system oauth2 update-scope-map webapp email openid
|
||||||
kanidm system oauth2 get webapp
|
kanidm system oauth2 get webapp
|
||||||
kanidm system oauth2 show-basic-secret webapp
|
kanidm system oauth2 show-basic-secret webapp
|
||||||
|
|
|
@ -5,45 +5,57 @@
|
||||||
- Debian packaging is complex enough that it lives in a separate repository:
|
- Debian packaging is complex enough that it lives in a separate repository:
|
||||||
[kanidm/kanidm_ppa_automation](https://github.com/kanidm/kanidm_ppa_automation).
|
[kanidm/kanidm_ppa_automation](https://github.com/kanidm/kanidm_ppa_automation).
|
||||||
- While official packages are available at https://kanidm.github.io/kanidm_ppa/ these instructions will guide you
|
- While official packages are available at https://kanidm.github.io/kanidm_ppa/ these instructions will guide you
|
||||||
through replicating the same process locally, using Docker to isolate the build process from your normal computer.
|
through replicating the same process locally, using [cross](https://github.com/cross-rs/cross) & Docker to isolate the build process
|
||||||
- Due to the complexity of crosscompilation, we no longer support it and recommend building natively,
|
from your normal computer and allow building packages for multiple architectures.
|
||||||
i.e. on the platform you're targeting.
|
|
||||||
- While the examples below will use `aarch64-unknown-linux-gnu` aka `arm64`,
|
- While the examples below will use `aarch64-unknown-linux-gnu` aka `arm64`,
|
||||||
the same process works for `x86_64-unknown-linux-gnu` aka `amd64` as well.
|
the same process works for `x86_64-unknown-linux-gnu` aka `amd64` as well.
|
||||||
|
|
||||||
1. Start in the root directory of the main [kanidm/kanidm](https://github.com/kanidm/kanidm) repository.
|
1. Start in the root directory of the main [kanidm/kanidm](https://github.com/kanidm/kanidm) repository.
|
||||||
|
1. Install cross:
|
||||||
|
```shell
|
||||||
|
cargo install cross
|
||||||
|
```
|
||||||
1. Pull in the separate deb packaging submodule:
|
1. Pull in the separate deb packaging submodule:
|
||||||
```shell
|
```shell
|
||||||
git submodule update platform/debian/kanidm_ppa_automation
|
git submodule update platform/debian/kanidm_ppa_automation
|
||||||
```
|
```
|
||||||
|
1. Launch your desired crossbuild target. Do note the script assumes you use rustup!
|
||||||
|
```shell
|
||||||
|
# See valid targets:
|
||||||
|
platform/debian/kanidm_ppa_automation/scripts/crossbuild.sh
|
||||||
|
# Launch a target:
|
||||||
|
platform/debian/kanidm_ppa_automation/scripts/crossbuild.sh debian-12-aarch64-unknown-linux-gnu
|
||||||
|
# You can also specify multiple targets within the same distribution:
|
||||||
|
platform/debian/kanidm_ppa_automation/scripts/crossbuild.sh debian-12-{aarch64,x86_64}-unknown-linux-gnu
|
||||||
|
```
|
||||||
|
1. Go get a drink of your choice while the build completes.
|
||||||
1. Create a sacrificial deb builder container to avoid changing your own system:
|
1. Create a sacrificial deb builder container to avoid changing your own system:
|
||||||
```shell
|
```shell
|
||||||
docker run --rm -it -e VERBOSE=true -e CI=true \
|
docker run --rm -it -e CI=true \
|
||||||
--mount "type=bind,src=$PWD,target=/src" \
|
--mount "type=bind,src=$PWD,target=/src" \
|
||||||
--workdir /src \
|
--workdir /src \
|
||||||
rust:bookworm
|
rust:bookworm
|
||||||
```
|
```
|
||||||
1. In the container install dependencies with:
|
1. In the container install dependencies with:
|
||||||
```shell
|
```shell
|
||||||
platform/debian/kanidm_ppa_automation/scripts/install_ci_build_dependencies.sh
|
# The parameter given is which additional target debian architecture to enable (amd64, arm64, etc.)
|
||||||
|
# If your native platform is amd64, running with arm64 is enough to cover both archs.
|
||||||
|
platform/debian/kanidm_ppa_automation/scripts/install_ci_build_dependencies.sh arm64
|
||||||
```
|
```
|
||||||
1. Launch your desired target build:
|
1. In the container launch the deb build:
|
||||||
```shell
|
|
||||||
platform/debian/kanidm_ppa_automation/scripts/build_native.sh aarch64-unknown-linux-gnu
|
|
||||||
```
|
|
||||||
1. Go get a drink of your choice while the build completes.
|
|
||||||
1. Launch the deb build:
|
|
||||||
```shell
|
```shell
|
||||||
platform/debian/kanidm_ppa_automation/scripts/build_debs.sh aarch64-unknown-linux-gnu
|
platform/debian/kanidm_ppa_automation/scripts/build_debs.sh aarch64-unknown-linux-gnu
|
||||||
|
# Again, multiple targets also work:
|
||||||
|
platform/debian/kanidm_ppa_automation/scripts/build_debs.sh {aarch64,x86_64}-unknown-linux-gnu
|
||||||
```
|
```
|
||||||
1. You can now exit the container, the package paths displayed at the end under `target` will
|
1. You can now exit the container, the package paths displayed at the end under `target` will
|
||||||
persist.
|
persist.
|
||||||
|
|
||||||
## Adding or amending a deb package
|
## Adding or amending a deb package
|
||||||
The rough overview of steps is as follows, see further down for details.
|
The rough overview of steps is:
|
||||||
1. Add cargo-deb specific metadata to the rust package and any static assets. Submit your changes as
|
1. Add cargo-deb specific metadata to the rust package and any static assets. Submit your changes as
|
||||||
a PR.
|
a PR.
|
||||||
2. Add build steps to the separate packaging repo. Submit your changes as a PR.
|
2. Add build instructions to the separate packaging repo. Submit your changes as a PR.
|
||||||
3. Go back to the main repo to update the packaging submodule reference to aid running manual dev
|
3. Go back to the main repo to update the packaging submodule reference to aid running manual dev
|
||||||
builds of the new package.
|
builds of the new package.
|
||||||
|
|
||||||
|
@ -60,8 +72,8 @@ an example, see `unix_integration/resolver/Cargo.toml`
|
||||||
### Configuration in the kanidm_ppa_automation repo
|
### Configuration in the kanidm_ppa_automation repo
|
||||||
- The repo is: [kanidm/kanidm_ppa_automation](https://github.com/kanidm/kanidm_ppa_automation)
|
- The repo is: [kanidm/kanidm_ppa_automation](https://github.com/kanidm/kanidm_ppa_automation)
|
||||||
- Changes are needed if a new binary and/or package is added, or if build time dependencies change.
|
- Changes are needed if a new binary and/or package is added, or if build time dependencies change.
|
||||||
- Amend `scripts/build_native.sh` build rules to include new binaries or packages with shared
|
- Amend `scripts/crossbuild.sh` build rules to include new binaries or packages with shared
|
||||||
libraries.
|
libraries. Search for the lines starting with `cross build`.
|
||||||
- Add any new build time system dependencies to `scripts/install_ci_build_dependencies.sh`, be aware
|
- Add any new build time system dependencies to `scripts/install_ci_build_dependencies.sh`, be aware
|
||||||
of any difference in package names between Debian & Ubuntu.
|
of any difference in package names between Debian & Ubuntu.
|
||||||
- Add any new packages to `scripts/build_debs.sh`, search for the line starting with `for package in`.
|
- Add any new packages to `scripts/build_debs.sh`, search for the line starting with `for package in`.
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
version = "2"
|
|
||||||
bindaddress = "[::]:8443"
|
bindaddress = "[::]:8443"
|
||||||
ldapbindaddress = "127.0.0.1:3636"
|
ldapbindaddress = "127.0.0.1:3636"
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
# The server configuration file version.
|
|
||||||
version = "2"
|
|
||||||
|
|
||||||
# The webserver bind address. Requires TLS certificates.
|
# The webserver bind address. Requires TLS certificates.
|
||||||
# If the port is set to 443 you may require the
|
# If the port is set to 443 you may require the
|
||||||
# NET_BIND_SERVICE capability.
|
# NET_BIND_SERVICE capability.
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
# The server configuration file version.
|
|
||||||
version = "2"
|
|
||||||
|
|
||||||
# The webserver bind address. Requires TLS certificates.
|
# The webserver bind address. Requires TLS certificates.
|
||||||
# If the port is set to 443 you may require the
|
# If the port is set to 443 you may require the
|
||||||
# NET_BIND_SERVICE capability.
|
# NET_BIND_SERVICE capability.
|
||||||
|
|
|
@ -195,20 +195,4 @@ impl KanidmClient {
|
||||||
self.perform_get_request(&format!("/v1/group/{}/_attr/mail", id))
|
self.perform_get_request(&format!("/v1/group/{}/_attr/mail", id))
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn idm_group_purge_description(&self, id: &str) -> Result<(), ClientError> {
|
|
||||||
self.idm_group_purge_attr(id, "description").await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn idm_group_set_description(
|
|
||||||
&self,
|
|
||||||
id: &str,
|
|
||||||
description: &str,
|
|
||||||
) -> Result<(), ClientError> {
|
|
||||||
self.perform_put_request(
|
|
||||||
&format!("/v1/group/{}/_attr/description", id),
|
|
||||||
&[description],
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
# The main difference from the release_linux profile is using
|
|
||||||
# per-package shared directories for a clearer separation and
|
|
||||||
# thus more consistent install & sysadmin experience.
|
|
||||||
|
|
||||||
# Don't set the value for autodetect
|
|
||||||
# cpu_flags = "none"
|
|
||||||
server_admin_bind_path = "/var/run/kanidmd/sock"
|
|
||||||
server_ui_pkg_path = "/usr/share/kanidmd/static"
|
|
||||||
server_config_path = "/etc/kanidmd/server.toml"
|
|
||||||
client_config_path = "/etc/kanidm/config"
|
|
||||||
# TODO: unixd should migrate to it's own config dir as part of the sparkled migration.
|
|
||||||
# No point in doing two back to back migrations.
|
|
||||||
resolver_config_path = "/etc/kanidm/unixd"
|
|
||||||
resolver_unix_shell_path = "/bin/bash"
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 8d7579fb543632df74e609892c69ce9f368fdd02
|
Subproject commit 942c7b69ca807cc38186b63ab02a391bac9eac7e
|
|
@ -12,7 +12,7 @@ Conflicts=nscd.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
DynamicUser=yes
|
DynamicUser=yes
|
||||||
SupplementaryGroups=tss
|
SupplementaryGroups=tss shadow
|
||||||
UMask=0027
|
UMask=0027
|
||||||
CacheDirectory=kanidm-unixd
|
CacheDirectory=kanidm-unixd
|
||||||
RuntimeDirectory=kanidm-unixd
|
RuntimeDirectory=kanidm-unixd
|
||||||
|
|
|
@ -22,8 +22,6 @@ pub enum Attribute {
|
||||||
AcpCreateClass,
|
AcpCreateClass,
|
||||||
AcpEnable,
|
AcpEnable,
|
||||||
AcpModifyClass,
|
AcpModifyClass,
|
||||||
AcpModifyPresentClass,
|
|
||||||
AcpModifyRemoveClass,
|
|
||||||
AcpModifyPresentAttr,
|
AcpModifyPresentAttr,
|
||||||
AcpModifyRemovedAttr,
|
AcpModifyRemovedAttr,
|
||||||
AcpReceiver,
|
AcpReceiver,
|
||||||
|
@ -82,7 +80,6 @@ pub enum Attribute {
|
||||||
IdVerificationEcKey,
|
IdVerificationEcKey,
|
||||||
Image,
|
Image,
|
||||||
Index,
|
Index,
|
||||||
Indexed,
|
|
||||||
IpaNtHash,
|
IpaNtHash,
|
||||||
IpaSshPubKey,
|
IpaSshPubKey,
|
||||||
JwsEs256PrivateKey,
|
JwsEs256PrivateKey,
|
||||||
|
@ -257,8 +254,6 @@ impl Attribute {
|
||||||
Attribute::AcpCreateClass => ATTR_ACP_CREATE_CLASS,
|
Attribute::AcpCreateClass => ATTR_ACP_CREATE_CLASS,
|
||||||
Attribute::AcpEnable => ATTR_ACP_ENABLE,
|
Attribute::AcpEnable => ATTR_ACP_ENABLE,
|
||||||
Attribute::AcpModifyClass => ATTR_ACP_MODIFY_CLASS,
|
Attribute::AcpModifyClass => ATTR_ACP_MODIFY_CLASS,
|
||||||
Attribute::AcpModifyPresentClass => ATTR_ACP_MODIFY_PRESENT_CLASS,
|
|
||||||
Attribute::AcpModifyRemoveClass => ATTR_ACP_MODIFY_REMOVE_CLASS,
|
|
||||||
Attribute::AcpModifyPresentAttr => ATTR_ACP_MODIFY_PRESENTATTR,
|
Attribute::AcpModifyPresentAttr => ATTR_ACP_MODIFY_PRESENTATTR,
|
||||||
Attribute::AcpModifyRemovedAttr => ATTR_ACP_MODIFY_REMOVEDATTR,
|
Attribute::AcpModifyRemovedAttr => ATTR_ACP_MODIFY_REMOVEDATTR,
|
||||||
Attribute::AcpReceiver => ATTR_ACP_RECEIVER,
|
Attribute::AcpReceiver => ATTR_ACP_RECEIVER,
|
||||||
|
@ -316,7 +311,6 @@ impl Attribute {
|
||||||
Attribute::IdVerificationEcKey => ATTR_ID_VERIFICATION_ECKEY,
|
Attribute::IdVerificationEcKey => ATTR_ID_VERIFICATION_ECKEY,
|
||||||
Attribute::Image => ATTR_IMAGE,
|
Attribute::Image => ATTR_IMAGE,
|
||||||
Attribute::Index => ATTR_INDEX,
|
Attribute::Index => ATTR_INDEX,
|
||||||
Attribute::Indexed => ATTR_INDEXED,
|
|
||||||
Attribute::IpaNtHash => ATTR_IPANTHASH,
|
Attribute::IpaNtHash => ATTR_IPANTHASH,
|
||||||
Attribute::IpaSshPubKey => ATTR_IPASSHPUBKEY,
|
Attribute::IpaSshPubKey => ATTR_IPASSHPUBKEY,
|
||||||
Attribute::JwsEs256PrivateKey => ATTR_JWS_ES256_PRIVATE_KEY,
|
Attribute::JwsEs256PrivateKey => ATTR_JWS_ES256_PRIVATE_KEY,
|
||||||
|
@ -444,8 +438,6 @@ impl Attribute {
|
||||||
ATTR_ACP_CREATE_CLASS => Attribute::AcpCreateClass,
|
ATTR_ACP_CREATE_CLASS => Attribute::AcpCreateClass,
|
||||||
ATTR_ACP_ENABLE => Attribute::AcpEnable,
|
ATTR_ACP_ENABLE => Attribute::AcpEnable,
|
||||||
ATTR_ACP_MODIFY_CLASS => Attribute::AcpModifyClass,
|
ATTR_ACP_MODIFY_CLASS => Attribute::AcpModifyClass,
|
||||||
ATTR_ACP_MODIFY_PRESENT_CLASS => Attribute::AcpModifyPresentClass,
|
|
||||||
ATTR_ACP_MODIFY_REMOVE_CLASS => Attribute::AcpModifyRemoveClass,
|
|
||||||
ATTR_ACP_MODIFY_PRESENTATTR => Attribute::AcpModifyPresentAttr,
|
ATTR_ACP_MODIFY_PRESENTATTR => Attribute::AcpModifyPresentAttr,
|
||||||
ATTR_ACP_MODIFY_REMOVEDATTR => Attribute::AcpModifyRemovedAttr,
|
ATTR_ACP_MODIFY_REMOVEDATTR => Attribute::AcpModifyRemovedAttr,
|
||||||
ATTR_ACP_RECEIVER => Attribute::AcpReceiver,
|
ATTR_ACP_RECEIVER => Attribute::AcpReceiver,
|
||||||
|
@ -503,7 +495,6 @@ impl Attribute {
|
||||||
ATTR_ID_VERIFICATION_ECKEY => Attribute::IdVerificationEcKey,
|
ATTR_ID_VERIFICATION_ECKEY => Attribute::IdVerificationEcKey,
|
||||||
ATTR_IMAGE => Attribute::Image,
|
ATTR_IMAGE => Attribute::Image,
|
||||||
ATTR_INDEX => Attribute::Index,
|
ATTR_INDEX => Attribute::Index,
|
||||||
ATTR_INDEXED => Attribute::Indexed,
|
|
||||||
ATTR_IPANTHASH => Attribute::IpaNtHash,
|
ATTR_IPANTHASH => Attribute::IpaNtHash,
|
||||||
ATTR_IPASSHPUBKEY => Attribute::IpaSshPubKey,
|
ATTR_IPASSHPUBKEY => Attribute::IpaSshPubKey,
|
||||||
ATTR_JWS_ES256_PRIVATE_KEY => Attribute::JwsEs256PrivateKey,
|
ATTR_JWS_ES256_PRIVATE_KEY => Attribute::JwsEs256PrivateKey,
|
||||||
|
|
|
@ -62,8 +62,6 @@ pub const ATTR_ACP_CREATE_ATTR: &str = "acp_create_attr";
|
||||||
pub const ATTR_ACP_CREATE_CLASS: &str = "acp_create_class";
|
pub const ATTR_ACP_CREATE_CLASS: &str = "acp_create_class";
|
||||||
pub const ATTR_ACP_ENABLE: &str = "acp_enable";
|
pub const ATTR_ACP_ENABLE: &str = "acp_enable";
|
||||||
pub const ATTR_ACP_MODIFY_CLASS: &str = "acp_modify_class";
|
pub const ATTR_ACP_MODIFY_CLASS: &str = "acp_modify_class";
|
||||||
pub const ATTR_ACP_MODIFY_PRESENT_CLASS: &str = "acp_modify_present_class";
|
|
||||||
pub const ATTR_ACP_MODIFY_REMOVE_CLASS: &str = "acp_modify_remove_class";
|
|
||||||
pub const ATTR_ACP_MODIFY_PRESENTATTR: &str = "acp_modify_presentattr";
|
pub const ATTR_ACP_MODIFY_PRESENTATTR: &str = "acp_modify_presentattr";
|
||||||
pub const ATTR_ACP_MODIFY_REMOVEDATTR: &str = "acp_modify_removedattr";
|
pub const ATTR_ACP_MODIFY_REMOVEDATTR: &str = "acp_modify_removedattr";
|
||||||
pub const ATTR_ACP_RECEIVER_GROUP: &str = "acp_receiver_group";
|
pub const ATTR_ACP_RECEIVER_GROUP: &str = "acp_receiver_group";
|
||||||
|
@ -126,7 +124,6 @@ pub const ATTR_GROUP: &str = "group";
|
||||||
pub const ATTR_ID_VERIFICATION_ECKEY: &str = "id_verification_eckey";
|
pub const ATTR_ID_VERIFICATION_ECKEY: &str = "id_verification_eckey";
|
||||||
pub const ATTR_IMAGE: &str = "image";
|
pub const ATTR_IMAGE: &str = "image";
|
||||||
pub const ATTR_INDEX: &str = "index";
|
pub const ATTR_INDEX: &str = "index";
|
||||||
pub const ATTR_INDEXED: &str = "indexed";
|
|
||||||
pub const ATTR_IPANTHASH: &str = "ipanthash";
|
pub const ATTR_IPANTHASH: &str = "ipanthash";
|
||||||
pub const ATTR_IPASSHPUBKEY: &str = "ipasshpubkey";
|
pub const ATTR_IPASSHPUBKEY: &str = "ipasshpubkey";
|
||||||
pub const ATTR_JWS_ES256_PRIVATE_KEY: &str = "jws_es256_private_key";
|
pub const ATTR_JWS_ES256_PRIVATE_KEY: &str = "jws_es256_private_key";
|
||||||
|
|
|
@ -33,7 +33,7 @@ pub enum ScimAttributeEffectiveAccess {
|
||||||
/// All attributes on the entry have this permission granted
|
/// All attributes on the entry have this permission granted
|
||||||
Grant,
|
Grant,
|
||||||
/// All attributes on the entry have this permission denied
|
/// All attributes on the entry have this permission denied
|
||||||
Deny,
|
Denied,
|
||||||
/// The following attributes on the entry have this permission granted
|
/// The following attributes on the entry have this permission granted
|
||||||
Allow(BTreeSet<Attribute>),
|
Allow(BTreeSet<Attribute>),
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ impl ScimAttributeEffectiveAccess {
|
||||||
pub fn check(&self, attr: &Attribute) -> bool {
|
pub fn check(&self, attr: &Attribute) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Grant => true,
|
Self::Grant => true,
|
||||||
Self::Deny => false,
|
Self::Denied => false,
|
||||||
Self::Allow(set) => set.contains(attr),
|
Self::Allow(set) => set.contains(attr),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
724
pykanidm/poetry.lock
generated
724
pykanidm/poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -29,7 +29,7 @@ Authlib = "^1.2.0"
|
||||||
|
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
ruff = ">=0.5.1,<0.11.3"
|
ruff = ">=0.5.1,<0.9.10"
|
||||||
pytest = "^8.3.4"
|
pytest = "^8.3.4"
|
||||||
mypy = "^1.14.1"
|
mypy = "^1.14.1"
|
||||||
types-requests = "^2.32.0.20241016"
|
types-requests = "^2.32.0.20241016"
|
||||||
|
@ -40,7 +40,7 @@ pylint-pydantic = "^0.3.5"
|
||||||
coverage = "^7.6.10"
|
coverage = "^7.6.10"
|
||||||
mkdocs = "^1.6.1"
|
mkdocs = "^1.6.1"
|
||||||
mkdocs-material = "^9.6.1"
|
mkdocs-material = "^9.6.1"
|
||||||
mkdocstrings = ">=0.27,<0.30"
|
mkdocstrings = ">=0.27,<0.29"
|
||||||
mkdocstrings-python = "^1.13.0"
|
mkdocstrings-python = "^1.13.0"
|
||||||
pook = "^2.1.3"
|
pook = "^2.1.3"
|
||||||
|
|
||||||
|
|
|
@ -21,8 +21,7 @@ sudo apt-get install -y \
|
||||||
libsystemd-dev \
|
libsystemd-dev \
|
||||||
libudev-dev \
|
libudev-dev \
|
||||||
pkg-config \
|
pkg-config \
|
||||||
ripgrep \
|
ripgrep
|
||||||
lld
|
|
||||||
|
|
||||||
export PATH="$HOME/.cargo/bin:$PATH"
|
export PATH="$HOME/.cargo/bin:$PATH"
|
||||||
|
|
||||||
|
@ -37,7 +36,7 @@ sudo chgrp vscode ~/ -R
|
||||||
# shellcheck disable=SC1091
|
# shellcheck disable=SC1091
|
||||||
source scripts/devcontainer_poststart.sh
|
source scripts/devcontainer_poststart.sh
|
||||||
|
|
||||||
cargo install \
|
cargo install
|
||||||
cargo-audit \
|
cargo-audit \
|
||||||
mdbook-mermaid \
|
mdbook-mermaid \
|
||||||
mdbook
|
mdbook
|
||||||
|
|
|
@ -25,7 +25,7 @@ def recover_account(username: str) -> str:
|
||||||
"recover-account",
|
"recover-account",
|
||||||
username,
|
username,
|
||||||
"--config",
|
"--config",
|
||||||
"./insecure_server.toml",
|
"../../examples/insecure_server.toml",
|
||||||
"--output",
|
"--output",
|
||||||
"json",
|
"json",
|
||||||
]
|
]
|
||||||
|
|
|
@ -44,7 +44,7 @@ fi
|
||||||
|
|
||||||
|
|
||||||
# defaults
|
# defaults
|
||||||
KANIDM_CONFIG_FILE="./insecure_server.toml"
|
KANIDM_CONFIG_FILE="../../examples/insecure_server.toml"
|
||||||
KANIDM_URL="$(rg origin "${KANIDM_CONFIG_FILE}" | awk '{print $NF}' | tr -d '"')"
|
KANIDM_URL="$(rg origin "${KANIDM_CONFIG_FILE}" | awk '{print $NF}' | tr -d '"')"
|
||||||
KANIDM_CA_PATH="/tmp/kanidm/ca.pem"
|
KANIDM_CA_PATH="/tmp/kanidm/ca.pem"
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ if [ "${REMOVE_TEST_DB}" -eq 1 ]; then
|
||||||
rm /tmp/kanidm/kanidm.db || true
|
rm /tmp/kanidm/kanidm.db || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export KANIDM_CONFIG="./insecure_server.toml"
|
export KANIDM_CONFIG="../../examples/insecure_server.toml"
|
||||||
IDM_ADMIN_USER="idm_admin@localhost"
|
IDM_ADMIN_USER="idm_admin@localhost"
|
||||||
|
|
||||||
echo "Resetting the idm_admin user..."
|
echo "Resetting the idm_admin user..."
|
||||||
|
|
|
@ -25,7 +25,7 @@ if [ ! -f "run_insecure_dev_server.sh" ]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export KANIDM_CONFIG="./insecure_server.toml"
|
export KANIDM_CONFIG="../../examples/insecure_server.toml"
|
||||||
|
|
||||||
mkdir -p /tmp/kanidm/client_ca
|
mkdir -p /tmp/kanidm/client_ca
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ fi
|
||||||
|
|
||||||
ATTEMPT=0
|
ATTEMPT=0
|
||||||
|
|
||||||
KANIDM_CONFIG_FILE="./insecure_server.toml"
|
KANIDM_CONFIG_FILE="../../examples/insecure_server.toml"
|
||||||
KANIDM_URL="$(rg origin "${KANIDM_CONFIG_FILE}" | awk '{print $NF}' | tr -d '"')"
|
KANIDM_URL="$(rg origin "${KANIDM_CONFIG_FILE}" | awk '{print $NF}' | tr -d '"')"
|
||||||
KANIDM_CA_PATH="/tmp/kanidm/ca.pem"
|
KANIDM_CA_PATH="/tmp/kanidm/ca.pem"
|
||||||
|
|
||||||
|
|
|
@ -191,7 +191,7 @@ impl QueryServerReadV1 {
|
||||||
pub async fn handle_online_backup(
|
pub async fn handle_online_backup(
|
||||||
&self,
|
&self,
|
||||||
msg: OnlineBackupEvent,
|
msg: OnlineBackupEvent,
|
||||||
outpath: &Path,
|
outpath: &str,
|
||||||
versions: usize,
|
versions: usize,
|
||||||
) -> Result<(), OperationError> {
|
) -> Result<(), OperationError> {
|
||||||
trace!(eventid = ?msg.eventid, "Begin online backup event");
|
trace!(eventid = ?msg.eventid, "Begin online backup event");
|
||||||
|
@ -200,12 +200,12 @@ impl QueryServerReadV1 {
|
||||||
|
|
||||||
#[allow(clippy::unwrap_used)]
|
#[allow(clippy::unwrap_used)]
|
||||||
let timestamp = now.format(&Rfc3339).unwrap();
|
let timestamp = now.format(&Rfc3339).unwrap();
|
||||||
let dest_file = outpath.join(format!("backup-{}.json", timestamp));
|
let dest_file = format!("{}/backup-{}.json", outpath, timestamp);
|
||||||
|
|
||||||
if dest_file.exists() {
|
if Path::new(&dest_file).exists() {
|
||||||
error!(
|
error!(
|
||||||
"Online backup file {} already exists, will not overwrite it.",
|
"Online backup file {} already exists, will not overwrite it.",
|
||||||
dest_file.display()
|
dest_file
|
||||||
);
|
);
|
||||||
return Err(OperationError::InvalidState);
|
return Err(OperationError::InvalidState);
|
||||||
}
|
}
|
||||||
|
@ -218,14 +218,10 @@ impl QueryServerReadV1 {
|
||||||
.get_be_txn()
|
.get_be_txn()
|
||||||
.backup(&dest_file)
|
.backup(&dest_file)
|
||||||
.map(|()| {
|
.map(|()| {
|
||||||
info!("Online backup created {} successfully", dest_file.display());
|
info!("Online backup created {} successfully", dest_file);
|
||||||
})
|
})
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
error!(
|
error!("Online backup failed to create {}: {:?}", dest_file, e);
|
||||||
"Online backup failed to create {}: {:?}",
|
|
||||||
dest_file.display(),
|
|
||||||
e
|
|
||||||
);
|
|
||||||
OperationError::InvalidState
|
OperationError::InvalidState
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
@ -271,11 +267,7 @@ impl QueryServerReadV1 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(
|
error!("Online backup cleanup error read dir {}: {}", outpath, e);
|
||||||
"Online backup cleanup error read dir {}: {}",
|
|
||||||
outpath.display(),
|
|
||||||
e
|
|
||||||
);
|
|
||||||
return Err(OperationError::InvalidState);
|
return Err(OperationError::InvalidState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -112,19 +112,19 @@ impl IntervalActor {
|
||||||
if !op.exists() {
|
if !op.exists() {
|
||||||
info!(
|
info!(
|
||||||
"Online backup output folder '{}' does not exist, trying to create it.",
|
"Online backup output folder '{}' does not exist, trying to create it.",
|
||||||
outpath.display()
|
outpath
|
||||||
);
|
);
|
||||||
fs::create_dir_all(&outpath).map_err(|e| {
|
fs::create_dir_all(&outpath).map_err(|e| {
|
||||||
error!(
|
error!(
|
||||||
"Online backup failed to create output directory '{}': {}",
|
"Online backup failed to create output directory '{}': {}",
|
||||||
outpath.display(),
|
outpath.clone(),
|
||||||
e
|
e
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !op.is_dir() {
|
if !op.is_dir() {
|
||||||
error!("Online backup output '{}' is not a directory or we are missing permissions to access it.", outpath.display());
|
error!("Online backup output '{}' is not a directory or we are missing permissions to access it.", outpath);
|
||||||
return Err(());
|
return Err(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,7 +148,7 @@ impl IntervalActor {
|
||||||
if let Err(e) = server
|
if let Err(e) = server
|
||||||
.handle_online_backup(
|
.handle_online_backup(
|
||||||
OnlineBackupEvent::new(),
|
OnlineBackupEvent::new(),
|
||||||
&outpath,
|
outpath.clone().as_str(),
|
||||||
versions,
|
versions,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
|
use std::net;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use crate::actors::QueryServerReadV1;
|
use crate::actors::QueryServerReadV1;
|
||||||
use crate::CoreAction;
|
|
||||||
use futures_util::sink::SinkExt;
|
use futures_util::sink::SinkExt;
|
||||||
use futures_util::stream::StreamExt;
|
use futures_util::stream::StreamExt;
|
||||||
use kanidmd_lib::idm::ldap::{LdapBoundToken, LdapResponseState};
|
use kanidmd_lib::idm::ldap::{LdapBoundToken, LdapResponseState};
|
||||||
|
@ -7,16 +10,14 @@ use kanidmd_lib::prelude::*;
|
||||||
use ldap3_proto::proto::LdapMsg;
|
use ldap3_proto::proto::LdapMsg;
|
||||||
use ldap3_proto::LdapCodec;
|
use ldap3_proto::LdapCodec;
|
||||||
use openssl::ssl::{Ssl, SslAcceptor};
|
use openssl::ssl::{Ssl, SslAcceptor};
|
||||||
use std::net;
|
|
||||||
use std::pin::Pin;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use tokio::io::{AsyncRead, AsyncWrite};
|
|
||||||
use tokio::net::{TcpListener, TcpStream};
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
use tokio::sync::broadcast;
|
|
||||||
use tokio::sync::mpsc;
|
|
||||||
use tokio_openssl::SslStream;
|
use tokio_openssl::SslStream;
|
||||||
use tokio_util::codec::{FramedRead, FramedWrite};
|
use tokio_util::codec::{FramedRead, FramedWrite};
|
||||||
|
|
||||||
|
use crate::CoreAction;
|
||||||
|
use tokio::sync::broadcast;
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
struct LdapSession {
|
struct LdapSession {
|
||||||
uat: Option<LdapBoundToken>,
|
uat: Option<LdapBoundToken>,
|
||||||
}
|
}
|
||||||
|
@ -48,14 +49,28 @@ async fn client_process_msg(
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn client_process<STREAM>(
|
async fn client_process(
|
||||||
stream: STREAM,
|
tcpstream: TcpStream,
|
||||||
|
tls_acceptor: SslAcceptor,
|
||||||
client_address: net::SocketAddr,
|
client_address: net::SocketAddr,
|
||||||
qe_r_ref: &'static QueryServerReadV1,
|
qe_r_ref: &'static QueryServerReadV1,
|
||||||
) where
|
) {
|
||||||
STREAM: AsyncRead + AsyncWrite,
|
// Start the event
|
||||||
{
|
// From the parameters we need to create an SslContext.
|
||||||
let (r, w) = tokio::io::split(stream);
|
let mut tlsstream = match Ssl::new(tls_acceptor.context())
|
||||||
|
.and_then(|tls_obj| SslStream::new(tls_obj, tcpstream))
|
||||||
|
{
|
||||||
|
Ok(ta) => ta,
|
||||||
|
Err(e) => {
|
||||||
|
error!("LDAP TLS setup error, continuing -> {:?}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Err(e) = SslStream::accept(Pin::new(&mut tlsstream)).await {
|
||||||
|
error!("LDAP TLS accept error, continuing -> {:?}", e);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let (r, w) = tokio::io::split(tlsstream);
|
||||||
let mut r = FramedRead::new(r, LdapCodec::default());
|
let mut r = FramedRead::new(r, LdapCodec::default());
|
||||||
let mut w = FramedWrite::new(w, LdapCodec::default());
|
let mut w = FramedWrite::new(w, LdapCodec::default());
|
||||||
|
|
||||||
|
@ -111,32 +126,7 @@ async fn client_process<STREAM>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn client_tls_accept(
|
/// TLS LDAP Listener, hands off to [client_process]
|
||||||
tcpstream: TcpStream,
|
|
||||||
tls_acceptor: SslAcceptor,
|
|
||||||
client_socket_addr: net::SocketAddr,
|
|
||||||
qe_r_ref: &'static QueryServerReadV1,
|
|
||||||
) {
|
|
||||||
// Start the event
|
|
||||||
// From the parameters we need to create an SslContext.
|
|
||||||
let mut tlsstream = match Ssl::new(tls_acceptor.context())
|
|
||||||
.and_then(|tls_obj| SslStream::new(tls_obj, tcpstream))
|
|
||||||
{
|
|
||||||
Ok(ta) => ta,
|
|
||||||
Err(err) => {
|
|
||||||
error!(?err, %client_socket_addr, "LDAP TLS setup error");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if let Err(err) = SslStream::accept(Pin::new(&mut tlsstream)).await {
|
|
||||||
error!(?err, %client_socket_addr, "LDAP TLS accept error");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
tokio::spawn(client_process(tlsstream, client_socket_addr, qe_r_ref));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// TLS LDAP Listener, hands off to [client_tls_accept]
|
|
||||||
async fn ldap_tls_acceptor(
|
async fn ldap_tls_acceptor(
|
||||||
listener: TcpListener,
|
listener: TcpListener,
|
||||||
mut tls_acceptor: SslAcceptor,
|
mut tls_acceptor: SslAcceptor,
|
||||||
|
@ -155,10 +145,10 @@ async fn ldap_tls_acceptor(
|
||||||
match accept_result {
|
match accept_result {
|
||||||
Ok((tcpstream, client_socket_addr)) => {
|
Ok((tcpstream, client_socket_addr)) => {
|
||||||
let clone_tls_acceptor = tls_acceptor.clone();
|
let clone_tls_acceptor = tls_acceptor.clone();
|
||||||
tokio::spawn(client_tls_accept(tcpstream, clone_tls_acceptor, client_socket_addr, qe_r_ref));
|
tokio::spawn(client_process(tcpstream, clone_tls_acceptor, client_socket_addr, qe_r_ref));
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(e) => {
|
||||||
warn!(?err, "LDAP acceptor error, continuing");
|
error!("LDAP acceptor error, continuing -> {:?}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -171,34 +161,6 @@ async fn ldap_tls_acceptor(
|
||||||
info!("Stopped {}", super::TaskName::LdapActor);
|
info!("Stopped {}", super::TaskName::LdapActor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// PLAIN LDAP Listener, hands off to [client_process]
|
|
||||||
async fn ldap_plaintext_acceptor(
|
|
||||||
listener: TcpListener,
|
|
||||||
qe_r_ref: &'static QueryServerReadV1,
|
|
||||||
mut rx: broadcast::Receiver<CoreAction>,
|
|
||||||
) {
|
|
||||||
loop {
|
|
||||||
tokio::select! {
|
|
||||||
Ok(action) = rx.recv() => {
|
|
||||||
match action {
|
|
||||||
CoreAction::Shutdown => break,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
accept_result = listener.accept() => {
|
|
||||||
match accept_result {
|
|
||||||
Ok((tcpstream, client_socket_addr)) => {
|
|
||||||
tokio::spawn(client_process(tcpstream, client_socket_addr, qe_r_ref));
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("LDAP acceptor error, continuing -> {:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
info!("Stopped {}", super::TaskName::LdapActor);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn create_ldap_server(
|
pub(crate) async fn create_ldap_server(
|
||||||
address: &str,
|
address: &str,
|
||||||
opt_ssl_acceptor: Option<SslAcceptor>,
|
opt_ssl_acceptor: Option<SslAcceptor>,
|
||||||
|
@ -235,7 +197,10 @@ pub(crate) async fn create_ldap_server(
|
||||||
tls_acceptor_reload_rx,
|
tls_acceptor_reload_rx,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
None => tokio::spawn(ldap_plaintext_acceptor(listener, qe_r_ref, rx)),
|
None => {
|
||||||
|
error!("The server won't run without TLS!");
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
info!("Created LDAP interface");
|
info!("Created LDAP interface");
|
||||||
|
|
|
@ -36,10 +36,9 @@ mod ldaps;
|
||||||
mod repl;
|
mod repl;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use crate::actors::{QueryServerReadV1, QueryServerWriteV1};
|
use std::fmt::{Display, Formatter};
|
||||||
use crate::admin::AdminActor;
|
use std::sync::Arc;
|
||||||
use crate::config::{Configuration, ServerRole};
|
|
||||||
use crate::interval::IntervalActor;
|
|
||||||
use crate::utils::touch_file_or_quit;
|
use crate::utils::touch_file_or_quit;
|
||||||
use compact_jwt::{JwsHs256Signer, JwsSigner};
|
use compact_jwt::{JwsHs256Signer, JwsSigner};
|
||||||
use kanidm_proto::internal::OperationError;
|
use kanidm_proto::internal::OperationError;
|
||||||
|
@ -51,14 +50,17 @@ use kanidmd_lib::status::StatusActor;
|
||||||
use kanidmd_lib::value::CredentialType;
|
use kanidmd_lib::value::CredentialType;
|
||||||
#[cfg(not(target_family = "windows"))]
|
#[cfg(not(target_family = "windows"))]
|
||||||
use libc::umask;
|
use libc::umask;
|
||||||
use std::fmt::{Display, Formatter};
|
|
||||||
use std::path::Path;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
use tokio::sync::mpsc;
|
|
||||||
use tokio::sync::Notify;
|
use tokio::sync::Notify;
|
||||||
use tokio::task;
|
use tokio::task;
|
||||||
|
|
||||||
|
use crate::actors::{QueryServerReadV1, QueryServerWriteV1};
|
||||||
|
use crate::admin::AdminActor;
|
||||||
|
use crate::config::{Configuration, ServerRole};
|
||||||
|
use crate::interval::IntervalActor;
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
// === internal setup helpers
|
// === internal setup helpers
|
||||||
|
|
||||||
fn setup_backend(config: &Configuration, schema: &Schema) -> Result<Backend, OperationError> {
|
fn setup_backend(config: &Configuration, schema: &Schema) -> Result<Backend, OperationError> {
|
||||||
|
@ -78,7 +80,7 @@ fn setup_backend_vacuum(
|
||||||
let pool_size: u32 = config.threads as u32;
|
let pool_size: u32 = config.threads as u32;
|
||||||
|
|
||||||
let cfg = BackendConfig::new(
|
let cfg = BackendConfig::new(
|
||||||
config.db_path.as_deref(),
|
config.db_path.as_str(),
|
||||||
pool_size,
|
pool_size,
|
||||||
config.db_fs_type.unwrap_or_default(),
|
config.db_fs_type.unwrap_or_default(),
|
||||||
config.db_arc_size,
|
config.db_arc_size,
|
||||||
|
@ -333,7 +335,7 @@ pub fn dbscan_restore_quarantined_core(config: &Configuration, id: u64) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn backup_server_core(config: &Configuration, dst_path: &Path) {
|
pub fn backup_server_core(config: &Configuration, dst_path: &str) {
|
||||||
let schema = match Schema::new() {
|
let schema = match Schema::new() {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -369,11 +371,8 @@ pub fn backup_server_core(config: &Configuration, dst_path: &Path) {
|
||||||
// Let the txn abort, even on success.
|
// Let the txn abort, even on success.
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn restore_server_core(config: &Configuration, dst_path: &Path) {
|
pub async fn restore_server_core(config: &Configuration, dst_path: &str) {
|
||||||
// If it's an in memory database, we don't need to touch anything
|
touch_file_or_quit(config.db_path.as_str());
|
||||||
if let Some(db_path) = config.db_path.as_ref() {
|
|
||||||
touch_file_or_quit(db_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
// First, we provide the in-memory schema so that core attrs are indexed correctly.
|
// First, we provide the in-memory schema so that core attrs are indexed correctly.
|
||||||
let schema = match Schema::new() {
|
let schema = match Schema::new() {
|
||||||
|
@ -1012,7 +1011,7 @@ pub async fn create_server_core(
|
||||||
let tls_accepter_reload_task_notify = tls_acceptor_reload_notify.clone();
|
let tls_accepter_reload_task_notify = tls_acceptor_reload_notify.clone();
|
||||||
let tls_config = config.tls_config.clone();
|
let tls_config = config.tls_config.clone();
|
||||||
|
|
||||||
let ldap_configured = config.ldapbindaddress.is_some();
|
let ldap_configured = config.ldapaddress.is_some();
|
||||||
let (ldap_tls_acceptor_reload_tx, ldap_tls_acceptor_reload_rx) = mpsc::channel(1);
|
let (ldap_tls_acceptor_reload_tx, ldap_tls_acceptor_reload_rx) = mpsc::channel(1);
|
||||||
let (http_tls_acceptor_reload_tx, http_tls_acceptor_reload_rx) = mpsc::channel(1);
|
let (http_tls_acceptor_reload_tx, http_tls_acceptor_reload_rx) = mpsc::channel(1);
|
||||||
|
|
||||||
|
@ -1077,19 +1076,24 @@ pub async fn create_server_core(
|
||||||
};
|
};
|
||||||
|
|
||||||
// If we have been requested to init LDAP, configure it now.
|
// If we have been requested to init LDAP, configure it now.
|
||||||
let maybe_ldap_acceptor_handle = match &config.ldapbindaddress {
|
let maybe_ldap_acceptor_handle = match &config.ldapaddress {
|
||||||
Some(la) => {
|
Some(la) => {
|
||||||
let opt_ldap_ssl_acceptor = maybe_tls_acceptor.clone();
|
let opt_ldap_ssl_acceptor = maybe_tls_acceptor.clone();
|
||||||
|
|
||||||
let h = ldaps::create_ldap_server(
|
if !config_test {
|
||||||
la.as_str(),
|
// ⚠️ only start the sockets and listeners in non-config-test modes.
|
||||||
opt_ldap_ssl_acceptor,
|
let h = ldaps::create_ldap_server(
|
||||||
server_read_ref,
|
la.as_str(),
|
||||||
broadcast_tx.subscribe(),
|
opt_ldap_ssl_acceptor,
|
||||||
ldap_tls_acceptor_reload_rx,
|
server_read_ref,
|
||||||
)
|
broadcast_tx.subscribe(),
|
||||||
.await?;
|
ldap_tls_acceptor_reload_rx,
|
||||||
Some(h)
|
)
|
||||||
|
.await?;
|
||||||
|
Some(h)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
debug!("LDAP not requested, skipping");
|
debug!("LDAP not requested, skipping");
|
||||||
|
|
|
@ -1,39 +1,32 @@
|
||||||
use filetime::FileTime;
|
use filetime::FileTime;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
use std::path::Path;
|
use std::path::PathBuf;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
pub fn touch_file_or_quit<P: AsRef<Path>>(file_path: P) {
|
pub fn touch_file_or_quit(file_path: &str) {
|
||||||
/*
|
/*
|
||||||
Attempt to touch the file file_path, will quit the application if it fails for any reason.
|
Attempt to touch the file file_path, will quit the application if it fails for any reason.
|
||||||
|
|
||||||
Will also create a new file if it doesn't already exist.
|
Will also create a new file if it doesn't already exist.
|
||||||
*/
|
*/
|
||||||
|
if PathBuf::from(file_path).exists() {
|
||||||
let file_path: &Path = file_path.as_ref();
|
|
||||||
|
|
||||||
if file_path.exists() {
|
|
||||||
let t = FileTime::from_system_time(SystemTime::now());
|
let t = FileTime::from_system_time(SystemTime::now());
|
||||||
match filetime::set_file_times(file_path, t, t) {
|
match filetime::set_file_times(file_path, t, t) {
|
||||||
Ok(_) => debug!(
|
Ok(_) => debug!(
|
||||||
"Successfully touched existing file {}, can continue",
|
"Successfully touched existing file {}, can continue",
|
||||||
file_path.display()
|
file_path
|
||||||
),
|
),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
match e.kind() {
|
match e.kind() {
|
||||||
ErrorKind::PermissionDenied => {
|
ErrorKind::PermissionDenied => {
|
||||||
// we bail here because you won't be able to write them back...
|
// we bail here because you won't be able to write them back...
|
||||||
error!(
|
error!("Permission denied writing to {}, quitting.", file_path)
|
||||||
"Permission denied writing to {}, quitting.",
|
|
||||||
file_path.display()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
error!(
|
error!(
|
||||||
"Failed to write to {} due to error: {:?} ... quitting.",
|
"Failed to write to {} due to error: {:?} ... quitting.",
|
||||||
file_path.display(),
|
file_path, e
|
||||||
e
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,12 +35,11 @@ pub fn touch_file_or_quit<P: AsRef<Path>>(file_path: P) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match File::create(file_path) {
|
match File::create(file_path) {
|
||||||
Ok(_) => debug!("Successfully touched new file {}", file_path.display()),
|
Ok(_) => debug!("Successfully touched new file {}", file_path),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(
|
error!(
|
||||||
"Failed to write to {} due to error: {:?} ... quitting.",
|
"Failed to write to {} due to error: {:?} ... quitting.",
|
||||||
file_path.display(),
|
file_path, e
|
||||||
e
|
|
||||||
);
|
);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,31 +57,6 @@ clap = { workspace = true, features = ["derive"] }
|
||||||
clap_complete = { workspace = true }
|
clap_complete = { workspace = true }
|
||||||
kanidm_build_profiles = { workspace = true }
|
kanidm_build_profiles = { workspace = true }
|
||||||
|
|
||||||
## Debian packaging
|
|
||||||
[package.metadata.deb]
|
|
||||||
name = "kanidmd"
|
|
||||||
maintainer = "James Hodgkinson <james@terminaloutcomes.com>"
|
|
||||||
# Can't use $auto depends because the name of libssl3 varies by distro and version
|
|
||||||
depends = [
|
|
||||||
"libc6",
|
|
||||||
"tpm-udev",
|
|
||||||
"libssl3 | libssl3t64",
|
|
||||||
]
|
|
||||||
section = "network"
|
|
||||||
priority = "optional"
|
|
||||||
changelog = "../../target/debian/changelog" # Generated by platform/debian/build_debs.sh
|
|
||||||
assets = [
|
|
||||||
[ "target/release/kanidmd", "usr/bin/", "755" ],
|
|
||||||
[ "debian/group.conf", "usr/lib/sysusers.d/kandimd.conf", "644" ],
|
|
||||||
[ "debian/server.toml", "etc/kanidmd/server.toml", "640" ],
|
|
||||||
[ "../../examples/server.toml", "usr/share/kanidmd/", "444" ],
|
|
||||||
[ "../core/static/**/*", "usr/share/kanidmd/static", "444" ],
|
|
||||||
]
|
|
||||||
maintainer-scripts = "debian/"
|
|
||||||
systemd-units = [
|
|
||||||
{ unit-name = "kanidmd", enable = false}, # Cannot start without manual config
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
[package.metadata.cargo-machete]
|
[package.metadata.cargo-machete]
|
||||||
ignored = ["clap_complete", "kanidm_build_profiles"]
|
ignored = ["clap_complete", "kanidm_build_profiles"]
|
||||||
|
|
|
@ -10,15 +10,13 @@ Before=radiusd.service
|
||||||
[Service]
|
[Service]
|
||||||
Type=notify
|
Type=notify
|
||||||
DynamicUser=yes
|
DynamicUser=yes
|
||||||
User=kanidmd_dyn
|
StateDirectory=kanidm
|
||||||
Group=kanidmd
|
|
||||||
StateDirectory=kanidmd
|
|
||||||
StateDirectoryMode=0750
|
StateDirectoryMode=0750
|
||||||
CacheDirectory=kanidmd
|
CacheDirectory=kanidmd
|
||||||
CacheDirectoryMode=0750
|
CacheDirectoryMode=0750
|
||||||
RuntimeDirectory=kanidmd
|
RuntimeDirectory=kanidmd
|
||||||
RuntimeDirectoryMode=0755
|
RuntimeDirectoryMode=0755
|
||||||
ExecStart=/usr/bin/kanidmd server
|
ExecStart=/usr/sbin/kanidmd server -c /etc/kanidm/server.toml
|
||||||
|
|
||||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
|
@ -1,2 +0,0 @@
|
||||||
# This is a sysusers.d format config, please refer to man sysusers.d(5)
|
|
||||||
g kanidmd -
|
|
|
@ -1,38 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
# postinst script for kanidmd
|
|
||||||
#
|
|
||||||
# see: dh_installdeb(1)
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
|
|
||||||
case "$1" in
|
|
||||||
configure)
|
|
||||||
echo "Creating the kanidmd group for config & cert ownership..."
|
|
||||||
systemd-sysusers
|
|
||||||
echo "Fixing ownership of server configuration ..."
|
|
||||||
chown :kanidmd /etc/kanidmd/server.toml*
|
|
||||||
|
|
||||||
echo "============================="
|
|
||||||
echo "Thanks for installing Kanidm!"
|
|
||||||
echo "============================="
|
|
||||||
echo "Please ensure you modify the configuration file at /etc/kanidmd/server.toml"
|
|
||||||
echo "Only then: systemctl enable kanidmd.service"
|
|
||||||
echo "Full examples are in /usr/share/kanidmd/"
|
|
||||||
;;
|
|
||||||
|
|
||||||
abort-upgrade|abort-remove|abort-deconfigure)
|
|
||||||
;;
|
|
||||||
|
|
||||||
*)
|
|
||||||
echo "postinst called with unknown argument \`$1'" >&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# dh_installdeb will replace this with shell code automatically
|
|
||||||
# generated by other debhelper scripts.
|
|
||||||
|
|
||||||
#DEBHELPER#
|
|
||||||
|
|
||||||
exit 0
|
|
|
@ -1,51 +0,0 @@
|
||||||
# Kanidm server minimal configuration - /etc/kanidm/server.toml
|
|
||||||
# For a full example and documentation, see /usr/share/kanidmd/server.toml
|
|
||||||
# or `example/server.toml` in the source repository
|
|
||||||
|
|
||||||
# NOTE: You must configure at least domain & origin below to allow the server to start!
|
|
||||||
|
|
||||||
# The webserver bind address. Requires TLS certificates.
|
|
||||||
# If the port is set to 443 you may require the
|
|
||||||
# NET_BIND_SERVICE capability.
|
|
||||||
# Defaults to "127.0.0.1:8443"
|
|
||||||
bindaddress = "127.0.0.1:8443"
|
|
||||||
|
|
||||||
# The path to the kanidm database.
|
|
||||||
# The provided example uses systemd dynamic user pathing for security
|
|
||||||
db_path = "/var/lib/private/kanidmd/kanidm.db"
|
|
||||||
|
|
||||||
# TLS chain and key in pem format. Both must be present.
|
|
||||||
# If the server receives a SIGHUP, these files will be
|
|
||||||
# re-read and reloaded if their content is valid.
|
|
||||||
# These should be owned by root:kanidmd to give the service access.
|
|
||||||
tls_chain = "/etc/kanidmd/chain.pem"
|
|
||||||
tls_key = "/etc/kanidmd/key.pem"
|
|
||||||
|
|
||||||
log_level = "info"
|
|
||||||
|
|
||||||
# The DNS domain name of the server. This is used in a
|
|
||||||
# number of security-critical contexts
|
|
||||||
# such as webauthn, so it *must* match your DNS
|
|
||||||
#
|
|
||||||
# ⚠️ WARNING ⚠️
|
|
||||||
#
|
|
||||||
# Changing this value after first use WILL break many types of
|
|
||||||
# registered credentials for accounts including but not limited
|
|
||||||
# to: webauthn, oauth tokens, and more.
|
|
||||||
# If you change this value you *must* run
|
|
||||||
# `kanidmd domain rename` immediately after.
|
|
||||||
# NOTE: You must set this value!
|
|
||||||
#domain = "idm.example.com"
|
|
||||||
#
|
|
||||||
# The origin for webauthn. This is the url to the server,
|
|
||||||
# with the port included if it is non-standard (any port
|
|
||||||
# except 443). This must match or be a descendent of the
|
|
||||||
# domain name you configure above. If these two items are
|
|
||||||
# not consistent, the server WILL refuse to start!
|
|
||||||
# origin = "https://idm.example.com"
|
|
||||||
# NOTE: You must set this value!
|
|
||||||
#origin = "https://idm.example.com:8443"
|
|
||||||
|
|
||||||
[online_backup]
|
|
||||||
path = "/var/lib/private/kanidmd/backups/"
|
|
||||||
schedule = "00 22 * * *"
|
|
|
@ -22,16 +22,14 @@ fi
|
||||||
|
|
||||||
mkdir -p "${KANI_TMP}"/client_ca
|
mkdir -p "${KANI_TMP}"/client_ca
|
||||||
|
|
||||||
CONFIG_FILE=${CONFIG_FILE:="${SCRIPT_DIR}/insecure_server.toml"}
|
CONFIG_FILE=${CONFIG_FILE:="${SCRIPT_DIR}/../../examples/insecure_server.toml"}
|
||||||
|
|
||||||
if [ ! -f "${CONFIG_FILE}" ]; then
|
if [ ! -f "${CONFIG_FILE}" ]; then
|
||||||
echo "Couldn't find configuration file at ${CONFIG_FILE}, please ensure you're running this script from its base directory (${SCRIPT_DIR})."
|
echo "Couldn't find configuration file at ${CONFIG_FILE}, please ensure you're running this script from its base directory (${SCRIPT_DIR})."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Save current directory and change to script directory without pushd
|
pushd "${SCRIPT_DIR}" > /dev/null 2>&1
|
||||||
OLD_DIR=$(pwd)
|
|
||||||
cd "${SCRIPT_DIR}" || exit 1
|
|
||||||
if [ -n "${1}" ]; then
|
if [ -n "${1}" ]; then
|
||||||
COMMAND=$*
|
COMMAND=$*
|
||||||
#shellcheck disable=SC2086
|
#shellcheck disable=SC2086
|
||||||
|
@ -42,4 +40,4 @@ else
|
||||||
#shellcheck disable=SC2086
|
#shellcheck disable=SC2086
|
||||||
cargo run ${KANI_CARGO_OPTS} --bin kanidmd -- server -c "${CONFIG_FILE}"
|
cargo run ${KANI_CARGO_OPTS} --bin kanidmd -- server -c "${CONFIG_FILE}"
|
||||||
fi
|
fi
|
||||||
cd "${OLD_DIR}" || exit 1
|
popd > /dev/null 2>&1
|
||||||
|
|
|
@ -37,7 +37,7 @@ use kanidmd_core::admin::{
|
||||||
AdminTaskRequest, AdminTaskResponse, ClientCodec, ProtoDomainInfo,
|
AdminTaskRequest, AdminTaskResponse, ClientCodec, ProtoDomainInfo,
|
||||||
ProtoDomainUpgradeCheckReport, ProtoDomainUpgradeCheckStatus,
|
ProtoDomainUpgradeCheckReport, ProtoDomainUpgradeCheckStatus,
|
||||||
};
|
};
|
||||||
use kanidmd_core::config::{CliConfig, Configuration, EnvironmentConfig, ServerConfigUntagged};
|
use kanidmd_core::config::{Configuration, ServerConfig};
|
||||||
use kanidmd_core::{
|
use kanidmd_core::{
|
||||||
backup_server_core, cert_generate_core, create_server_core, dbscan_get_id2entry_core,
|
backup_server_core, cert_generate_core, create_server_core, dbscan_get_id2entry_core,
|
||||||
dbscan_list_id2entry_core, dbscan_list_index_analysis_core, dbscan_list_index_core,
|
dbscan_list_id2entry_core, dbscan_list_index_analysis_core, dbscan_list_index_core,
|
||||||
|
@ -379,13 +379,17 @@ fn check_file_ownership(opt: &KanidmdParser) -> Result<(), ExitCode> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// We have to do this because we can't use tracing until we've started the logging pipeline, and we can't start the logging pipeline until the tokio runtime's doing its thing.
|
// We have to do this because we can't use tracing until we've started the logging pipeline, and we can't start the logging pipeline until the tokio runtime's doing its thing.
|
||||||
async fn start_daemon(opt: KanidmdParser, config: Configuration) -> ExitCode {
|
async fn start_daemon(
|
||||||
|
opt: KanidmdParser,
|
||||||
|
mut config: Configuration,
|
||||||
|
sconfig: ServerConfig,
|
||||||
|
) -> ExitCode {
|
||||||
// if we have a server config and it has an OTEL URL, then we'll start the logging pipeline now.
|
// if we have a server config and it has an OTEL URL, then we'll start the logging pipeline now.
|
||||||
|
|
||||||
// TODO: only send to stderr when we're not in a TTY
|
// TODO: only send to stderr when we're not in a TTY
|
||||||
let sub = match sketching::otel::start_logging_pipeline(
|
let sub = match sketching::otel::start_logging_pipeline(
|
||||||
&config.otel_grpc_url,
|
&sconfig.otel_grpc_url,
|
||||||
config.log_level,
|
sconfig.log_level.unwrap_or_default(),
|
||||||
"kanidmd",
|
"kanidmd",
|
||||||
) {
|
) {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
@ -419,8 +423,8 @@ async fn start_daemon(opt: KanidmdParser, config: Configuration) -> ExitCode {
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(db_path) = config.db_path.as_ref() {
|
if let Some(db_path) = sconfig.db_path.as_ref() {
|
||||||
let db_pathbuf = db_path.to_path_buf();
|
let db_pathbuf = PathBuf::from(db_path.as_str());
|
||||||
// We can't check the db_path permissions because it may not exist yet!
|
// We can't check the db_path permissions because it may not exist yet!
|
||||||
if let Some(db_parent_path) = db_pathbuf.parent() {
|
if let Some(db_parent_path) = db_pathbuf.parent() {
|
||||||
if !db_parent_path.exists() {
|
if !db_parent_path.exists() {
|
||||||
|
@ -460,11 +464,33 @@ async fn start_daemon(opt: KanidmdParser, config: Configuration) -> ExitCode {
|
||||||
warn!("WARNING: DB folder {} has 'everyone' permission bits in the mode. This could be a security risk ...", db_par_path_buf.to_str().unwrap_or("invalid file path"));
|
warn!("WARNING: DB folder {} has 'everyone' permission bits in the mode. This could be a security risk ...", db_par_path_buf.to_str().unwrap_or("invalid file path"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
config.update_db_path(db_path);
|
||||||
} else {
|
} else {
|
||||||
error!("No db_path set in configuration, server startup will FAIL!");
|
error!("No db_path set in configuration, server startup will FAIL!");
|
||||||
return ExitCode::FAILURE;
|
return ExitCode::FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(origin) = sconfig.origin.clone() {
|
||||||
|
config.update_origin(&origin);
|
||||||
|
} else {
|
||||||
|
error!("No origin set in configuration, server startup will FAIL!");
|
||||||
|
return ExitCode::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(domain) = sconfig.domain.clone() {
|
||||||
|
config.update_domain(&domain);
|
||||||
|
} else {
|
||||||
|
error!("No domain set in configuration, server startup will FAIL!");
|
||||||
|
return ExitCode::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
config.update_db_arc_size(sconfig.get_db_arc_size());
|
||||||
|
config.update_role(sconfig.role);
|
||||||
|
config.update_output_mode(opt.commands.commonopt().output_mode.to_owned().into());
|
||||||
|
config.update_trust_x_forward_for(sconfig.trust_x_forward_for);
|
||||||
|
config.update_admin_bind_path(&sconfig.adminbindpath);
|
||||||
|
config.update_replication_config(sconfig.repl_config.clone());
|
||||||
|
|
||||||
match &opt.commands {
|
match &opt.commands {
|
||||||
// we aren't going to touch the DB so we can carry on
|
// we aren't going to touch the DB so we can carry on
|
||||||
KanidmdOpt::ShowReplicationCertificate { .. }
|
KanidmdOpt::ShowReplicationCertificate { .. }
|
||||||
|
@ -475,15 +501,19 @@ async fn start_daemon(opt: KanidmdParser, config: Configuration) -> ExitCode {
|
||||||
_ => {
|
_ => {
|
||||||
// Okay - Lets now create our lock and go.
|
// Okay - Lets now create our lock and go.
|
||||||
#[allow(clippy::expect_used)]
|
#[allow(clippy::expect_used)]
|
||||||
let klock_path = match config.db_path.clone() {
|
let klock_path = match sconfig.db_path.clone() {
|
||||||
Some(val) => val.with_extension("klock"),
|
Some(val) => format!("{}.klock", val),
|
||||||
None => std::env::temp_dir().join("kanidmd.klock"),
|
None => std::env::temp_dir()
|
||||||
|
.join("kanidmd.klock")
|
||||||
|
.to_str()
|
||||||
|
.expect("Unable to create klock path, this is a critical error!")
|
||||||
|
.to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let flock = match File::create(&klock_path) {
|
let flock = match File::create(&klock_path) {
|
||||||
Ok(flock) => flock,
|
Ok(flock) => flock,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("ERROR: Refusing to start - unable to create kanidmd exclusive lock at {} - {:?}", klock_path.display(), e);
|
error!("ERROR: Refusing to start - unable to create kanidmd exclusive lock at {} - {:?}", klock_path, e);
|
||||||
return ExitCode::FAILURE;
|
return ExitCode::FAILURE;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -491,7 +521,7 @@ async fn start_daemon(opt: KanidmdParser, config: Configuration) -> ExitCode {
|
||||||
match flock.try_lock_exclusive() {
|
match flock.try_lock_exclusive() {
|
||||||
Ok(()) => debug!("Acquired kanidm exclusive lock"),
|
Ok(()) => debug!("Acquired kanidm exclusive lock"),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("ERROR: Refusing to start - unable to lock kanidmd exclusive lock at {} - {:?}", klock_path.display(), e);
|
error!("ERROR: Refusing to start - unable to lock kanidmd exclusive lock at {} - {:?}", klock_path, e);
|
||||||
error!("Is another kanidmd process running?");
|
error!("Is another kanidmd process running?");
|
||||||
return ExitCode::FAILURE;
|
return ExitCode::FAILURE;
|
||||||
}
|
}
|
||||||
|
@ -499,7 +529,7 @@ async fn start_daemon(opt: KanidmdParser, config: Configuration) -> ExitCode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kanidm_main(config, opt).await
|
kanidm_main(sconfig, config, opt).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> ExitCode {
|
fn main() -> ExitCode {
|
||||||
|
@ -526,6 +556,10 @@ fn main() -> ExitCode {
|
||||||
return ExitCode::SUCCESS;
|
return ExitCode::SUCCESS;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//we set up a list of these so we can set the log config THEN log out the errors.
|
||||||
|
let mut config_error: Vec<String> = Vec::new();
|
||||||
|
let mut config = Configuration::new();
|
||||||
|
|
||||||
if env!("KANIDM_SERVER_CONFIG_PATH").is_empty() {
|
if env!("KANIDM_SERVER_CONFIG_PATH").is_empty() {
|
||||||
println!("CRITICAL: Kanidmd was not built correctly and is missing a valid KANIDM_SERVER_CONFIG_PATH value");
|
println!("CRITICAL: Kanidmd was not built correctly and is missing a valid KANIDM_SERVER_CONFIG_PATH value");
|
||||||
return ExitCode::FAILURE;
|
return ExitCode::FAILURE;
|
||||||
|
@ -547,56 +581,49 @@ fn main() -> ExitCode {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let maybe_sconfig = if let Some(config_path) = maybe_config_path {
|
let sconfig = match ServerConfig::new(maybe_config_path) {
|
||||||
match ServerConfigUntagged::new(config_path) {
|
Ok(c) => Some(c),
|
||||||
Ok(c) => Some(c),
|
Err(e) => {
|
||||||
Err(err) => {
|
config_error.push(format!("Config Parse failure {:?}", e));
|
||||||
eprintln!("ERROR: Configuration Parse Failure: {:?}", err);
|
|
||||||
return ExitCode::FAILURE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
eprintln!("WARNING: No configuration path was provided, relying on environment variables.");
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let envconfig = match EnvironmentConfig::new() {
|
|
||||||
Ok(ec) => ec,
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("ERROR: Environment Configuration Parse Failure: {:?}", err);
|
|
||||||
return ExitCode::FAILURE;
|
return ExitCode::FAILURE;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let cli_config = CliConfig {
|
|
||||||
output_mode: Some(opt.commands.commonopt().output_mode.to_owned().into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let is_server = matches!(&opt.commands, KanidmdOpt::Server(_));
|
|
||||||
|
|
||||||
let config = Configuration::build()
|
|
||||||
.add_env_config(envconfig)
|
|
||||||
.add_opt_toml_config(maybe_sconfig)
|
|
||||||
// We always set threads to 1 unless it's the main server.
|
|
||||||
.add_cli_config(cli_config)
|
|
||||||
.is_server_mode(is_server)
|
|
||||||
.finish();
|
|
||||||
|
|
||||||
let Some(config) = config else {
|
|
||||||
eprintln!(
|
|
||||||
"ERROR: Unable to build server configuration from provided configuration inputs."
|
|
||||||
);
|
|
||||||
return ExitCode::FAILURE;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ===========================================================================
|
|
||||||
// Config ready
|
|
||||||
|
|
||||||
// Get information on the windows username
|
// Get information on the windows username
|
||||||
#[cfg(target_family = "windows")]
|
#[cfg(target_family = "windows")]
|
||||||
get_user_details_windows();
|
get_user_details_windows();
|
||||||
|
|
||||||
|
if !config_error.is_empty() {
|
||||||
|
println!("There were errors on startup, which prevent the server from starting:");
|
||||||
|
for e in config_error {
|
||||||
|
println!(" - {}", e);
|
||||||
|
}
|
||||||
|
return ExitCode::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sconfig = match sconfig {
|
||||||
|
Some(val) => val,
|
||||||
|
None => {
|
||||||
|
println!("Somehow you got an empty ServerConfig after error checking? Cannot start!");
|
||||||
|
return ExitCode::FAILURE;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Config ready
|
||||||
|
|
||||||
|
// We always set threads to 1 unless it's the main server.
|
||||||
|
if matches!(&opt.commands, KanidmdOpt::Server(_)) {
|
||||||
|
// If not updated, will default to maximum
|
||||||
|
if let Some(threads) = sconfig.thread_count {
|
||||||
|
config.update_threads_count(threads);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
config.update_threads_count(1);
|
||||||
|
};
|
||||||
|
|
||||||
// Start the runtime
|
// Start the runtime
|
||||||
|
|
||||||
let maybe_rt = tokio::runtime::Builder::new_multi_thread()
|
let maybe_rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
.worker_threads(config.threads)
|
.worker_threads(config.threads)
|
||||||
.enable_all()
|
.enable_all()
|
||||||
|
@ -616,12 +643,16 @@ fn main() -> ExitCode {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
rt.block_on(start_daemon(opt, config))
|
rt.block_on(start_daemon(opt, config, sconfig))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build and execute the main server. The ServerConfig are the configuration options
|
/// Build and execute the main server. The ServerConfig are the configuration options
|
||||||
/// that we are processing into the config for the main server.
|
/// that we are processing into the config for the main server.
|
||||||
async fn kanidm_main(config: Configuration, opt: KanidmdParser) -> ExitCode {
|
async fn kanidm_main(
|
||||||
|
sconfig: ServerConfig,
|
||||||
|
mut config: Configuration,
|
||||||
|
opt: KanidmdParser,
|
||||||
|
) -> ExitCode {
|
||||||
match &opt.commands {
|
match &opt.commands {
|
||||||
KanidmdOpt::Server(_sopt) | KanidmdOpt::ConfigTest(_sopt) => {
|
KanidmdOpt::Server(_sopt) | KanidmdOpt::ConfigTest(_sopt) => {
|
||||||
let config_test = matches!(&opt.commands, KanidmdOpt::ConfigTest(_));
|
let config_test = matches!(&opt.commands, KanidmdOpt::ConfigTest(_));
|
||||||
|
@ -631,90 +662,88 @@ async fn kanidm_main(config: Configuration, opt: KanidmdParser) -> ExitCode {
|
||||||
info!("Running in server mode ...");
|
info!("Running in server mode ...");
|
||||||
};
|
};
|
||||||
|
|
||||||
// Verify the TLs configs.
|
// configuration options that only relate to server mode
|
||||||
if let Some(tls_config) = config.tls_config.as_ref() {
|
config.update_config_for_server_mode(&sconfig);
|
||||||
{
|
|
||||||
let i_meta = match metadata(&tls_config.chain) {
|
|
||||||
Ok(m) => m,
|
|
||||||
Err(e) => {
|
|
||||||
error!(
|
|
||||||
"Unable to read metadata for TLS chain file '{}' - {:?}",
|
|
||||||
tls_config.chain.display(),
|
|
||||||
e
|
|
||||||
);
|
|
||||||
let diag =
|
|
||||||
kanidm_lib_file_permissions::diagnose_path(&tls_config.chain);
|
|
||||||
info!(%diag);
|
|
||||||
return ExitCode::FAILURE;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if !kanidm_lib_file_permissions::readonly(&i_meta) {
|
|
||||||
warn!("permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ...", tls_config.chain.display());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
if let Some(i_str) = &(sconfig.tls_chain) {
|
||||||
let i_meta = match metadata(&tls_config.key) {
|
let i_path = PathBuf::from(i_str.as_str());
|
||||||
Ok(m) => m,
|
let i_meta = match metadata(&i_path) {
|
||||||
Err(e) => {
|
Ok(m) => m,
|
||||||
error!(
|
Err(e) => {
|
||||||
"Unable to read metadata for TLS key file '{}' - {:?}",
|
|
||||||
tls_config.key.display(),
|
|
||||||
e
|
|
||||||
);
|
|
||||||
let diag = kanidm_lib_file_permissions::diagnose_path(&tls_config.key);
|
|
||||||
info!(%diag);
|
|
||||||
return ExitCode::FAILURE;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if !kanidm_lib_file_permissions::readonly(&i_meta) {
|
|
||||||
warn!("permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ...", tls_config.key.display());
|
|
||||||
}
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
if i_meta.mode() & 0o007 != 0 {
|
|
||||||
warn!("WARNING: {} has 'everyone' permission bits in the mode. This could be a security risk ...", tls_config.key.display());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(ca_dir) = tls_config.client_ca.as_ref() {
|
|
||||||
// check that the TLS client CA config option is what we expect
|
|
||||||
let ca_dir_path = PathBuf::from(&ca_dir);
|
|
||||||
if !ca_dir_path.exists() {
|
|
||||||
error!(
|
error!(
|
||||||
"TLS CA folder {} does not exist, server startup will FAIL!",
|
"Unable to read metadata for TLS chain file '{}' - {:?}",
|
||||||
ca_dir.display()
|
&i_path.to_str().unwrap_or("invalid file path"),
|
||||||
|
e
|
||||||
);
|
);
|
||||||
let diag = kanidm_lib_file_permissions::diagnose_path(&ca_dir_path);
|
let diag = kanidm_lib_file_permissions::diagnose_path(&i_path);
|
||||||
info!(%diag);
|
info!(%diag);
|
||||||
}
|
|
||||||
|
|
||||||
let i_meta = match metadata(&ca_dir_path) {
|
|
||||||
Ok(m) => m,
|
|
||||||
Err(e) => {
|
|
||||||
error!(
|
|
||||||
"Unable to read metadata for '{}' - {:?}",
|
|
||||||
ca_dir.display(),
|
|
||||||
e
|
|
||||||
);
|
|
||||||
let diag = kanidm_lib_file_permissions::diagnose_path(&ca_dir_path);
|
|
||||||
info!(%diag);
|
|
||||||
return ExitCode::FAILURE;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if !i_meta.is_dir() {
|
|
||||||
error!(
|
|
||||||
"ERROR: Refusing to run - TLS Client CA folder {} may not be a directory",
|
|
||||||
ca_dir.display()
|
|
||||||
);
|
|
||||||
return ExitCode::FAILURE;
|
return ExitCode::FAILURE;
|
||||||
}
|
}
|
||||||
if kanidm_lib_file_permissions::readonly(&i_meta) {
|
};
|
||||||
warn!("WARNING: TLS Client CA folder permissions on {} indicate it may not be RW. This could cause the server start up to fail!", ca_dir.display());
|
if !kanidm_lib_file_permissions::readonly(&i_meta) {
|
||||||
|
warn!("permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ...", i_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(i_str) = &(sconfig.tls_key) {
|
||||||
|
let i_path = PathBuf::from(i_str.as_str());
|
||||||
|
|
||||||
|
let i_meta = match metadata(&i_path) {
|
||||||
|
Ok(m) => m,
|
||||||
|
Err(e) => {
|
||||||
|
error!(
|
||||||
|
"Unable to read metadata for TLS key file '{}' - {:?}",
|
||||||
|
&i_path.to_str().unwrap_or("invalid file path"),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
let diag = kanidm_lib_file_permissions::diagnose_path(&i_path);
|
||||||
|
info!(%diag);
|
||||||
|
return ExitCode::FAILURE;
|
||||||
}
|
}
|
||||||
#[cfg(not(target_os = "windows"))]
|
};
|
||||||
if i_meta.mode() & 0o007 != 0 {
|
if !kanidm_lib_file_permissions::readonly(&i_meta) {
|
||||||
warn!("WARNING: TLS Client CA folder {} has 'everyone' permission bits in the mode. This could be a security risk ...", ca_dir.display());
|
warn!("permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ...", i_str);
|
||||||
|
}
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
if i_meta.mode() & 0o007 != 0 {
|
||||||
|
warn!("WARNING: {} has 'everyone' permission bits in the mode. This could be a security risk ...", i_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ca_dir) = &(sconfig.tls_client_ca) {
|
||||||
|
// check that the TLS client CA config option is what we expect
|
||||||
|
let ca_dir_path = PathBuf::from(&ca_dir);
|
||||||
|
if !ca_dir_path.exists() {
|
||||||
|
error!(
|
||||||
|
"TLS CA folder {} does not exist, server startup will FAIL!",
|
||||||
|
ca_dir
|
||||||
|
);
|
||||||
|
let diag = kanidm_lib_file_permissions::diagnose_path(&ca_dir_path);
|
||||||
|
info!(%diag);
|
||||||
|
}
|
||||||
|
|
||||||
|
let i_meta = match metadata(&ca_dir_path) {
|
||||||
|
Ok(m) => m,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Unable to read metadata for '{}' - {:?}", ca_dir, e);
|
||||||
|
let diag = kanidm_lib_file_permissions::diagnose_path(&ca_dir_path);
|
||||||
|
info!(%diag);
|
||||||
|
return ExitCode::FAILURE;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
if !i_meta.is_dir() {
|
||||||
|
error!(
|
||||||
|
"ERROR: Refusing to run - TLS Client CA folder {} may not be a directory",
|
||||||
|
ca_dir
|
||||||
|
);
|
||||||
|
return ExitCode::FAILURE;
|
||||||
|
}
|
||||||
|
if kanidm_lib_file_permissions::readonly(&i_meta) {
|
||||||
|
warn!("WARNING: TLS Client CA folder permissions on {} indicate it may not be RW. This could cause the server start up to fail!", ca_dir);
|
||||||
|
}
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
if i_meta.mode() & 0o007 != 0 {
|
||||||
|
warn!("WARNING: TLS Client CA folder {} has 'everyone' permission bits in the mode. This could be a security risk ...", ca_dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -724,6 +753,14 @@ async fn kanidm_main(config: Configuration, opt: KanidmdParser) -> ExitCode {
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
{
|
{
|
||||||
let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Ready]);
|
let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Ready]);
|
||||||
|
// Undocumented systemd feature - all messages should have a monotonic usec sent
|
||||||
|
// with them. In some cases like "reloading" messages, it is undocumented but
|
||||||
|
// failure to send this message causes the reload to fail.
|
||||||
|
if let Ok(monotonic_usec) = sd_notify::NotifyState::monotonic_usec_now() {
|
||||||
|
let _ = sd_notify::notify(true, &[monotonic_usec]);
|
||||||
|
} else {
|
||||||
|
error!("CRITICAL!!! Unable to access clock monotonic time. SYSTEMD WILL KILL US.");
|
||||||
|
};
|
||||||
let _ = sd_notify::notify(
|
let _ = sd_notify::notify(
|
||||||
true,
|
true,
|
||||||
&[sd_notify::NotifyState::Status("Started Kanidm 🦀")],
|
&[sd_notify::NotifyState::Status("Started Kanidm 🦀")],
|
||||||
|
@ -737,80 +774,86 @@ async fn kanidm_main(config: Configuration, opt: KanidmdParser) -> ExitCode {
|
||||||
{
|
{
|
||||||
let mut listener = sctx.subscribe();
|
let mut listener = sctx.subscribe();
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
Ok(()) = tokio::signal::ctrl_c() => {
|
Ok(()) = tokio::signal::ctrl_c() => {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
Some(()) = async move {
|
Some(()) = async move {
|
||||||
let sigterm = tokio::signal::unix::SignalKind::terminate();
|
let sigterm = tokio::signal::unix::SignalKind::terminate();
|
||||||
#[allow(clippy::unwrap_used)]
|
#[allow(clippy::unwrap_used)]
|
||||||
tokio::signal::unix::signal(sigterm).unwrap().recv().await
|
tokio::signal::unix::signal(sigterm).unwrap().recv().await
|
||||||
} => {
|
} => {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
Some(()) = async move {
|
Some(()) = async move {
|
||||||
let sigterm = tokio::signal::unix::SignalKind::alarm();
|
let sigterm = tokio::signal::unix::SignalKind::alarm();
|
||||||
#[allow(clippy::unwrap_used)]
|
#[allow(clippy::unwrap_used)]
|
||||||
tokio::signal::unix::signal(sigterm).unwrap().recv().await
|
tokio::signal::unix::signal(sigterm).unwrap().recv().await
|
||||||
} => {
|
} => {
|
||||||
// Ignore
|
// Ignore
|
||||||
}
|
}
|
||||||
Some(()) = async move {
|
Some(()) = async move {
|
||||||
let sigterm = tokio::signal::unix::SignalKind::hangup();
|
let sigterm = tokio::signal::unix::SignalKind::hangup();
|
||||||
#[allow(clippy::unwrap_used)]
|
#[allow(clippy::unwrap_used)]
|
||||||
tokio::signal::unix::signal(sigterm).unwrap().recv().await
|
tokio::signal::unix::signal(sigterm).unwrap().recv().await
|
||||||
} => {
|
} => {
|
||||||
// Reload TLS certificates
|
// Reload TLS certificates
|
||||||
// systemd has a special reload handler for this.
|
// systemd has a special reload handler for this.
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
{
|
{
|
||||||
if let Ok(monotonic_usec) = sd_notify::NotifyState::monotonic_usec_now() {
|
let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Reloading]);
|
||||||
let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Reloading, monotonic_usec]);
|
// CRITICAL - if you do not send a monotonic usec message after a reloading
|
||||||
let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Status("Reloading ...")]);
|
// message, your service WILL BE KILLED.
|
||||||
} else {
|
if let Ok(monotonic_usec) = sd_notify::NotifyState::monotonic_usec_now() {
|
||||||
error!("CRITICAL!!! Unable to access clock monotonic time. SYSTEMD WILL KILL US.");
|
let _ =
|
||||||
};
|
sd_notify::notify(true, &[monotonic_usec]);
|
||||||
}
|
} else {
|
||||||
|
error!("CRITICAL!!! Unable to access clock monotonic time. SYSTEMD WILL KILL US.");
|
||||||
|
};
|
||||||
|
let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Status("Reloading ...")]);
|
||||||
|
}
|
||||||
|
|
||||||
sctx.tls_acceptor_reload().await;
|
sctx.tls_acceptor_reload().await;
|
||||||
|
|
||||||
// Systemd freaks out if you send the ready state too fast after the
|
// Systemd freaks out if you send the ready state too fast after the
|
||||||
// reload state and can kill Kanidmd as a result.
|
// reload state and can kill Kanidmd as a result.
|
||||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
{
|
{
|
||||||
if let Ok(monotonic_usec) = sd_notify::NotifyState::monotonic_usec_now() {
|
let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Ready]);
|
||||||
let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Ready, monotonic_usec]);
|
if let Ok(monotonic_usec) = sd_notify::NotifyState::monotonic_usec_now() {
|
||||||
let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Status("Reload Success")]);
|
let _ =
|
||||||
} else {
|
sd_notify::notify(true, &[monotonic_usec]);
|
||||||
error!("CRITICAL!!! Unable to access clock monotonic time. SYSTEMD WILL KILL US.");
|
} else {
|
||||||
};
|
error!("CRITICAL!!! Unable to access clock monotonic time. SYSTEMD WILL KILL US.");
|
||||||
}
|
};
|
||||||
|
let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Status("Reload Success")]);
|
||||||
|
}
|
||||||
|
|
||||||
info!("Reload complete");
|
info!("Reload complete");
|
||||||
}
|
}
|
||||||
Some(()) = async move {
|
Some(()) = async move {
|
||||||
let sigterm = tokio::signal::unix::SignalKind::user_defined1();
|
let sigterm = tokio::signal::unix::SignalKind::user_defined1();
|
||||||
#[allow(clippy::unwrap_used)]
|
#[allow(clippy::unwrap_used)]
|
||||||
tokio::signal::unix::signal(sigterm).unwrap().recv().await
|
tokio::signal::unix::signal(sigterm).unwrap().recv().await
|
||||||
} => {
|
} => {
|
||||||
// Ignore
|
// Ignore
|
||||||
}
|
}
|
||||||
Some(()) = async move {
|
Some(()) = async move {
|
||||||
let sigterm = tokio::signal::unix::SignalKind::user_defined2();
|
let sigterm = tokio::signal::unix::SignalKind::user_defined2();
|
||||||
#[allow(clippy::unwrap_used)]
|
#[allow(clippy::unwrap_used)]
|
||||||
tokio::signal::unix::signal(sigterm).unwrap().recv().await
|
tokio::signal::unix::signal(sigterm).unwrap().recv().await
|
||||||
} => {
|
} => {
|
||||||
// Ignore
|
// Ignore
|
||||||
}
|
}
|
||||||
// we got a message on thr broadcast from somewhere else
|
// we got a message on thr broadcast from somewhere else
|
||||||
Ok(msg) = async move {
|
Ok(msg) = async move {
|
||||||
listener.recv().await
|
listener.recv().await
|
||||||
} => {
|
} => {
|
||||||
debug!("Main loop received message: {:?}", msg);
|
debug!("Main loop received message: {:?}", msg);
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(target_family = "windows")]
|
#[cfg(target_family = "windows")]
|
||||||
{
|
{
|
||||||
|
@ -837,19 +880,34 @@ async fn kanidm_main(config: Configuration, opt: KanidmdParser) -> ExitCode {
|
||||||
}
|
}
|
||||||
KanidmdOpt::CertGenerate(_sopt) => {
|
KanidmdOpt::CertGenerate(_sopt) => {
|
||||||
info!("Running in certificate generate mode ...");
|
info!("Running in certificate generate mode ...");
|
||||||
|
config.update_config_for_server_mode(&sconfig);
|
||||||
cert_generate_core(&config);
|
cert_generate_core(&config);
|
||||||
}
|
}
|
||||||
KanidmdOpt::Database {
|
KanidmdOpt::Database {
|
||||||
commands: DbCommands::Backup(bopt),
|
commands: DbCommands::Backup(bopt),
|
||||||
} => {
|
} => {
|
||||||
info!("Running in backup mode ...");
|
info!("Running in backup mode ...");
|
||||||
backup_server_core(&config, &bopt.path);
|
let p = match bopt.path.to_str() {
|
||||||
|
Some(p) => p,
|
||||||
|
None => {
|
||||||
|
error!("Invalid backup path");
|
||||||
|
return ExitCode::FAILURE;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
backup_server_core(&config, p);
|
||||||
}
|
}
|
||||||
KanidmdOpt::Database {
|
KanidmdOpt::Database {
|
||||||
commands: DbCommands::Restore(ropt),
|
commands: DbCommands::Restore(ropt),
|
||||||
} => {
|
} => {
|
||||||
info!("Running in restore mode ...");
|
info!("Running in restore mode ...");
|
||||||
restore_server_core(&config, &ropt.path).await;
|
let p = match ropt.path.to_str() {
|
||||||
|
Some(p) => p,
|
||||||
|
None => {
|
||||||
|
error!("Invalid restore path");
|
||||||
|
return ExitCode::FAILURE;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
restore_server_core(&config, p).await;
|
||||||
}
|
}
|
||||||
KanidmdOpt::Database {
|
KanidmdOpt::Database {
|
||||||
commands: DbCommands::Verify(_vopt),
|
commands: DbCommands::Verify(_vopt),
|
||||||
|
@ -1030,6 +1088,8 @@ async fn kanidm_main(config: Configuration, opt: KanidmdParser) -> ExitCode {
|
||||||
vacuum_server_core(&config);
|
vacuum_server_core(&config);
|
||||||
}
|
}
|
||||||
KanidmdOpt::HealthCheck(sopt) => {
|
KanidmdOpt::HealthCheck(sopt) => {
|
||||||
|
config.update_config_for_server_mode(&sconfig);
|
||||||
|
|
||||||
debug!("{sopt:?}");
|
debug!("{sopt:?}");
|
||||||
|
|
||||||
let healthcheck_url = match &sopt.check_origin {
|
let healthcheck_url = match &sopt.check_origin {
|
||||||
|
@ -1050,15 +1110,12 @@ async fn kanidm_main(config: Configuration, opt: KanidmdParser) -> ExitCode {
|
||||||
.danger_accept_invalid_hostnames(!sopt.verify_tls)
|
.danger_accept_invalid_hostnames(!sopt.verify_tls)
|
||||||
.https_only(true);
|
.https_only(true);
|
||||||
|
|
||||||
client = match &config.tls_config {
|
client = match &sconfig.tls_chain {
|
||||||
None => client,
|
None => client,
|
||||||
Some(tls_config) => {
|
Some(ca_cert) => {
|
||||||
debug!(
|
debug!("Trying to load {} to build a CA cert path", ca_cert);
|
||||||
"Trying to load {} to build a CA cert path",
|
|
||||||
tls_config.chain.display()
|
|
||||||
);
|
|
||||||
// if the ca_cert file exists, then we'll use it
|
// if the ca_cert file exists, then we'll use it
|
||||||
let ca_cert_path = tls_config.chain.clone();
|
let ca_cert_path = PathBuf::from(ca_cert);
|
||||||
match ca_cert_path.exists() {
|
match ca_cert_path.exists() {
|
||||||
true => {
|
true => {
|
||||||
let mut cert_buf = Vec::new();
|
let mut cert_buf = Vec::new();
|
||||||
|
@ -1091,10 +1148,7 @@ async fn kanidm_main(config: Configuration, opt: KanidmdParser) -> ExitCode {
|
||||||
client
|
client
|
||||||
}
|
}
|
||||||
false => {
|
false => {
|
||||||
warn!(
|
warn!("Couldn't find ca cert {} but carrying on...", ca_cert);
|
||||||
"Couldn't find ca cert {} but carrying on...",
|
|
||||||
tls_config.chain.display()
|
|
||||||
);
|
|
||||||
client
|
client
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ fn parse_attributes(
|
||||||
});
|
});
|
||||||
|
|
||||||
if !args_are_allowed {
|
if !args_are_allowed {
|
||||||
let msg = "Invalid test config attribute. The following are allowed";
|
let msg = "Invalid test config attribute. The following are allow";
|
||||||
return Err(syn::Error::new_spanned(
|
return Err(syn::Error::new_spanned(
|
||||||
input.sig.fn_token,
|
input.sig.fn_token,
|
||||||
format!("{}: {}", msg, ALLOWED_ATTRIBUTES.join(", ")),
|
format!("{}: {}", msg, ALLOWED_ATTRIBUTES.join(", ")),
|
||||||
|
|
|
@ -1,21 +1,27 @@
|
||||||
|
use std::collections::{BTreeMap, BTreeSet, VecDeque};
|
||||||
|
use std::convert::{TryFrom, TryInto};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use super::keystorage::{KeyHandle, KeyHandleId};
|
use super::keystorage::{KeyHandle, KeyHandleId};
|
||||||
|
|
||||||
|
// use crate::valueset;
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
use idlset::v2::IDLBitRange;
|
||||||
|
use kanidm_proto::internal::{ConsistencyError, OperationError};
|
||||||
|
use rusqlite::vtab::array::Array;
|
||||||
|
use rusqlite::{Connection, OpenFlags, OptionalExtension};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::be::dbentry::DbIdentSpn;
|
use crate::be::dbentry::DbIdentSpn;
|
||||||
use crate::be::dbvalue::DbCidV1;
|
use crate::be::dbvalue::DbCidV1;
|
||||||
use crate::be::{BackendConfig, IdList, IdRawEntry, IdxKey, IdxSlope};
|
use crate::be::{BackendConfig, IdList, IdRawEntry, IdxKey, IdxSlope};
|
||||||
use crate::entry::{Entry, EntryCommitted, EntrySealed};
|
use crate::entry::{Entry, EntryCommitted, EntrySealed};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::value::{IndexType, Value};
|
use crate::value::{IndexType, Value};
|
||||||
use hashbrown::HashMap;
|
|
||||||
use idlset::v2::IDLBitRange;
|
// use uuid::Uuid;
|
||||||
use kanidm_proto::internal::{ConsistencyError, OperationError};
|
|
||||||
use rusqlite::vtab::array::Array;
|
|
||||||
use rusqlite::{Connection, OpenFlags, OptionalExtension};
|
|
||||||
use std::collections::{BTreeMap, BTreeSet, VecDeque};
|
|
||||||
use std::convert::{TryFrom, TryInto};
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::sync::Mutex;
|
|
||||||
use std::time::Duration;
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
const DBV_ID2ENTRY: &str = "id2entry";
|
const DBV_ID2ENTRY: &str = "id2entry";
|
||||||
const DBV_INDEXV: &str = "indexv";
|
const DBV_INDEXV: &str = "indexv";
|
||||||
|
@ -199,7 +205,7 @@ pub(crate) trait IdlSqliteTransaction {
|
||||||
let mut stmt = self
|
let mut stmt = self
|
||||||
.get_conn()?
|
.get_conn()?
|
||||||
.prepare(&format!(
|
.prepare(&format!(
|
||||||
"SELECT rowid from {}.sqlite_master where type=\"table\" AND name = :tname LIMIT 1",
|
"SELECT rowid from {}.sqlite_master where name = :tname LIMIT 1",
|
||||||
self.get_db_name()
|
self.get_db_name()
|
||||||
))
|
))
|
||||||
.map_err(sqlite_error)?;
|
.map_err(sqlite_error)?;
|
||||||
|
@ -1706,7 +1712,7 @@ impl IdlSqliteWriteTransaction {
|
||||||
|
|
||||||
impl IdlSqlite {
|
impl IdlSqlite {
|
||||||
pub fn new(cfg: &BackendConfig, vacuum: bool) -> Result<Self, OperationError> {
|
pub fn new(cfg: &BackendConfig, vacuum: bool) -> Result<Self, OperationError> {
|
||||||
if cfg.path.as_os_str().is_empty() {
|
if cfg.path.is_empty() {
|
||||||
debug_assert_eq!(cfg.pool_size, 1);
|
debug_assert_eq!(cfg.pool_size, 1);
|
||||||
}
|
}
|
||||||
// If provided, set the page size to match the tuning we want. By default we use 4096. The VACUUM
|
// If provided, set the page size to match the tuning we want. By default we use 4096. The VACUUM
|
||||||
|
@ -1728,7 +1734,8 @@ impl IdlSqlite {
|
||||||
|
|
||||||
// Initial setup routines.
|
// Initial setup routines.
|
||||||
{
|
{
|
||||||
let vconn = Connection::open_with_flags(&cfg.path, flags).map_err(sqlite_error)?;
|
let vconn =
|
||||||
|
Connection::open_with_flags(cfg.path.as_str(), flags).map_err(sqlite_error)?;
|
||||||
|
|
||||||
vconn
|
vconn
|
||||||
.execute_batch(
|
.execute_batch(
|
||||||
|
@ -1757,7 +1764,8 @@ impl IdlSqlite {
|
||||||
);
|
);
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let vconn = Connection::open_with_flags(&cfg.path, flags).map_err(sqlite_error)?;
|
let vconn =
|
||||||
|
Connection::open_with_flags(cfg.path.as_str(), flags).map_err(sqlite_error)?;
|
||||||
|
|
||||||
vconn
|
vconn
|
||||||
.execute_batch("PRAGMA wal_checkpoint(TRUNCATE);")
|
.execute_batch("PRAGMA wal_checkpoint(TRUNCATE);")
|
||||||
|
@ -1778,7 +1786,8 @@ impl IdlSqlite {
|
||||||
OperationError::SqliteError
|
OperationError::SqliteError
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let vconn = Connection::open_with_flags(&cfg.path, flags).map_err(sqlite_error)?;
|
let vconn =
|
||||||
|
Connection::open_with_flags(cfg.path.as_str(), flags).map_err(sqlite_error)?;
|
||||||
|
|
||||||
vconn
|
vconn
|
||||||
.pragma_update(None, "page_size", cfg.fstype as u32)
|
.pragma_update(None, "page_size", cfg.fstype as u32)
|
||||||
|
@ -1812,7 +1821,7 @@ impl IdlSqlite {
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
trace!("Opening Connection {}", i);
|
trace!("Opening Connection {}", i);
|
||||||
let conn =
|
let conn =
|
||||||
Connection::open_with_flags(&cfg.path, flags).map_err(sqlite_error);
|
Connection::open_with_flags(cfg.path.as_str(), flags).map_err(sqlite_error);
|
||||||
match conn {
|
match conn {
|
||||||
Ok(conn) => {
|
Ok(conn) => {
|
||||||
// We need to set the cachesize at this point as well.
|
// We need to set the cachesize at this point as well.
|
||||||
|
|
|
@ -4,6 +4,20 @@
|
||||||
//! is to persist content safely to disk, load that content, and execute queries
|
//! is to persist content safely to disk, load that content, and execute queries
|
||||||
//! utilising indexes in the most effective way possible.
|
//! utilising indexes in the most effective way possible.
|
||||||
|
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::fs;
|
||||||
|
use std::ops::DerefMut;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use concread::cowcell::*;
|
||||||
|
use hashbrown::{HashMap as Map, HashSet};
|
||||||
|
use idlset::v2::IDLBitRange;
|
||||||
|
use idlset::AndNot;
|
||||||
|
use kanidm_proto::internal::{ConsistencyError, OperationError};
|
||||||
|
use tracing::{trace, trace_span};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::be::dbentry::{DbBackup, DbEntry};
|
use crate::be::dbentry::{DbBackup, DbEntry};
|
||||||
use crate::be::dbrepl::DbReplMeta;
|
use crate::be::dbrepl::DbReplMeta;
|
||||||
use crate::entry::Entry;
|
use crate::entry::Entry;
|
||||||
|
@ -17,19 +31,6 @@ use crate::repl::ruv::{
|
||||||
};
|
};
|
||||||
use crate::utils::trigraph_iter;
|
use crate::utils::trigraph_iter;
|
||||||
use crate::value::{IndexType, Value};
|
use crate::value::{IndexType, Value};
|
||||||
use concread::cowcell::*;
|
|
||||||
use hashbrown::{HashMap as Map, HashSet};
|
|
||||||
use idlset::v2::IDLBitRange;
|
|
||||||
use idlset::AndNot;
|
|
||||||
use kanidm_proto::internal::{ConsistencyError, OperationError};
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use std::fs;
|
|
||||||
use std::ops::DerefMut;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::time::Duration;
|
|
||||||
use tracing::{trace, trace_span};
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
pub(crate) mod dbentry;
|
pub(crate) mod dbentry;
|
||||||
pub(crate) mod dbrepl;
|
pub(crate) mod dbrepl;
|
||||||
|
@ -131,7 +132,7 @@ impl IdxMeta {
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct BackendConfig {
|
pub struct BackendConfig {
|
||||||
path: PathBuf,
|
path: String,
|
||||||
pool_size: u32,
|
pool_size: u32,
|
||||||
db_name: &'static str,
|
db_name: &'static str,
|
||||||
fstype: FsType,
|
fstype: FsType,
|
||||||
|
@ -140,16 +141,10 @@ pub struct BackendConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BackendConfig {
|
impl BackendConfig {
|
||||||
pub fn new(
|
pub fn new(path: &str, pool_size: u32, fstype: FsType, arcsize: Option<usize>) -> Self {
|
||||||
path: Option<&Path>,
|
|
||||||
pool_size: u32,
|
|
||||||
fstype: FsType,
|
|
||||||
arcsize: Option<usize>,
|
|
||||||
) -> Self {
|
|
||||||
BackendConfig {
|
BackendConfig {
|
||||||
pool_size,
|
pool_size,
|
||||||
// This means if path is None, that "" implies an sqlite in memory/ram only database.
|
path: path.to_string(),
|
||||||
path: path.unwrap_or_else(|| Path::new("")).to_path_buf(),
|
|
||||||
db_name: "main",
|
db_name: "main",
|
||||||
fstype,
|
fstype,
|
||||||
arcsize,
|
arcsize,
|
||||||
|
@ -159,7 +154,7 @@ impl BackendConfig {
|
||||||
pub(crate) fn new_test(db_name: &'static str) -> Self {
|
pub(crate) fn new_test(db_name: &'static str) -> Self {
|
||||||
BackendConfig {
|
BackendConfig {
|
||||||
pool_size: 1,
|
pool_size: 1,
|
||||||
path: PathBuf::from(""),
|
path: "".to_string(),
|
||||||
db_name,
|
db_name,
|
||||||
fstype: FsType::Generic,
|
fstype: FsType::Generic,
|
||||||
arcsize: Some(2048),
|
arcsize: Some(2048),
|
||||||
|
@ -554,11 +549,10 @@ pub trait BackendTransaction {
|
||||||
}
|
}
|
||||||
(_, fp) => {
|
(_, fp) => {
|
||||||
plan.push(fp);
|
plan.push(fp);
|
||||||
let setplan = FilterPlan::InclusionInvalid(plan);
|
filter_error!(
|
||||||
error!(
|
|
||||||
?setplan,
|
|
||||||
"Inclusion is unable to proceed - all terms must be fully indexed!"
|
"Inclusion is unable to proceed - all terms must be fully indexed!"
|
||||||
);
|
);
|
||||||
|
let setplan = FilterPlan::InclusionInvalid(plan);
|
||||||
return Ok((IdList::Partial(IDLBitRange::new()), setplan));
|
return Ok((IdList::Partial(IDLBitRange::new()), setplan));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -941,7 +935,7 @@ pub trait BackendTransaction {
|
||||||
self.get_ruv().verify(&entries, results);
|
self.get_ruv().verify(&entries, results);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn backup(&mut self, dst_path: &Path) -> Result<(), OperationError> {
|
fn backup(&mut self, dst_path: &str) -> Result<(), OperationError> {
|
||||||
let repl_meta = self.get_ruv().to_db_backup_ruv();
|
let repl_meta = self.get_ruv().to_db_backup_ruv();
|
||||||
|
|
||||||
// load all entries into RAM, may need to change this later
|
// load all entries into RAM, may need to change this later
|
||||||
|
@ -1433,16 +1427,20 @@ impl<'a> BackendWriteTransaction<'a> {
|
||||||
if self.is_idx_slopeyness_generated()? {
|
if self.is_idx_slopeyness_generated()? {
|
||||||
trace!("Indexing slopes available");
|
trace!("Indexing slopes available");
|
||||||
} else {
|
} else {
|
||||||
warn!("No indexing slopes available. You should consider reindexing to generate these");
|
admin_warn!(
|
||||||
|
"No indexing slopes available. You should consider reindexing to generate these"
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Setup idxkeys here. By default we set these all to "max slope" aka
|
// Setup idxkeys here. By default we set these all to "max slope" aka
|
||||||
// all indexes are "equal" but also worse case unless analysed. If they
|
// all indexes are "equal" but also worse case unless analysed. If they
|
||||||
// have been analysed, we can set the slope factor into here.
|
// have been analysed, we can set the slope factor into here.
|
||||||
let mut idxkeys = idxkeys
|
let idxkeys: Result<Map<_, _>, _> = idxkeys
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|k| self.get_idx_slope(&k).map(|slope| (k, slope)))
|
.map(|k| self.get_idx_slope(&k).map(|slope| (k, slope)))
|
||||||
.collect::<Result<Map<_, _>, _>>()?;
|
.collect();
|
||||||
|
|
||||||
|
let mut idxkeys = idxkeys?;
|
||||||
|
|
||||||
std::mem::swap(&mut self.idxmeta_wr.deref_mut().idxkeys, &mut idxkeys);
|
std::mem::swap(&mut self.idxmeta_wr.deref_mut().idxkeys, &mut idxkeys);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1813,7 +1811,7 @@ impl<'a> BackendWriteTransaction<'a> {
|
||||||
Ok(slope)
|
Ok(slope)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn restore(&mut self, src_path: &Path) -> Result<(), OperationError> {
|
pub fn restore(&mut self, src_path: &str) -> Result<(), OperationError> {
|
||||||
let serialized_string = fs::read_to_string(src_path).map_err(|e| {
|
let serialized_string = fs::read_to_string(src_path).map_err(|e| {
|
||||||
admin_error!("fs::read_to_string {:?}", e);
|
admin_error!("fs::read_to_string {:?}", e);
|
||||||
OperationError::FsError
|
OperationError::FsError
|
||||||
|
@ -2126,7 +2124,7 @@ impl Backend {
|
||||||
debug!(db_tickets = ?cfg.pool_size, profile = %env!("KANIDM_PROFILE_NAME"), cpu_flags = %env!("KANIDM_CPU_FLAGS"));
|
debug!(db_tickets = ?cfg.pool_size, profile = %env!("KANIDM_PROFILE_NAME"), cpu_flags = %env!("KANIDM_CPU_FLAGS"));
|
||||||
|
|
||||||
// If in memory, reduce pool to 1
|
// If in memory, reduce pool to 1
|
||||||
if cfg.path.as_os_str().is_empty() {
|
if cfg.path.is_empty() {
|
||||||
cfg.pool_size = 1;
|
cfg.pool_size = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2212,6 +2210,13 @@ impl Backend {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::fs;
|
||||||
|
use std::iter::FromIterator;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use idlset::v2::IDLBitRange;
|
||||||
|
|
||||||
use super::super::entry::{Entry, EntryInit, EntryNew};
|
use super::super::entry::{Entry, EntryInit, EntryNew};
|
||||||
use super::Limits;
|
use super::Limits;
|
||||||
use super::{
|
use super::{
|
||||||
|
@ -2221,12 +2226,6 @@ mod tests {
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::repl::cid::Cid;
|
use crate::repl::cid::Cid;
|
||||||
use crate::value::{IndexType, PartialValue, Value};
|
use crate::value::{IndexType, PartialValue, Value};
|
||||||
use idlset::v2::IDLBitRange;
|
|
||||||
use std::fs;
|
|
||||||
use std::iter::FromIterator;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref CID_ZERO: Cid = Cid::new_zero();
|
static ref CID_ZERO: Cid = Cid::new_zero();
|
||||||
|
@ -2601,9 +2600,11 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_be_backup_restore() {
|
fn test_be_backup_restore() {
|
||||||
let db_backup_file_name =
|
let db_backup_file_name = format!(
|
||||||
Path::new(option_env!("OUT_DIR").unwrap_or("/tmp")).join(".backup_test.json");
|
"{}/.backup_test.json",
|
||||||
eprintln!(" ⚠️ {}", db_backup_file_name.display());
|
option_env!("OUT_DIR").unwrap_or("/tmp")
|
||||||
|
);
|
||||||
|
eprintln!(" ⚠️ {db_backup_file_name}");
|
||||||
run_test!(|be: &mut BackendWriteTransaction| {
|
run_test!(|be: &mut BackendWriteTransaction| {
|
||||||
// Important! Need db metadata setup!
|
// Important! Need db metadata setup!
|
||||||
be.reset_db_s_uuid().unwrap();
|
be.reset_db_s_uuid().unwrap();
|
||||||
|
@ -2658,9 +2659,11 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_be_backup_restore_tampered() {
|
fn test_be_backup_restore_tampered() {
|
||||||
let db_backup_file_name =
|
let db_backup_file_name = format!(
|
||||||
Path::new(option_env!("OUT_DIR").unwrap_or("/tmp")).join(".backup2_test.json");
|
"{}/.backup2_test.json",
|
||||||
eprintln!(" ⚠️ {}", db_backup_file_name.display());
|
option_env!("OUT_DIR").unwrap_or("/tmp")
|
||||||
|
);
|
||||||
|
eprintln!(" ⚠️ {db_backup_file_name}");
|
||||||
run_test!(|be: &mut BackendWriteTransaction| {
|
run_test!(|be: &mut BackendWriteTransaction| {
|
||||||
// Important! Need db metadata setup!
|
// Important! Need db metadata setup!
|
||||||
be.reset_db_s_uuid().unwrap();
|
be.reset_db_s_uuid().unwrap();
|
||||||
|
|
|
@ -136,6 +136,8 @@ pub const UUID_SCHEMA_ATTR_PRIMARY_CREDENTIAL: Uuid = uuid!("00000000-0000-0000-
|
||||||
pub const UUID_SCHEMA_CLASS_PERSON: Uuid = uuid!("00000000-0000-0000-0000-ffff00000044");
|
pub const UUID_SCHEMA_CLASS_PERSON: Uuid = uuid!("00000000-0000-0000-0000-ffff00000044");
|
||||||
pub const UUID_SCHEMA_CLASS_GROUP: Uuid = uuid!("00000000-0000-0000-0000-ffff00000045");
|
pub const UUID_SCHEMA_CLASS_GROUP: Uuid = uuid!("00000000-0000-0000-0000-ffff00000045");
|
||||||
pub const UUID_SCHEMA_CLASS_ACCOUNT: Uuid = uuid!("00000000-0000-0000-0000-ffff00000046");
|
pub const UUID_SCHEMA_CLASS_ACCOUNT: Uuid = uuid!("00000000-0000-0000-0000-ffff00000046");
|
||||||
|
pub const UUID_SCHEMA_ATTR_LDAP_MAXIMUM_QUERYABLE_ATTRIBUTES: Uuid =
|
||||||
|
uuid!("00000000-0000-0000-0000-ffff00000187");
|
||||||
pub const UUID_SCHEMA_ATTR_ATTRIBUTENAME: Uuid = uuid!("00000000-0000-0000-0000-ffff00000048");
|
pub const UUID_SCHEMA_ATTR_ATTRIBUTENAME: Uuid = uuid!("00000000-0000-0000-0000-ffff00000048");
|
||||||
pub const UUID_SCHEMA_ATTR_CLASSNAME: Uuid = uuid!("00000000-0000-0000-0000-ffff00000049");
|
pub const UUID_SCHEMA_ATTR_CLASSNAME: Uuid = uuid!("00000000-0000-0000-0000-ffff00000049");
|
||||||
pub const UUID_SCHEMA_ATTR_LEGALNAME: Uuid = uuid!("00000000-0000-0000-0000-ffff00000050");
|
pub const UUID_SCHEMA_ATTR_LEGALNAME: Uuid = uuid!("00000000-0000-0000-0000-ffff00000050");
|
||||||
|
@ -327,13 +329,6 @@ pub const UUID_SCHEMA_ATTR_ALLOW_PRIMARY_CRED_FALLBACK: Uuid =
|
||||||
uuid!("00000000-0000-0000-0000-ffff00000185");
|
uuid!("00000000-0000-0000-0000-ffff00000185");
|
||||||
pub const UUID_SCHEMA_ATTR_DOMAIN_ALLOW_EASTER_EGGS: Uuid =
|
pub const UUID_SCHEMA_ATTR_DOMAIN_ALLOW_EASTER_EGGS: Uuid =
|
||||||
uuid!("00000000-0000-0000-0000-ffff00000186");
|
uuid!("00000000-0000-0000-0000-ffff00000186");
|
||||||
pub const UUID_SCHEMA_ATTR_LDAP_MAXIMUM_QUERYABLE_ATTRIBUTES: Uuid =
|
|
||||||
uuid!("00000000-0000-0000-0000-ffff00000187");
|
|
||||||
pub const UUID_SCHEMA_ATTR_INDEXED: Uuid = uuid!("00000000-0000-0000-0000-ffff00000188");
|
|
||||||
pub const UUID_SCHEMA_ATTR_ACP_MODIFY_PRESENT_CLASS: Uuid =
|
|
||||||
uuid!("00000000-0000-0000-0000-ffff00000189");
|
|
||||||
pub const UUID_SCHEMA_ATTR_ACP_MODIFY_REMOVE_CLASS: Uuid =
|
|
||||||
uuid!("00000000-0000-0000-0000-ffff00000190");
|
|
||||||
|
|
||||||
// System and domain infos
|
// System and domain infos
|
||||||
// I'd like to strongly criticise william of the past for making poor choices about these allocations.
|
// I'd like to strongly criticise william of the past for making poor choices about these allocations.
|
||||||
|
|
|
@ -492,97 +492,6 @@ impl Entry<EntryInit, EntryNew> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<SchemaAttribute> for EntryInitNew {
|
|
||||||
fn from(value: SchemaAttribute) -> Self {
|
|
||||||
EntryInitNew::from(&value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&SchemaAttribute> for EntryInitNew {
|
|
||||||
fn from(s: &SchemaAttribute) -> Self {
|
|
||||||
// Build the Map of the attributes
|
|
||||||
let mut attrs = Eattrs::new();
|
|
||||||
attrs.insert(Attribute::AttributeName, vs_iutf8![s.name.as_str()]);
|
|
||||||
attrs.insert(Attribute::Description, vs_utf8![s.description.to_owned()]);
|
|
||||||
attrs.insert(Attribute::Uuid, vs_uuid![s.uuid]);
|
|
||||||
attrs.insert(Attribute::MultiValue, vs_bool![s.multivalue]);
|
|
||||||
attrs.insert(Attribute::Phantom, vs_bool![s.phantom]);
|
|
||||||
attrs.insert(Attribute::SyncAllowed, vs_bool![s.sync_allowed]);
|
|
||||||
attrs.insert(Attribute::Replicated, vs_bool![s.replicated.into()]);
|
|
||||||
attrs.insert(Attribute::Unique, vs_bool![s.unique]);
|
|
||||||
attrs.insert(Attribute::Indexed, vs_bool![s.indexed]);
|
|
||||||
attrs.insert(Attribute::Syntax, vs_syntax![s.syntax]);
|
|
||||||
attrs.insert(
|
|
||||||
Attribute::Class,
|
|
||||||
vs_iutf8![
|
|
||||||
EntryClass::Object.into(),
|
|
||||||
EntryClass::System.into(),
|
|
||||||
EntryClass::AttributeType.into()
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Insert stuff.
|
|
||||||
|
|
||||||
Entry {
|
|
||||||
valid: EntryInit,
|
|
||||||
state: EntryNew,
|
|
||||||
attrs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<SchemaClass> for EntryInitNew {
|
|
||||||
fn from(value: SchemaClass) -> Self {
|
|
||||||
EntryInitNew::from(&value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&SchemaClass> for EntryInitNew {
|
|
||||||
fn from(s: &SchemaClass) -> Self {
|
|
||||||
let mut attrs = Eattrs::new();
|
|
||||||
attrs.insert(Attribute::ClassName, vs_iutf8![s.name.as_str()]);
|
|
||||||
attrs.insert(Attribute::Description, vs_utf8![s.description.to_owned()]);
|
|
||||||
attrs.insert(Attribute::SyncAllowed, vs_bool![s.sync_allowed]);
|
|
||||||
attrs.insert(Attribute::Uuid, vs_uuid![s.uuid]);
|
|
||||||
attrs.insert(
|
|
||||||
Attribute::Class,
|
|
||||||
vs_iutf8![
|
|
||||||
EntryClass::Object.into(),
|
|
||||||
EntryClass::System.into(),
|
|
||||||
EntryClass::ClassType.into()
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
let vs_systemmay = ValueSetIutf8::from_iter(s.systemmay.iter().map(|sm| sm.as_str()));
|
|
||||||
if let Some(vs) = vs_systemmay {
|
|
||||||
attrs.insert(Attribute::SystemMay, vs);
|
|
||||||
}
|
|
||||||
|
|
||||||
let vs_systemmust = ValueSetIutf8::from_iter(s.systemmust.iter().map(|sm| sm.as_str()));
|
|
||||||
if let Some(vs) = vs_systemmust {
|
|
||||||
attrs.insert(Attribute::SystemMust, vs);
|
|
||||||
}
|
|
||||||
|
|
||||||
let vs_systemexcludes =
|
|
||||||
ValueSetIutf8::from_iter(s.systemexcludes.iter().map(|sm| sm.as_str()));
|
|
||||||
if let Some(vs) = vs_systemexcludes {
|
|
||||||
attrs.insert(Attribute::SystemExcludes, vs);
|
|
||||||
}
|
|
||||||
|
|
||||||
let vs_systemsupplements =
|
|
||||||
ValueSetIutf8::from_iter(s.systemsupplements.iter().map(|sm| sm.as_str()));
|
|
||||||
if let Some(vs) = vs_systemsupplements {
|
|
||||||
attrs.insert(Attribute::SystemSupplements, vs);
|
|
||||||
}
|
|
||||||
|
|
||||||
Entry {
|
|
||||||
valid: EntryInit,
|
|
||||||
state: EntryNew,
|
|
||||||
attrs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Entry<EntryRefresh, EntryNew> {
|
impl Entry<EntryRefresh, EntryNew> {
|
||||||
pub fn from_repl_entry_v1(repl_entry: ReplEntryV1) -> Result<Self, OperationError> {
|
pub fn from_repl_entry_v1(repl_entry: ReplEntryV1) -> Result<Self, OperationError> {
|
||||||
// From the entry, we have to rebuild the ecstate and the attrs.
|
// From the entry, we have to rebuild the ecstate and the attrs.
|
||||||
|
@ -2040,7 +1949,7 @@ impl<STATE> Entry<EntryValid, STATE> {
|
||||||
};
|
};
|
||||||
|
|
||||||
if !valid_supplements {
|
if !valid_supplements {
|
||||||
warn!(
|
admin_warn!(
|
||||||
"Validation error, the following possible supplement classes are missing - {:?}",
|
"Validation error, the following possible supplement classes are missing - {:?}",
|
||||||
supplements_classes
|
supplements_classes
|
||||||
);
|
);
|
||||||
|
@ -2724,6 +2633,21 @@ impl<VALID, STATE> Entry<VALID, STATE> {
|
||||||
// These are special types to allow returning typed values from
|
// These are special types to allow returning typed values from
|
||||||
// an entry, if we "know" what we expect to receive.
|
// an entry, if we "know" what we expect to receive.
|
||||||
|
|
||||||
|
/// This returns an array of IndexTypes, when the type is an Optional
|
||||||
|
/// multivalue in schema - IE this will *not* fail if the attribute is
|
||||||
|
/// empty, yielding and empty array instead.
|
||||||
|
///
|
||||||
|
/// However, the conversion to IndexType is fallible, so in case of a failure
|
||||||
|
/// to convert, an empty vec is returned
|
||||||
|
pub(crate) fn get_ava_opt_index<A: AsRef<Attribute>>(&self, attr: A) -> Option<Vec<IndexType>> {
|
||||||
|
if let Some(vs) = self.get_ava_set(attr) {
|
||||||
|
vs.as_indextype_iter().map(|i| i.collect())
|
||||||
|
} else {
|
||||||
|
// Empty, but consider as valid.
|
||||||
|
Some(vec![])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return a single value of this attributes name, or `None` if it is NOT present, or
|
/// Return a single value of this attributes name, or `None` if it is NOT present, or
|
||||||
/// there are multiple values present (ambiguous).
|
/// there are multiple values present (ambiguous).
|
||||||
pub fn get_ava_single<A: AsRef<Attribute>>(&self, attr: A) -> Option<Value> {
|
pub fn get_ava_single<A: AsRef<Attribute>>(&self, attr: A) -> Option<Value> {
|
||||||
|
@ -3336,6 +3260,97 @@ impl<VALID, STATE> PartialEq for Entry<VALID, STATE> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<&SchemaAttribute> for Entry<EntryInit, EntryNew> {
|
||||||
|
fn from(s: &SchemaAttribute) -> Self {
|
||||||
|
// Convert an Attribute to an entry ... make it good!
|
||||||
|
let uuid_v = vs_uuid![s.uuid];
|
||||||
|
let name_v = vs_iutf8![s.name.as_str()];
|
||||||
|
let desc_v = vs_utf8![s.description.to_owned()];
|
||||||
|
|
||||||
|
let multivalue_v = vs_bool![s.multivalue];
|
||||||
|
let sync_allowed_v = vs_bool![s.sync_allowed];
|
||||||
|
let replicated_v = vs_bool![s.replicated];
|
||||||
|
let phantom_v = vs_bool![s.phantom];
|
||||||
|
let unique_v = vs_bool![s.unique];
|
||||||
|
|
||||||
|
let index_v = ValueSetIndex::from_iter(s.index.iter().copied());
|
||||||
|
|
||||||
|
let syntax_v = vs_syntax![s.syntax];
|
||||||
|
|
||||||
|
// Build the Map of the attributes relevant
|
||||||
|
// let mut attrs: Map<AttrString, Set<Value>> = Map::with_capacity(8);
|
||||||
|
let mut attrs: Map<Attribute, ValueSet> = Map::new();
|
||||||
|
attrs.insert(Attribute::AttributeName, name_v);
|
||||||
|
attrs.insert(Attribute::Description, desc_v);
|
||||||
|
attrs.insert(Attribute::Uuid, uuid_v);
|
||||||
|
attrs.insert(Attribute::MultiValue, multivalue_v);
|
||||||
|
attrs.insert(Attribute::Phantom, phantom_v);
|
||||||
|
attrs.insert(Attribute::SyncAllowed, sync_allowed_v);
|
||||||
|
attrs.insert(Attribute::Replicated, replicated_v);
|
||||||
|
attrs.insert(Attribute::Unique, unique_v);
|
||||||
|
if let Some(vs) = index_v {
|
||||||
|
attrs.insert(Attribute::Index, vs);
|
||||||
|
}
|
||||||
|
attrs.insert(Attribute::Syntax, syntax_v);
|
||||||
|
attrs.insert(
|
||||||
|
Attribute::Class,
|
||||||
|
vs_iutf8![
|
||||||
|
EntryClass::Object.into(),
|
||||||
|
EntryClass::System.into(),
|
||||||
|
EntryClass::AttributeType.into()
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Insert stuff.
|
||||||
|
|
||||||
|
Entry {
|
||||||
|
valid: EntryInit,
|
||||||
|
state: EntryNew,
|
||||||
|
attrs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&SchemaClass> for Entry<EntryInit, EntryNew> {
|
||||||
|
fn from(s: &SchemaClass) -> Self {
|
||||||
|
let uuid_v = vs_uuid![s.uuid];
|
||||||
|
let name_v = vs_iutf8![s.name.as_str()];
|
||||||
|
let desc_v = vs_utf8![s.description.to_owned()];
|
||||||
|
let sync_allowed_v = vs_bool![s.sync_allowed];
|
||||||
|
|
||||||
|
let mut attrs: Map<Attribute, ValueSet> = Map::new();
|
||||||
|
attrs.insert(Attribute::ClassName, name_v);
|
||||||
|
attrs.insert(Attribute::Description, desc_v);
|
||||||
|
attrs.insert(Attribute::SyncAllowed, sync_allowed_v);
|
||||||
|
attrs.insert(Attribute::Uuid, uuid_v);
|
||||||
|
attrs.insert(
|
||||||
|
Attribute::Class,
|
||||||
|
vs_iutf8![
|
||||||
|
EntryClass::Object.into(),
|
||||||
|
EntryClass::System.into(),
|
||||||
|
EntryClass::ClassType.into()
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
let vs_systemmay = ValueSetIutf8::from_iter(s.systemmay.iter().map(|sm| sm.as_str()));
|
||||||
|
if let Some(vs) = vs_systemmay {
|
||||||
|
attrs.insert(Attribute::SystemMay, vs);
|
||||||
|
}
|
||||||
|
|
||||||
|
let vs_systemmust = ValueSetIutf8::from_iter(s.systemmust.iter().map(|sm| sm.as_str()));
|
||||||
|
|
||||||
|
if let Some(vs) = vs_systemmust {
|
||||||
|
attrs.insert(Attribute::SystemMust, vs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Entry {
|
||||||
|
valid: EntryInit,
|
||||||
|
state: EntryNew,
|
||||||
|
attrs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
|
@ -527,8 +527,7 @@ impl Filter<FilterValid> {
|
||||||
// cases! The exception is *large* filters, especially from the memberof plugin. We
|
// cases! The exception is *large* filters, especially from the memberof plugin. We
|
||||||
// want to skip these because they can really jam up the server.
|
// want to skip these because they can really jam up the server.
|
||||||
|
|
||||||
// Don't cache anything unless we have valid indexing metadata.
|
let cacheable = FilterResolved::resolve_cacheable(&self.state.inner);
|
||||||
let cacheable = idxmeta.is_some() && FilterResolved::resolve_cacheable(&self.state.inner);
|
|
||||||
|
|
||||||
let cache_key = if cacheable {
|
let cache_key = if cacheable {
|
||||||
// do we have a cache?
|
// do we have a cache?
|
||||||
|
@ -537,7 +536,6 @@ impl Filter<FilterValid> {
|
||||||
let cache_key = (ev.get_event_origin_id(), Arc::new(self.clone()));
|
let cache_key = (ev.get_event_origin_id(), Arc::new(self.clone()));
|
||||||
if let Some(f) = rcache.get(&cache_key) {
|
if let Some(f) = rcache.get(&cache_key) {
|
||||||
// Got it? Shortcut and return!
|
// Got it? Shortcut and return!
|
||||||
trace!("shortcut: a resolved filter already exists.");
|
|
||||||
return Ok(f.as_ref().clone());
|
return Ok(f.as_ref().clone());
|
||||||
};
|
};
|
||||||
// Not in cache? Set the cache_key.
|
// Not in cache? Set the cache_key.
|
||||||
|
@ -576,7 +574,6 @@ impl Filter<FilterValid> {
|
||||||
// if cacheable == false.
|
// if cacheable == false.
|
||||||
if let Some(cache_key) = cache_key {
|
if let Some(cache_key) = cache_key {
|
||||||
if let Some(rcache) = rsv_cache.as_mut() {
|
if let Some(rcache) = rsv_cache.as_mut() {
|
||||||
trace!(?resolved_filt, "inserting filter to resolved cache");
|
|
||||||
rcache.insert(cache_key, Arc::new(resolved_filt.clone()));
|
rcache.insert(cache_key, Arc::new(resolved_filt.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -599,19 +599,19 @@ impl IdmServerProxyWriteTransaction<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let eperm_search_primary_cred = match &eperm.search {
|
let eperm_search_primary_cred = match &eperm.search {
|
||||||
Access::Deny => false,
|
Access::Denied => false,
|
||||||
Access::Grant => true,
|
Access::Grant => true,
|
||||||
Access::Allow(attrs) => attrs.contains(&Attribute::PrimaryCredential),
|
Access::Allow(attrs) => attrs.contains(&Attribute::PrimaryCredential),
|
||||||
};
|
};
|
||||||
|
|
||||||
let eperm_mod_primary_cred = match &eperm.modify_pres {
|
let eperm_mod_primary_cred = match &eperm.modify_pres {
|
||||||
Access::Deny => false,
|
Access::Denied => false,
|
||||||
Access::Grant => true,
|
Access::Grant => true,
|
||||||
Access::Allow(attrs) => attrs.contains(&Attribute::PrimaryCredential),
|
Access::Allow(attrs) => attrs.contains(&Attribute::PrimaryCredential),
|
||||||
};
|
};
|
||||||
|
|
||||||
let eperm_rem_primary_cred = match &eperm.modify_rem {
|
let eperm_rem_primary_cred = match &eperm.modify_rem {
|
||||||
Access::Deny => false,
|
Access::Denied => false,
|
||||||
Access::Grant => true,
|
Access::Grant => true,
|
||||||
Access::Allow(attrs) => attrs.contains(&Attribute::PrimaryCredential),
|
Access::Allow(attrs) => attrs.contains(&Attribute::PrimaryCredential),
|
||||||
};
|
};
|
||||||
|
@ -620,19 +620,19 @@ impl IdmServerProxyWriteTransaction<'_> {
|
||||||
eperm_search_primary_cred && eperm_mod_primary_cred && eperm_rem_primary_cred;
|
eperm_search_primary_cred && eperm_mod_primary_cred && eperm_rem_primary_cred;
|
||||||
|
|
||||||
let eperm_search_passkeys = match &eperm.search {
|
let eperm_search_passkeys = match &eperm.search {
|
||||||
Access::Deny => false,
|
Access::Denied => false,
|
||||||
Access::Grant => true,
|
Access::Grant => true,
|
||||||
Access::Allow(attrs) => attrs.contains(&Attribute::PassKeys),
|
Access::Allow(attrs) => attrs.contains(&Attribute::PassKeys),
|
||||||
};
|
};
|
||||||
|
|
||||||
let eperm_mod_passkeys = match &eperm.modify_pres {
|
let eperm_mod_passkeys = match &eperm.modify_pres {
|
||||||
Access::Deny => false,
|
Access::Denied => false,
|
||||||
Access::Grant => true,
|
Access::Grant => true,
|
||||||
Access::Allow(attrs) => attrs.contains(&Attribute::PassKeys),
|
Access::Allow(attrs) => attrs.contains(&Attribute::PassKeys),
|
||||||
};
|
};
|
||||||
|
|
||||||
let eperm_rem_passkeys = match &eperm.modify_rem {
|
let eperm_rem_passkeys = match &eperm.modify_rem {
|
||||||
Access::Deny => false,
|
Access::Denied => false,
|
||||||
Access::Grant => true,
|
Access::Grant => true,
|
||||||
Access::Allow(attrs) => attrs.contains(&Attribute::PassKeys),
|
Access::Allow(attrs) => attrs.contains(&Attribute::PassKeys),
|
||||||
};
|
};
|
||||||
|
@ -640,19 +640,19 @@ impl IdmServerProxyWriteTransaction<'_> {
|
||||||
let passkeys_can_edit = eperm_search_passkeys && eperm_mod_passkeys && eperm_rem_passkeys;
|
let passkeys_can_edit = eperm_search_passkeys && eperm_mod_passkeys && eperm_rem_passkeys;
|
||||||
|
|
||||||
let eperm_search_attested_passkeys = match &eperm.search {
|
let eperm_search_attested_passkeys = match &eperm.search {
|
||||||
Access::Deny => false,
|
Access::Denied => false,
|
||||||
Access::Grant => true,
|
Access::Grant => true,
|
||||||
Access::Allow(attrs) => attrs.contains(&Attribute::AttestedPasskeys),
|
Access::Allow(attrs) => attrs.contains(&Attribute::AttestedPasskeys),
|
||||||
};
|
};
|
||||||
|
|
||||||
let eperm_mod_attested_passkeys = match &eperm.modify_pres {
|
let eperm_mod_attested_passkeys = match &eperm.modify_pres {
|
||||||
Access::Deny => false,
|
Access::Denied => false,
|
||||||
Access::Grant => true,
|
Access::Grant => true,
|
||||||
Access::Allow(attrs) => attrs.contains(&Attribute::AttestedPasskeys),
|
Access::Allow(attrs) => attrs.contains(&Attribute::AttestedPasskeys),
|
||||||
};
|
};
|
||||||
|
|
||||||
let eperm_rem_attested_passkeys = match &eperm.modify_rem {
|
let eperm_rem_attested_passkeys = match &eperm.modify_rem {
|
||||||
Access::Deny => false,
|
Access::Denied => false,
|
||||||
Access::Grant => true,
|
Access::Grant => true,
|
||||||
Access::Allow(attrs) => attrs.contains(&Attribute::AttestedPasskeys),
|
Access::Allow(attrs) => attrs.contains(&Attribute::AttestedPasskeys),
|
||||||
};
|
};
|
||||||
|
@ -662,19 +662,19 @@ impl IdmServerProxyWriteTransaction<'_> {
|
||||||
&& eperm_rem_attested_passkeys;
|
&& eperm_rem_attested_passkeys;
|
||||||
|
|
||||||
let eperm_search_unixcred = match &eperm.search {
|
let eperm_search_unixcred = match &eperm.search {
|
||||||
Access::Deny => false,
|
Access::Denied => false,
|
||||||
Access::Grant => true,
|
Access::Grant => true,
|
||||||
Access::Allow(attrs) => attrs.contains(&Attribute::UnixPassword),
|
Access::Allow(attrs) => attrs.contains(&Attribute::UnixPassword),
|
||||||
};
|
};
|
||||||
|
|
||||||
let eperm_mod_unixcred = match &eperm.modify_pres {
|
let eperm_mod_unixcred = match &eperm.modify_pres {
|
||||||
Access::Deny => false,
|
Access::Denied => false,
|
||||||
Access::Grant => true,
|
Access::Grant => true,
|
||||||
Access::Allow(attrs) => attrs.contains(&Attribute::UnixPassword),
|
Access::Allow(attrs) => attrs.contains(&Attribute::UnixPassword),
|
||||||
};
|
};
|
||||||
|
|
||||||
let eperm_rem_unixcred = match &eperm.modify_rem {
|
let eperm_rem_unixcred = match &eperm.modify_rem {
|
||||||
Access::Deny => false,
|
Access::Denied => false,
|
||||||
Access::Grant => true,
|
Access::Grant => true,
|
||||||
Access::Allow(attrs) => attrs.contains(&Attribute::UnixPassword),
|
Access::Allow(attrs) => attrs.contains(&Attribute::UnixPassword),
|
||||||
};
|
};
|
||||||
|
@ -685,19 +685,19 @@ impl IdmServerProxyWriteTransaction<'_> {
|
||||||
&& eperm_rem_unixcred;
|
&& eperm_rem_unixcred;
|
||||||
|
|
||||||
let eperm_search_sshpubkey = match &eperm.search {
|
let eperm_search_sshpubkey = match &eperm.search {
|
||||||
Access::Deny => false,
|
Access::Denied => false,
|
||||||
Access::Grant => true,
|
Access::Grant => true,
|
||||||
Access::Allow(attrs) => attrs.contains(&Attribute::SshPublicKey),
|
Access::Allow(attrs) => attrs.contains(&Attribute::SshPublicKey),
|
||||||
};
|
};
|
||||||
|
|
||||||
let eperm_mod_sshpubkey = match &eperm.modify_pres {
|
let eperm_mod_sshpubkey = match &eperm.modify_pres {
|
||||||
Access::Deny => false,
|
Access::Denied => false,
|
||||||
Access::Grant => true,
|
Access::Grant => true,
|
||||||
Access::Allow(attrs) => attrs.contains(&Attribute::SshPublicKey),
|
Access::Allow(attrs) => attrs.contains(&Attribute::SshPublicKey),
|
||||||
};
|
};
|
||||||
|
|
||||||
let eperm_rem_sshpubkey = match &eperm.modify_rem {
|
let eperm_rem_sshpubkey = match &eperm.modify_rem {
|
||||||
Access::Deny => false,
|
Access::Denied => false,
|
||||||
Access::Grant => true,
|
Access::Grant => true,
|
||||||
Access::Allow(attrs) => attrs.contains(&Attribute::SshPublicKey),
|
Access::Allow(attrs) => attrs.contains(&Attribute::SshPublicKey),
|
||||||
};
|
};
|
||||||
|
@ -726,7 +726,7 @@ impl IdmServerProxyWriteTransaction<'_> {
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
match &eperm.search {
|
match &eperm.search {
|
||||||
Access::Deny => false,
|
Access::Denied => false,
|
||||||
Access::Grant => true,
|
Access::Grant => true,
|
||||||
Access::Allow(attrs) => attrs.contains(&Attribute::SyncCredentialPortal),
|
Access::Allow(attrs) => attrs.contains(&Attribute::SyncCredentialPortal),
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,8 +124,8 @@ pub mod prelude {
|
||||||
pub use kanidmd_lib_macros::*;
|
pub use kanidmd_lib_macros::*;
|
||||||
|
|
||||||
pub(crate) use crate::valueset::{
|
pub(crate) use crate::valueset::{
|
||||||
ValueSet, ValueSetBool, ValueSetCid, ValueSetIutf8, ValueSetRefer, ValueSetSyntax,
|
ValueSet, ValueSetBool, ValueSetCid, ValueSetIndex, ValueSetIutf8, ValueSetRefer,
|
||||||
ValueSetT, ValueSetUtf8, ValueSetUuid,
|
ValueSetSyntax, ValueSetT, ValueSetUtf8, ValueSetUuid,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) use kanidm_proto::scim_v1::{
|
pub(crate) use kanidm_proto::scim_v1::{
|
||||||
|
|
|
@ -620,6 +620,22 @@ macro_rules! vs_syntax {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unused_macros)]
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! vs_index {
|
||||||
|
() => (
|
||||||
|
compile_error!("ValueSetIndex needs at least 1 element")
|
||||||
|
);
|
||||||
|
($e:expr) => ({
|
||||||
|
ValueSetIndex::new($e)
|
||||||
|
});
|
||||||
|
($e:expr, $($item:expr),*) => ({
|
||||||
|
let mut x = ValueSetIndex::new($e);
|
||||||
|
$(assert!(x.push($item));)*
|
||||||
|
x
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(unused_macros)]
|
#[allow(unused_macros)]
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! vs_cid {
|
macro_rules! vs_cid {
|
||||||
|
|
|
@ -72,8 +72,6 @@ pub struct BuiltinAcp {
|
||||||
modify_present_attrs: Vec<Attribute>,
|
modify_present_attrs: Vec<Attribute>,
|
||||||
modify_removed_attrs: Vec<Attribute>,
|
modify_removed_attrs: Vec<Attribute>,
|
||||||
modify_classes: Vec<EntryClass>,
|
modify_classes: Vec<EntryClass>,
|
||||||
modify_present_classes: Vec<EntryClass>,
|
|
||||||
modify_remove_classes: Vec<EntryClass>,
|
|
||||||
create_classes: Vec<EntryClass>,
|
create_classes: Vec<EntryClass>,
|
||||||
create_attrs: Vec<Attribute>,
|
create_attrs: Vec<Attribute>,
|
||||||
}
|
}
|
||||||
|
@ -161,19 +159,9 @@ impl From<BuiltinAcp> for EntryInitNew {
|
||||||
value.modify_removed_attrs.into_iter().for_each(|attr| {
|
value.modify_removed_attrs.into_iter().for_each(|attr| {
|
||||||
entry.add_ava(Attribute::AcpModifyRemovedAttr, Value::from(attr));
|
entry.add_ava(Attribute::AcpModifyRemovedAttr, Value::from(attr));
|
||||||
});
|
});
|
||||||
|
|
||||||
value.modify_classes.into_iter().for_each(|class| {
|
value.modify_classes.into_iter().for_each(|class| {
|
||||||
entry.add_ava(Attribute::AcpModifyClass, Value::from(class));
|
entry.add_ava(Attribute::AcpModifyClass, Value::from(class));
|
||||||
});
|
});
|
||||||
|
|
||||||
value.modify_present_classes.into_iter().for_each(|class| {
|
|
||||||
entry.add_ava(Attribute::AcpModifyPresentClass, Value::from(class));
|
|
||||||
});
|
|
||||||
|
|
||||||
value.modify_remove_classes.into_iter().for_each(|class| {
|
|
||||||
entry.add_ava(Attribute::AcpModifyRemoveClass, Value::from(class));
|
|
||||||
});
|
|
||||||
|
|
||||||
value.create_classes.into_iter().for_each(|class| {
|
value.create_classes.into_iter().for_each(|class| {
|
||||||
entry.add_ava(Attribute::AcpCreateClass, Value::from(class));
|
entry.add_ava(Attribute::AcpCreateClass, Value::from(class));
|
||||||
});
|
});
|
||||||
|
@ -226,7 +214,7 @@ lazy_static! {
|
||||||
ATTR_RECYCLED.to_string()
|
ATTR_RECYCLED.to_string()
|
||||||
)),
|
)),
|
||||||
modify_removed_attrs: vec![Attribute::Class],
|
modify_removed_attrs: vec![Attribute::Class],
|
||||||
modify_remove_classes: vec![EntryClass::Recycled],
|
modify_classes: vec![EntryClass::Recycled],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -437,7 +425,6 @@ lazy_static! {
|
||||||
EntryClass::AccessControlCreate,
|
EntryClass::AccessControlCreate,
|
||||||
EntryClass::AccessControlDelete,
|
EntryClass::AccessControlDelete,
|
||||||
],
|
],
|
||||||
..Default::default()
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,7 @@ pub fn phase_1_schema_attrs() -> Vec<EntryInitNew> {
|
||||||
SCHEMA_ATTR_SYNC_TOKEN_SESSION.clone().into(),
|
SCHEMA_ATTR_SYNC_TOKEN_SESSION.clone().into(),
|
||||||
SCHEMA_ATTR_UNIX_PASSWORD.clone().into(),
|
SCHEMA_ATTR_UNIX_PASSWORD.clone().into(),
|
||||||
SCHEMA_ATTR_USER_AUTH_TOKEN_SESSION.clone().into(),
|
SCHEMA_ATTR_USER_AUTH_TOKEN_SESSION.clone().into(),
|
||||||
|
SCHEMA_ATTR_DENIED_NAME.clone().into(),
|
||||||
SCHEMA_ATTR_CREDENTIAL_TYPE_MINIMUM.clone().into(),
|
SCHEMA_ATTR_CREDENTIAL_TYPE_MINIMUM.clone().into(),
|
||||||
SCHEMA_ATTR_WEBAUTHN_ATTESTATION_CA_LIST.clone().into(),
|
SCHEMA_ATTR_WEBAUTHN_ATTESTATION_CA_LIST.clone().into(),
|
||||||
// DL4
|
// DL4
|
||||||
|
|
|
@ -2,25 +2,52 @@
|
||||||
use crate::constants::entries::{Attribute, EntryClass};
|
use crate::constants::entries::{Attribute, EntryClass};
|
||||||
use crate::constants::uuids::*;
|
use crate::constants::uuids::*;
|
||||||
use crate::schema::{SchemaAttribute, SchemaClass};
|
use crate::schema::{SchemaAttribute, SchemaClass};
|
||||||
|
use crate::value::IndexType;
|
||||||
use crate::value::SyntaxType;
|
use crate::value::SyntaxType;
|
||||||
|
|
||||||
lazy_static!(
|
lazy_static!(
|
||||||
|
|
||||||
|
pub static ref SCHEMA_ATTR_DISPLAYNAME: SchemaAttribute = SchemaAttribute {
|
||||||
|
uuid: UUID_SCHEMA_ATTR_DISPLAYNAME,
|
||||||
|
name: Attribute::DisplayName,
|
||||||
|
description: "The publicly visible display name of this person".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
|
sync_allowed: true,
|
||||||
|
syntax: SyntaxType::Utf8String,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
pub static ref SCHEMA_ATTR_DISPLAYNAME_DL7: SchemaAttribute = SchemaAttribute {
|
pub static ref SCHEMA_ATTR_DISPLAYNAME_DL7: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_DISPLAYNAME,
|
uuid: UUID_SCHEMA_ATTR_DISPLAYNAME,
|
||||||
name: Attribute::DisplayName,
|
name: Attribute::DisplayName,
|
||||||
description: "The publicly visible display name of this person".to_string(),
|
description: "The publicly visible display name of this person".to_string(),
|
||||||
indexed: true,
|
|
||||||
|
index: vec![IndexType::Equality, IndexType::SubString],
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::Utf8String,
|
syntax: SyntaxType::Utf8String,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub static ref SCHEMA_ATTR_MAIL: SchemaAttribute = SchemaAttribute {
|
||||||
|
uuid: UUID_SCHEMA_ATTR_MAIL,
|
||||||
|
name: Attribute::Mail,
|
||||||
|
description: "Mail addresses of the object".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
|
unique: true,
|
||||||
|
multivalue: true,
|
||||||
|
sync_allowed: true,
|
||||||
|
syntax: SyntaxType::EmailAddress,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
pub static ref SCHEMA_ATTR_MAIL_DL7: SchemaAttribute = SchemaAttribute {
|
pub static ref SCHEMA_ATTR_MAIL_DL7: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_MAIL,
|
uuid: UUID_SCHEMA_ATTR_MAIL,
|
||||||
name: Attribute::Mail,
|
name: Attribute::Mail,
|
||||||
description: "Mail addresses of the object".to_string(),
|
description: "Mail addresses of the object".to_string(),
|
||||||
indexed: true,
|
|
||||||
|
index: vec![IndexType::Equality, IndexType::SubString],
|
||||||
unique: true,
|
unique: true,
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
|
@ -32,7 +59,8 @@ pub static ref SCHEMA_ATTR_EC_KEY_PRIVATE: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_EC_KEY_PRIVATE,
|
uuid: UUID_SCHEMA_ATTR_EC_KEY_PRIVATE,
|
||||||
name: Attribute::IdVerificationEcKey,
|
name: Attribute::IdVerificationEcKey,
|
||||||
description: "Account verification private key".to_string(),
|
description: "Account verification private key".to_string(),
|
||||||
indexed: true,
|
|
||||||
|
index: vec![IndexType::Presence],
|
||||||
unique: false,
|
unique: false,
|
||||||
sync_allowed: false,
|
sync_allowed: false,
|
||||||
syntax: SyntaxType::EcKeyPrivate,
|
syntax: SyntaxType::EcKeyPrivate,
|
||||||
|
@ -54,17 +82,30 @@ pub static ref SCHEMA_ATTR_PRIMARY_CREDENTIAL: SchemaAttribute = SchemaAttribute
|
||||||
uuid: UUID_SCHEMA_ATTR_PRIMARY_CREDENTIAL,
|
uuid: UUID_SCHEMA_ATTR_PRIMARY_CREDENTIAL,
|
||||||
name: Attribute::PrimaryCredential,
|
name: Attribute::PrimaryCredential,
|
||||||
description: "Primary credential material of the account for authentication interactively".to_string(),
|
description: "Primary credential material of the account for authentication interactively".to_string(),
|
||||||
indexed: true,
|
|
||||||
|
index: vec![IndexType::Presence],
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::Credential,
|
syntax: SyntaxType::Credential,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub static ref SCHEMA_ATTR_LEGALNAME: SchemaAttribute = SchemaAttribute {
|
||||||
|
uuid: UUID_SCHEMA_ATTR_LEGALNAME,
|
||||||
|
name: Attribute::LegalName,
|
||||||
|
description: "The private and sensitive legal name of this person".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
|
sync_allowed: true,
|
||||||
|
syntax: SyntaxType::Utf8String,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
pub static ref SCHEMA_ATTR_LEGALNAME_DL7: SchemaAttribute = SchemaAttribute {
|
pub static ref SCHEMA_ATTR_LEGALNAME_DL7: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_LEGALNAME,
|
uuid: UUID_SCHEMA_ATTR_LEGALNAME,
|
||||||
name: Attribute::LegalName,
|
name: Attribute::LegalName,
|
||||||
description: "The private and sensitive legal name of this person".to_string(),
|
description: "The private and sensitive legal name of this person".to_string(),
|
||||||
indexed: true,
|
|
||||||
|
index: vec![IndexType::Equality, IndexType::SubString],
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::Utf8String,
|
syntax: SyntaxType::Utf8String,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -74,7 +115,8 @@ pub static ref SCHEMA_ATTR_NAME_HISTORY: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_NAME_HISTORY,
|
uuid: UUID_SCHEMA_ATTR_NAME_HISTORY,
|
||||||
name: Attribute::NameHistory,
|
name: Attribute::NameHistory,
|
||||||
description: "The history of names that a person has had".to_string(),
|
description: "The history of names that a person has had".to_string(),
|
||||||
indexed: true,
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::AuditLogString,
|
syntax: SyntaxType::AuditLogString,
|
||||||
|
@ -85,6 +127,7 @@ pub static ref SCHEMA_ATTR_RADIUS_SECRET: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_RADIUS_SECRET,
|
uuid: UUID_SCHEMA_ATTR_RADIUS_SECRET,
|
||||||
name: Attribute::RadiusSecret,
|
name: Attribute::RadiusSecret,
|
||||||
description: "The accounts generated radius secret for device network authentication".to_string(),
|
description: "The accounts generated radius secret for device network authentication".to_string(),
|
||||||
|
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::SecretUtf8String,
|
syntax: SyntaxType::SecretUtf8String,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -94,7 +137,8 @@ pub static ref SCHEMA_ATTR_DOMAIN_NAME: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_DOMAIN_NAME,
|
uuid: UUID_SCHEMA_ATTR_DOMAIN_NAME,
|
||||||
name: Attribute::DomainName,
|
name: Attribute::DomainName,
|
||||||
description: "The domain's DNS name for webauthn and SPN generation purposes".to_string(),
|
description: "The domain's DNS name for webauthn and SPN generation purposes".to_string(),
|
||||||
indexed: true,
|
|
||||||
|
index: vec![IndexType::Equality, IndexType::Presence],
|
||||||
unique: true,
|
unique: true,
|
||||||
syntax: SyntaxType::Utf8StringIname,
|
syntax: SyntaxType::Utf8StringIname,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -104,6 +148,7 @@ pub static ref SCHEMA_ATTR_LDAP_ALLOW_UNIX_PW_BIND: SchemaAttribute = SchemaAttr
|
||||||
uuid: UUID_SCHEMA_ATTR_LDAP_ALLOW_UNIX_PW_BIND,
|
uuid: UUID_SCHEMA_ATTR_LDAP_ALLOW_UNIX_PW_BIND,
|
||||||
name: Attribute::LdapAllowUnixPwBind,
|
name: Attribute::LdapAllowUnixPwBind,
|
||||||
description: "Configuration to enable binds to LDAP objects using their UNIX password".to_string(),
|
description: "Configuration to enable binds to LDAP objects using their UNIX password".to_string(),
|
||||||
|
|
||||||
unique: false,
|
unique: false,
|
||||||
syntax: SyntaxType::Boolean,
|
syntax: SyntaxType::Boolean,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -113,6 +158,7 @@ pub static ref SCHEMA_ATTR_DOMAIN_LDAP_BASEDN: SchemaAttribute = SchemaAttribute
|
||||||
uuid: UUID_SCHEMA_ATTR_DOMAIN_LDAP_BASEDN,
|
uuid: UUID_SCHEMA_ATTR_DOMAIN_LDAP_BASEDN,
|
||||||
name: Attribute::DomainLdapBasedn,
|
name: Attribute::DomainLdapBasedn,
|
||||||
description: "The domain's optional ldap basedn. If unset defaults to domain components of domain name".to_string(),
|
description: "The domain's optional ldap basedn. If unset defaults to domain components of domain name".to_string(),
|
||||||
|
|
||||||
unique: true,
|
unique: true,
|
||||||
syntax: SyntaxType::Utf8StringInsensitive,
|
syntax: SyntaxType::Utf8StringInsensitive,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -122,6 +168,7 @@ pub static ref SCHEMA_ATTR_LDAP_MAXIMUM_QUERYABLE_ATTRIBUTES: SchemaAttribute =
|
||||||
uuid: UUID_SCHEMA_ATTR_LDAP_MAXIMUM_QUERYABLE_ATTRIBUTES,
|
uuid: UUID_SCHEMA_ATTR_LDAP_MAXIMUM_QUERYABLE_ATTRIBUTES,
|
||||||
name: Attribute::LdapMaxQueryableAttrs,
|
name: Attribute::LdapMaxQueryableAttrs,
|
||||||
description: "The maximum number of LDAP attributes that can be queried in one operation".to_string(),
|
description: "The maximum number of LDAP attributes that can be queried in one operation".to_string(),
|
||||||
|
|
||||||
multivalue: false,
|
multivalue: false,
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::Uint32,
|
syntax: SyntaxType::Uint32,
|
||||||
|
@ -132,7 +179,8 @@ pub static ref SCHEMA_ATTR_DOMAIN_DISPLAY_NAME: SchemaAttribute = SchemaAttribut
|
||||||
uuid: UUID_SCHEMA_ATTR_DOMAIN_DISPLAY_NAME,
|
uuid: UUID_SCHEMA_ATTR_DOMAIN_DISPLAY_NAME,
|
||||||
name: Attribute::DomainDisplayName,
|
name: Attribute::DomainDisplayName,
|
||||||
description: "The user-facing display name of the Kanidm domain".to_string(),
|
description: "The user-facing display name of the Kanidm domain".to_string(),
|
||||||
indexed: true,
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
syntax: SyntaxType::Utf8String,
|
syntax: SyntaxType::Utf8String,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -141,7 +189,8 @@ pub static ref SCHEMA_ATTR_DOMAIN_UUID: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_DOMAIN_UUID,
|
uuid: UUID_SCHEMA_ATTR_DOMAIN_UUID,
|
||||||
name: Attribute::DomainUuid,
|
name: Attribute::DomainUuid,
|
||||||
description: "The domain's uuid, used in CSN and trust relationships".to_string(),
|
description: "The domain's uuid, used in CSN and trust relationships".to_string(),
|
||||||
indexed: true,
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
unique: true,
|
unique: true,
|
||||||
syntax: SyntaxType::Uuid,
|
syntax: SyntaxType::Uuid,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -151,16 +200,27 @@ pub static ref SCHEMA_ATTR_DOMAIN_SSID: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_DOMAIN_SSID,
|
uuid: UUID_SCHEMA_ATTR_DOMAIN_SSID,
|
||||||
name: Attribute::DomainSsid,
|
name: Attribute::DomainSsid,
|
||||||
description: "The domains site-wide SSID for device autoconfiguration of wireless".to_string(),
|
description: "The domains site-wide SSID for device autoconfiguration of wireless".to_string(),
|
||||||
indexed: true,
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
unique: true,
|
unique: true,
|
||||||
syntax: SyntaxType::Utf8String,
|
syntax: SyntaxType::Utf8String,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub static ref SCHEMA_ATTR_DENIED_NAME: SchemaAttribute = SchemaAttribute {
|
||||||
|
uuid: UUID_SCHEMA_ATTR_DENIED_NAME,
|
||||||
|
name: Attribute::DeniedName,
|
||||||
|
description: "Iname values that are not allowed to be used in 'name'.".to_string(),
|
||||||
|
|
||||||
|
syntax: SyntaxType::Utf8StringIname,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
pub static ref SCHEMA_ATTR_DENIED_NAME_DL10: SchemaAttribute = SchemaAttribute {
|
pub static ref SCHEMA_ATTR_DENIED_NAME_DL10: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_DENIED_NAME,
|
uuid: UUID_SCHEMA_ATTR_DENIED_NAME,
|
||||||
name: Attribute::DeniedName,
|
name: Attribute::DeniedName,
|
||||||
description: "Iname values that are not allowed to be used in 'name'.".to_string(),
|
description: "Iname values that are not allowed to be used in 'name'.".to_string(),
|
||||||
|
|
||||||
syntax: SyntaxType::Utf8StringIname,
|
syntax: SyntaxType::Utf8StringIname,
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -170,6 +230,7 @@ pub static ref SCHEMA_ATTR_DOMAIN_TOKEN_KEY: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_DOMAIN_TOKEN_KEY,
|
uuid: UUID_SCHEMA_ATTR_DOMAIN_TOKEN_KEY,
|
||||||
name: Attribute::DomainTokenKey,
|
name: Attribute::DomainTokenKey,
|
||||||
description: "The domain token encryption private key (NOT USED)".to_string(),
|
description: "The domain token encryption private key (NOT USED)".to_string(),
|
||||||
|
|
||||||
syntax: SyntaxType::SecretUtf8String,
|
syntax: SyntaxType::SecretUtf8String,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -187,7 +248,8 @@ pub static ref SCHEMA_ATTR_GIDNUMBER: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_GIDNUMBER,
|
uuid: UUID_SCHEMA_ATTR_GIDNUMBER,
|
||||||
name: Attribute::GidNumber,
|
name: Attribute::GidNumber,
|
||||||
description: "The groupid (uid) number of a group or account.to_string(). This is the same value as the UID number on posix accounts for security reasons".to_string(),
|
description: "The groupid (uid) number of a group or account.to_string(). This is the same value as the UID number on posix accounts for security reasons".to_string(),
|
||||||
indexed: true,
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
unique: true,
|
unique: true,
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::Uint32,
|
syntax: SyntaxType::Uint32,
|
||||||
|
@ -198,6 +260,7 @@ pub static ref SCHEMA_ATTR_BADLIST_PASSWORD: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_BADLIST_PASSWORD,
|
uuid: UUID_SCHEMA_ATTR_BADLIST_PASSWORD,
|
||||||
name: Attribute::BadlistPassword,
|
name: Attribute::BadlistPassword,
|
||||||
description: "A password that is badlisted meaning that it can not be set as a valid password by any user account".to_string(),
|
description: "A password that is badlisted meaning that it can not be set as a valid password by any user account".to_string(),
|
||||||
|
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
syntax: SyntaxType::Utf8StringInsensitive,
|
syntax: SyntaxType::Utf8StringInsensitive,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -207,6 +270,7 @@ pub static ref SCHEMA_ATTR_AUTH_SESSION_EXPIRY: SchemaAttribute = SchemaAttribut
|
||||||
uuid: UUID_SCHEMA_ATTR_AUTH_SESSION_EXPIRY,
|
uuid: UUID_SCHEMA_ATTR_AUTH_SESSION_EXPIRY,
|
||||||
name: Attribute::AuthSessionExpiry,
|
name: Attribute::AuthSessionExpiry,
|
||||||
description: "An expiration time for an authentication session".to_string(),
|
description: "An expiration time for an authentication session".to_string(),
|
||||||
|
|
||||||
syntax: SyntaxType::Uint32,
|
syntax: SyntaxType::Uint32,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -215,6 +279,7 @@ pub static ref SCHEMA_ATTR_AUTH_PRIVILEGE_EXPIRY: SchemaAttribute = SchemaAttrib
|
||||||
uuid: UUID_SCHEMA_ATTR_AUTH_PRIVILEGE_EXPIRY,
|
uuid: UUID_SCHEMA_ATTR_AUTH_PRIVILEGE_EXPIRY,
|
||||||
name: Attribute::PrivilegeExpiry,
|
name: Attribute::PrivilegeExpiry,
|
||||||
description: "An expiration time for a privileged authentication session".to_string(),
|
description: "An expiration time for a privileged authentication session".to_string(),
|
||||||
|
|
||||||
syntax: SyntaxType::Uint32,
|
syntax: SyntaxType::Uint32,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -223,6 +288,7 @@ pub static ref SCHEMA_ATTR_AUTH_PASSWORD_MINIMUM_LENGTH: SchemaAttribute = Schem
|
||||||
uuid: UUID_SCHEMA_ATTR_AUTH_PASSWORD_MINIMUM_LENGTH,
|
uuid: UUID_SCHEMA_ATTR_AUTH_PASSWORD_MINIMUM_LENGTH,
|
||||||
name: Attribute::AuthPasswordMinimumLength,
|
name: Attribute::AuthPasswordMinimumLength,
|
||||||
description: "Minimum length of passwords".to_string(),
|
description: "Minimum length of passwords".to_string(),
|
||||||
|
|
||||||
syntax: SyntaxType::Uint32,
|
syntax: SyntaxType::Uint32,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -231,6 +297,7 @@ pub static ref SCHEMA_ATTR_LOGINSHELL: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_LOGINSHELL,
|
uuid: UUID_SCHEMA_ATTR_LOGINSHELL,
|
||||||
name: Attribute::LoginShell,
|
name: Attribute::LoginShell,
|
||||||
description: "A POSIX user's UNIX login shell".to_string(),
|
description: "A POSIX user's UNIX login shell".to_string(),
|
||||||
|
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::Utf8StringInsensitive,
|
syntax: SyntaxType::Utf8StringInsensitive,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -240,7 +307,8 @@ pub static ref SCHEMA_ATTR_UNIX_PASSWORD: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_UNIX_PASSWORD,
|
uuid: UUID_SCHEMA_ATTR_UNIX_PASSWORD,
|
||||||
name: Attribute::UnixPassword,
|
name: Attribute::UnixPassword,
|
||||||
description: "A POSIX user's UNIX login password".to_string(),
|
description: "A POSIX user's UNIX login password".to_string(),
|
||||||
indexed: true,
|
|
||||||
|
index: vec![IndexType::Presence],
|
||||||
syntax: SyntaxType::Credential,
|
syntax: SyntaxType::Credential,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -249,7 +317,8 @@ pub static ref SCHEMA_ATTR_NSUNIQUEID: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_NSUNIQUEID,
|
uuid: UUID_SCHEMA_ATTR_NSUNIQUEID,
|
||||||
name: Attribute::NsUniqueId,
|
name: Attribute::NsUniqueId,
|
||||||
description: "A unique id compatibility for 389-ds/dsee".to_string(),
|
description: "A unique id compatibility for 389-ds/dsee".to_string(),
|
||||||
indexed: true,
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
unique: true,
|
unique: true,
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::NsUniqueId,
|
syntax: SyntaxType::NsUniqueId,
|
||||||
|
@ -260,6 +329,7 @@ pub static ref SCHEMA_ATTR_ACCOUNT_EXPIRE: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_ACCOUNT_EXPIRE,
|
uuid: UUID_SCHEMA_ATTR_ACCOUNT_EXPIRE,
|
||||||
name: Attribute::AccountExpire,
|
name: Attribute::AccountExpire,
|
||||||
description: "The datetime after which this account no longer may authenticate".to_string(),
|
description: "The datetime after which this account no longer may authenticate".to_string(),
|
||||||
|
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::DateTime,
|
syntax: SyntaxType::DateTime,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -269,6 +339,7 @@ pub static ref SCHEMA_ATTR_ACCOUNT_VALID_FROM: SchemaAttribute = SchemaAttribute
|
||||||
uuid: UUID_SCHEMA_ATTR_ACCOUNT_VALID_FROM,
|
uuid: UUID_SCHEMA_ATTR_ACCOUNT_VALID_FROM,
|
||||||
name: Attribute::AccountValidFrom,
|
name: Attribute::AccountValidFrom,
|
||||||
description: "The datetime after which this account may commence authenticating".to_string(),
|
description: "The datetime after which this account may commence authenticating".to_string(),
|
||||||
|
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::DateTime,
|
syntax: SyntaxType::DateTime,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -278,6 +349,7 @@ pub static ref SCHEMA_ATTR_WEBAUTHN_ATTESTATION_CA_LIST: SchemaAttribute = Schem
|
||||||
uuid: UUID_SCHEMA_ATTR_WEBAUTHN_ATTESTATION_CA_LIST,
|
uuid: UUID_SCHEMA_ATTR_WEBAUTHN_ATTESTATION_CA_LIST,
|
||||||
name: Attribute::WebauthnAttestationCaList,
|
name: Attribute::WebauthnAttestationCaList,
|
||||||
description: "A set of CA's that limit devices that can be used with webauthn".to_string(),
|
description: "A set of CA's that limit devices that can be used with webauthn".to_string(),
|
||||||
|
|
||||||
syntax: SyntaxType::WebauthnAttestationCaList,
|
syntax: SyntaxType::WebauthnAttestationCaList,
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -287,16 +359,27 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_NAME: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_NAME,
|
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_NAME,
|
||||||
name: Attribute::OAuth2RsName,
|
name: Attribute::OAuth2RsName,
|
||||||
description: "The unique name of an external Oauth2 resource".to_string(),
|
description: "The unique name of an external Oauth2 resource".to_string(),
|
||||||
indexed: true,
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
unique: true,
|
unique: true,
|
||||||
syntax: SyntaxType::Utf8StringIname,
|
syntax: SyntaxType::Utf8StringIname,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub static ref SCHEMA_ATTR_OAUTH2_RS_ORIGIN: SchemaAttribute = SchemaAttribute {
|
||||||
|
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_ORIGIN,
|
||||||
|
name: Attribute::OAuth2RsOrigin,
|
||||||
|
description: "The origin domain of an oauth2 resource server".to_string(),
|
||||||
|
|
||||||
|
syntax: SyntaxType::Url,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
pub static ref SCHEMA_ATTR_OAUTH2_RS_ORIGIN_DL7: SchemaAttribute = SchemaAttribute {
|
pub static ref SCHEMA_ATTR_OAUTH2_RS_ORIGIN_DL7: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_ORIGIN,
|
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_ORIGIN,
|
||||||
name: Attribute::OAuth2RsOrigin,
|
name: Attribute::OAuth2RsOrigin,
|
||||||
description: "The origin domain of an OAuth2 client".to_string(),
|
description: "The origin domain of an OAuth2 client".to_string(),
|
||||||
|
|
||||||
syntax: SyntaxType::Url,
|
syntax: SyntaxType::Url,
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -306,6 +389,7 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_ORIGIN_LANDING: SchemaAttribute = SchemaAtt
|
||||||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_ORIGIN_LANDING,
|
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_ORIGIN_LANDING,
|
||||||
name: Attribute::OAuth2RsOriginLanding,
|
name: Attribute::OAuth2RsOriginLanding,
|
||||||
description: "The landing page of an RS, that will automatically trigger the auth process".to_string(),
|
description: "The landing page of an RS, that will automatically trigger the auth process".to_string(),
|
||||||
|
|
||||||
syntax: SyntaxType::Url,
|
syntax: SyntaxType::Url,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -315,6 +399,7 @@ pub static ref SCHEMA_ATTR_OAUTH2_ALLOW_LOCALHOST_REDIRECT_DL4: SchemaAttribute
|
||||||
uuid: UUID_SCHEMA_ATTR_OAUTH2_ALLOW_LOCALHOST_REDIRECT,
|
uuid: UUID_SCHEMA_ATTR_OAUTH2_ALLOW_LOCALHOST_REDIRECT,
|
||||||
name: Attribute::OAuth2AllowLocalhostRedirect,
|
name: Attribute::OAuth2AllowLocalhostRedirect,
|
||||||
description: "Allow public clients associated to this RS to redirect to localhost".to_string(),
|
description: "Allow public clients associated to this RS to redirect to localhost".to_string(),
|
||||||
|
|
||||||
syntax: SyntaxType::Boolean,
|
syntax: SyntaxType::Boolean,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -323,7 +408,8 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_CLAIM_MAP_DL4: SchemaAttribute = SchemaAttr
|
||||||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_CLAIM_MAP,
|
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_CLAIM_MAP,
|
||||||
name: Attribute::OAuth2RsClaimMap,
|
name: Attribute::OAuth2RsClaimMap,
|
||||||
description: "A set of custom claims mapped to group memberships of accounts".to_string(),
|
description: "A set of custom claims mapped to group memberships of accounts".to_string(),
|
||||||
indexed: true,
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
// CHANGE ME
|
// CHANGE ME
|
||||||
syntax: SyntaxType::OauthClaimMap,
|
syntax: SyntaxType::OauthClaimMap,
|
||||||
|
@ -334,7 +420,8 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_SCOPE_MAP: SchemaAttribute = SchemaAttribut
|
||||||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_SCOPE_MAP,
|
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_SCOPE_MAP,
|
||||||
name: Attribute::OAuth2RsScopeMap,
|
name: Attribute::OAuth2RsScopeMap,
|
||||||
description: "A reference to a group mapped to scopes for the associated oauth2 resource server".to_string(),
|
description: "A reference to a group mapped to scopes for the associated oauth2 resource server".to_string(),
|
||||||
indexed: true,
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
syntax: SyntaxType::OauthScopeMap,
|
syntax: SyntaxType::OauthScopeMap,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -344,7 +431,8 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_SUP_SCOPE_MAP: SchemaAttribute = SchemaAttr
|
||||||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_SUP_SCOPE_MAP,
|
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_SUP_SCOPE_MAP,
|
||||||
name: Attribute::OAuth2RsSupScopeMap,
|
name: Attribute::OAuth2RsSupScopeMap,
|
||||||
description: "A reference to a group mapped to scopes for the associated oauth2 resource server".to_string(),
|
description: "A reference to a group mapped to scopes for the associated oauth2 resource server".to_string(),
|
||||||
indexed: true,
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
syntax: SyntaxType::OauthScopeMap,
|
syntax: SyntaxType::OauthScopeMap,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -354,6 +442,7 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_BASIC_SECRET: SchemaAttribute = SchemaAttri
|
||||||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_BASIC_SECRET,
|
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_BASIC_SECRET,
|
||||||
name: Attribute::OAuth2RsBasicSecret,
|
name: Attribute::OAuth2RsBasicSecret,
|
||||||
description: "When using oauth2 basic authentication, the secret string of the resource server".to_string(),
|
description: "When using oauth2 basic authentication, the secret string of the resource server".to_string(),
|
||||||
|
|
||||||
syntax: SyntaxType::SecretUtf8String,
|
syntax: SyntaxType::SecretUtf8String,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -362,6 +451,7 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_TOKEN_KEY: SchemaAttribute = SchemaAttribut
|
||||||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_TOKEN_KEY,
|
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_TOKEN_KEY,
|
||||||
name: Attribute::OAuth2RsTokenKey,
|
name: Attribute::OAuth2RsTokenKey,
|
||||||
description: "An oauth2 resource servers unique token signing key".to_string(),
|
description: "An oauth2 resource servers unique token signing key".to_string(),
|
||||||
|
|
||||||
syntax: SyntaxType::SecretUtf8String,
|
syntax: SyntaxType::SecretUtf8String,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -370,6 +460,7 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_IMPLICIT_SCOPES: SchemaAttribute = SchemaAt
|
||||||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_IMPLICIT_SCOPES,
|
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_IMPLICIT_SCOPES,
|
||||||
name: Attribute::OAuth2RsImplicitScopes,
|
name: Attribute::OAuth2RsImplicitScopes,
|
||||||
description: "An oauth2 resource servers scopes that are implicitly granted to all users".to_string(),
|
description: "An oauth2 resource servers scopes that are implicitly granted to all users".to_string(),
|
||||||
|
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
syntax: SyntaxType::OauthScope,
|
syntax: SyntaxType::OauthScope,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -379,7 +470,8 @@ pub static ref SCHEMA_ATTR_OAUTH2_CONSENT_SCOPE_MAP: SchemaAttribute = SchemaAtt
|
||||||
uuid: UUID_SCHEMA_ATTR_OAUTH2_CONSENT_SCOPE_MAP,
|
uuid: UUID_SCHEMA_ATTR_OAUTH2_CONSENT_SCOPE_MAP,
|
||||||
name: Attribute::OAuth2ConsentScopeMap,
|
name: Attribute::OAuth2ConsentScopeMap,
|
||||||
description: "A set of scopes mapped from a relying server to a user, where the user has previously consented to the following. If changed or deleted, consent will be re-sought".to_string(),
|
description: "A set of scopes mapped from a relying server to a user, where the user has previously consented to the following. If changed or deleted, consent will be re-sought".to_string(),
|
||||||
indexed: true,
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
syntax: SyntaxType::OauthScopeMap,
|
syntax: SyntaxType::OauthScopeMap,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -389,6 +481,7 @@ pub static ref SCHEMA_ATTR_OAUTH2_STRICT_REDIRECT_URI_DL7: SchemaAttribute = Sch
|
||||||
uuid: UUID_SCHEMA_ATTR_OAUTH2_STRICT_REDIRECT_URI,
|
uuid: UUID_SCHEMA_ATTR_OAUTH2_STRICT_REDIRECT_URI,
|
||||||
name: Attribute::OAuth2StrictRedirectUri,
|
name: Attribute::OAuth2StrictRedirectUri,
|
||||||
description: "Represents if strict redirect uri enforcement is enabled.".to_string(),
|
description: "Represents if strict redirect uri enforcement is enabled.".to_string(),
|
||||||
|
|
||||||
syntax: SyntaxType::Boolean,
|
syntax: SyntaxType::Boolean,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -398,6 +491,7 @@ pub static ref SCHEMA_ATTR_OAUTH2_DEVICE_FLOW_ENABLE_DL9: SchemaAttribute = Sche
|
||||||
uuid: UUID_SCHEMA_ATTR_OAUTH2_DEVICE_FLOW_ENABLE,
|
uuid: UUID_SCHEMA_ATTR_OAUTH2_DEVICE_FLOW_ENABLE,
|
||||||
name: Attribute::OAuth2DeviceFlowEnable,
|
name: Attribute::OAuth2DeviceFlowEnable,
|
||||||
description: "Represents if OAuth2 Device Flow is permitted on this client.".to_string(),
|
description: "Represents if OAuth2 Device Flow is permitted on this client.".to_string(),
|
||||||
|
|
||||||
syntax: SyntaxType::Boolean,
|
syntax: SyntaxType::Boolean,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -406,6 +500,7 @@ pub static ref SCHEMA_ATTR_ES256_PRIVATE_KEY_DER: SchemaAttribute = SchemaAttrib
|
||||||
uuid: UUID_SCHEMA_ATTR_ES256_PRIVATE_KEY_DER,
|
uuid: UUID_SCHEMA_ATTR_ES256_PRIVATE_KEY_DER,
|
||||||
name: Attribute::Es256PrivateKeyDer,
|
name: Attribute::Es256PrivateKeyDer,
|
||||||
description: "An es256 private key".to_string(),
|
description: "An es256 private key".to_string(),
|
||||||
|
|
||||||
syntax: SyntaxType::PrivateBinary,
|
syntax: SyntaxType::PrivateBinary,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -414,6 +509,7 @@ pub static ref SCHEMA_ATTR_RS256_PRIVATE_KEY_DER: SchemaAttribute = SchemaAttrib
|
||||||
uuid: UUID_SCHEMA_ATTR_RS256_PRIVATE_KEY_DER,
|
uuid: UUID_SCHEMA_ATTR_RS256_PRIVATE_KEY_DER,
|
||||||
name: Attribute::Rs256PrivateKeyDer,
|
name: Attribute::Rs256PrivateKeyDer,
|
||||||
description: "An rs256 private key".to_string(),
|
description: "An rs256 private key".to_string(),
|
||||||
|
|
||||||
syntax: SyntaxType::PrivateBinary,
|
syntax: SyntaxType::PrivateBinary,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -422,7 +518,8 @@ pub static ref SCHEMA_ATTR_JWS_ES256_PRIVATE_KEY: SchemaAttribute = SchemaAttrib
|
||||||
uuid: UUID_SCHEMA_ATTR_JWS_ES256_PRIVATE_KEY,
|
uuid: UUID_SCHEMA_ATTR_JWS_ES256_PRIVATE_KEY,
|
||||||
name: Attribute::JwsEs256PrivateKey,
|
name: Attribute::JwsEs256PrivateKey,
|
||||||
description: "An es256 private key for jws".to_string(),
|
description: "An es256 private key for jws".to_string(),
|
||||||
indexed: true,
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
unique: true,
|
unique: true,
|
||||||
syntax: SyntaxType::JwsKeyEs256,
|
syntax: SyntaxType::JwsKeyEs256,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -433,6 +530,7 @@ pub static ref SCHEMA_ATTR_PRIVATE_COOKIE_KEY: SchemaAttribute = SchemaAttribute
|
||||||
uuid: UUID_SCHEMA_ATTR_PRIVATE_COOKIE_KEY,
|
uuid: UUID_SCHEMA_ATTR_PRIVATE_COOKIE_KEY,
|
||||||
name: Attribute::PrivateCookieKey,
|
name: Attribute::PrivateCookieKey,
|
||||||
description: "An private cookie hmac key".to_string(),
|
description: "An private cookie hmac key".to_string(),
|
||||||
|
|
||||||
syntax: SyntaxType::PrivateBinary,
|
syntax: SyntaxType::PrivateBinary,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -441,6 +539,7 @@ pub static ref SCHEMA_ATTR_OAUTH2_ALLOW_INSECURE_CLIENT_DISABLE_PKCE: SchemaAttr
|
||||||
uuid: UUID_SCHEMA_ATTR_OAUTH2_ALLOW_INSECURE_CLIENT_DISABLE_PKCE,
|
uuid: UUID_SCHEMA_ATTR_OAUTH2_ALLOW_INSECURE_CLIENT_DISABLE_PKCE,
|
||||||
name: Attribute::OAuth2AllowInsecureClientDisablePkce,
|
name: Attribute::OAuth2AllowInsecureClientDisablePkce,
|
||||||
description: "Allows disabling of PKCE for insecure OAuth2 clients".to_string(),
|
description: "Allows disabling of PKCE for insecure OAuth2 clients".to_string(),
|
||||||
|
|
||||||
syntax: SyntaxType::Boolean,
|
syntax: SyntaxType::Boolean,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -449,6 +548,7 @@ pub static ref SCHEMA_ATTR_OAUTH2_JWT_LEGACY_CRYPTO_ENABLE: SchemaAttribute = Sc
|
||||||
uuid: UUID_SCHEMA_ATTR_OAUTH2_JWT_LEGACY_CRYPTO_ENABLE,
|
uuid: UUID_SCHEMA_ATTR_OAUTH2_JWT_LEGACY_CRYPTO_ENABLE,
|
||||||
name: Attribute::OAuth2JwtLegacyCryptoEnable,
|
name: Attribute::OAuth2JwtLegacyCryptoEnable,
|
||||||
description: "Allows enabling legacy JWT cryptograhpy for clients".to_string(),
|
description: "Allows enabling legacy JWT cryptograhpy for clients".to_string(),
|
||||||
|
|
||||||
syntax: SyntaxType::Boolean,
|
syntax: SyntaxType::Boolean,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -457,7 +557,8 @@ pub static ref SCHEMA_ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN: SchemaAttribute = Sch
|
||||||
uuid: UUID_SCHEMA_ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN,
|
uuid: UUID_SCHEMA_ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN,
|
||||||
name: Attribute::CredentialUpdateIntentToken,
|
name: Attribute::CredentialUpdateIntentToken,
|
||||||
description: "The status of a credential update intent token".to_string(),
|
description: "The status of a credential update intent token".to_string(),
|
||||||
indexed: true,
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
syntax: SyntaxType::IntentToken,
|
syntax: SyntaxType::IntentToken,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -467,7 +568,8 @@ pub static ref SCHEMA_ATTR_PASSKEYS: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_PASSKEYS,
|
uuid: UUID_SCHEMA_ATTR_PASSKEYS,
|
||||||
name: Attribute::PassKeys,
|
name: Attribute::PassKeys,
|
||||||
description: "A set of registered passkeys".to_string(),
|
description: "A set of registered passkeys".to_string(),
|
||||||
indexed: true,
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::Passkey,
|
syntax: SyntaxType::Passkey,
|
||||||
|
@ -478,7 +580,8 @@ pub static ref SCHEMA_ATTR_ATTESTED_PASSKEYS: SchemaAttribute = SchemaAttribute
|
||||||
uuid: UUID_SCHEMA_ATTR_ATTESTED_PASSKEYS,
|
uuid: UUID_SCHEMA_ATTR_ATTESTED_PASSKEYS,
|
||||||
name: Attribute::AttestedPasskeys,
|
name: Attribute::AttestedPasskeys,
|
||||||
description: "A set of registered device keys".to_string(),
|
description: "A set of registered device keys".to_string(),
|
||||||
indexed: true,
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::AttestedPasskey,
|
syntax: SyntaxType::AttestedPasskey,
|
||||||
|
@ -489,6 +592,7 @@ pub static ref SCHEMA_ATTR_DYNGROUP_FILTER: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_DYNGROUP_FILTER,
|
uuid: UUID_SCHEMA_ATTR_DYNGROUP_FILTER,
|
||||||
name: Attribute::DynGroupFilter,
|
name: Attribute::DynGroupFilter,
|
||||||
description: "A filter describing the set of entries to add to a dynamic group".to_string(),
|
description: "A filter describing the set of entries to add to a dynamic group".to_string(),
|
||||||
|
|
||||||
syntax: SyntaxType::JsonFilter,
|
syntax: SyntaxType::JsonFilter,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -497,6 +601,7 @@ pub static ref SCHEMA_ATTR_OAUTH2_PREFER_SHORT_USERNAME: SchemaAttribute = Schem
|
||||||
uuid: UUID_SCHEMA_ATTR_OAUTH2_PREFER_SHORT_USERNAME,
|
uuid: UUID_SCHEMA_ATTR_OAUTH2_PREFER_SHORT_USERNAME,
|
||||||
name: Attribute::OAuth2PreferShortUsername,
|
name: Attribute::OAuth2PreferShortUsername,
|
||||||
description: "Use 'name' instead of 'spn' in the preferred_username claim".to_string(),
|
description: "Use 'name' instead of 'spn' in the preferred_username claim".to_string(),
|
||||||
|
|
||||||
syntax: SyntaxType::Boolean,
|
syntax: SyntaxType::Boolean,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -505,7 +610,8 @@ pub static ref SCHEMA_ATTR_API_TOKEN_SESSION: SchemaAttribute = SchemaAttribute
|
||||||
uuid: UUID_SCHEMA_ATTR_API_TOKEN_SESSION,
|
uuid: UUID_SCHEMA_ATTR_API_TOKEN_SESSION,
|
||||||
name: Attribute::ApiTokenSession,
|
name: Attribute::ApiTokenSession,
|
||||||
description: "A session entry related to an issued API token".to_string(),
|
description: "A session entry related to an issued API token".to_string(),
|
||||||
indexed: true,
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
unique: true,
|
unique: true,
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
syntax: SyntaxType::ApiToken,
|
syntax: SyntaxType::ApiToken,
|
||||||
|
@ -516,7 +622,8 @@ pub static ref SCHEMA_ATTR_USER_AUTH_TOKEN_SESSION: SchemaAttribute = SchemaAttr
|
||||||
uuid: UUID_SCHEMA_ATTR_USER_AUTH_TOKEN_SESSION,
|
uuid: UUID_SCHEMA_ATTR_USER_AUTH_TOKEN_SESSION,
|
||||||
name: Attribute::UserAuthTokenSession,
|
name: Attribute::UserAuthTokenSession,
|
||||||
description: "A session entry related to an issued user auth token".to_string(),
|
description: "A session entry related to an issued user auth token".to_string(),
|
||||||
indexed: true,
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
unique: true,
|
unique: true,
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
syntax: SyntaxType::Session,
|
syntax: SyntaxType::Session,
|
||||||
|
@ -527,7 +634,8 @@ pub static ref SCHEMA_ATTR_OAUTH2_SESSION: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_OAUTH2_SESSION,
|
uuid: UUID_SCHEMA_ATTR_OAUTH2_SESSION,
|
||||||
name: Attribute::OAuth2Session,
|
name: Attribute::OAuth2Session,
|
||||||
description: "A session entry to an active oauth2 session, bound to a parent user auth token".to_string(),
|
description: "A session entry to an active oauth2 session, bound to a parent user auth token".to_string(),
|
||||||
indexed: true,
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
syntax: SyntaxType::Oauth2Session,
|
syntax: SyntaxType::Oauth2Session,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -537,7 +645,8 @@ pub static ref SCHEMA_ATTR_SYNC_TOKEN_SESSION: SchemaAttribute = SchemaAttribute
|
||||||
uuid: UUID_SCHEMA_ATTR_SYNC_TOKEN_SESSION,
|
uuid: UUID_SCHEMA_ATTR_SYNC_TOKEN_SESSION,
|
||||||
name: Attribute::SyncTokenSession,
|
name: Attribute::SyncTokenSession,
|
||||||
description: "A session entry related to an issued sync token".to_string(),
|
description: "A session entry related to an issued sync token".to_string(),
|
||||||
indexed: true,
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
unique: true,
|
unique: true,
|
||||||
syntax: SyntaxType::ApiToken,
|
syntax: SyntaxType::ApiToken,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -547,6 +656,7 @@ pub static ref SCHEMA_ATTR_SYNC_COOKIE: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_SYNC_COOKIE,
|
uuid: UUID_SCHEMA_ATTR_SYNC_COOKIE,
|
||||||
name: Attribute::SyncCookie,
|
name: Attribute::SyncCookie,
|
||||||
description: "A private sync cookie for a remote IDM source".to_string(),
|
description: "A private sync cookie for a remote IDM source".to_string(),
|
||||||
|
|
||||||
syntax: SyntaxType::PrivateBinary,
|
syntax: SyntaxType::PrivateBinary,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -555,7 +665,8 @@ pub static ref SCHEMA_ATTR_GRANT_UI_HINT: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_GRANT_UI_HINT,
|
uuid: UUID_SCHEMA_ATTR_GRANT_UI_HINT,
|
||||||
name: Attribute::GrantUiHint,
|
name: Attribute::GrantUiHint,
|
||||||
description: "A UI hint that is granted via membership to a group".to_string(),
|
description: "A UI hint that is granted via membership to a group".to_string(),
|
||||||
indexed: true,
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
syntax: SyntaxType::UiHint,
|
syntax: SyntaxType::UiHint,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -565,6 +676,7 @@ pub static ref SCHEMA_ATTR_SYNC_CREDENTIAL_PORTAL: SchemaAttribute = SchemaAttri
|
||||||
uuid: UUID_SCHEMA_ATTR_SYNC_CREDENTIAL_PORTAL,
|
uuid: UUID_SCHEMA_ATTR_SYNC_CREDENTIAL_PORTAL,
|
||||||
name: Attribute::SyncCredentialPortal,
|
name: Attribute::SyncCredentialPortal,
|
||||||
description: "The url of an external credential portal for synced accounts to visit to update their credentials".to_string(),
|
description: "The url of an external credential portal for synced accounts to visit to update their credentials".to_string(),
|
||||||
|
|
||||||
syntax: SyntaxType::Url,
|
syntax: SyntaxType::Url,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -573,6 +685,7 @@ pub static ref SCHEMA_ATTR_SYNC_YIELD_AUTHORITY: SchemaAttribute = SchemaAttribu
|
||||||
uuid: UUID_SCHEMA_ATTR_SYNC_YIELD_AUTHORITY,
|
uuid: UUID_SCHEMA_ATTR_SYNC_YIELD_AUTHORITY,
|
||||||
name: Attribute::SyncYieldAuthority,
|
name: Attribute::SyncYieldAuthority,
|
||||||
description: "A set of attributes that have their authority yielded to Kanidm in a sync agreement".to_string(),
|
description: "A set of attributes that have their authority yielded to Kanidm in a sync agreement".to_string(),
|
||||||
|
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
syntax: SyntaxType::Utf8StringInsensitive,
|
syntax: SyntaxType::Utf8StringInsensitive,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -582,6 +695,7 @@ pub static ref SCHEMA_ATTR_CREDENTIAL_TYPE_MINIMUM: SchemaAttribute = SchemaAttr
|
||||||
uuid: UUID_SCHEMA_ATTR_CREDENTIAL_TYPE_MINIMUM,
|
uuid: UUID_SCHEMA_ATTR_CREDENTIAL_TYPE_MINIMUM,
|
||||||
name: Attribute::CredentialTypeMinimum,
|
name: Attribute::CredentialTypeMinimum,
|
||||||
description: "The minimum level of credential type that can satisfy this policy".to_string(),
|
description: "The minimum level of credential type that can satisfy this policy".to_string(),
|
||||||
|
|
||||||
multivalue: false,
|
multivalue: false,
|
||||||
syntax: SyntaxType::CredentialType,
|
syntax: SyntaxType::CredentialType,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -591,6 +705,7 @@ pub static ref SCHEMA_ATTR_LIMIT_SEARCH_MAX_RESULTS_DL6: SchemaAttribute = Schem
|
||||||
uuid: UUID_SCHEMA_ATTR_LIMIT_SEARCH_MAX_RESULTS,
|
uuid: UUID_SCHEMA_ATTR_LIMIT_SEARCH_MAX_RESULTS,
|
||||||
name: Attribute::LimitSearchMaxResults,
|
name: Attribute::LimitSearchMaxResults,
|
||||||
description: "The maximum number of query results that may be returned in a single operation".to_string(),
|
description: "The maximum number of query results that may be returned in a single operation".to_string(),
|
||||||
|
|
||||||
multivalue: false,
|
multivalue: false,
|
||||||
syntax: SyntaxType::Uint32,
|
syntax: SyntaxType::Uint32,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -600,6 +715,7 @@ pub static ref SCHEMA_ATTR_LIMIT_SEARCH_MAX_FILTER_TEST_DL6: SchemaAttribute = S
|
||||||
uuid: UUID_SCHEMA_ATTR_LIMIT_SEARCH_MAX_FILTER_TEST,
|
uuid: UUID_SCHEMA_ATTR_LIMIT_SEARCH_MAX_FILTER_TEST,
|
||||||
name: Attribute::LimitSearchMaxFilterTest,
|
name: Attribute::LimitSearchMaxFilterTest,
|
||||||
description: "The maximum number of entries that may be examined in a partially indexed query".to_string(),
|
description: "The maximum number of entries that may be examined in a partially indexed query".to_string(),
|
||||||
|
|
||||||
multivalue: false,
|
multivalue: false,
|
||||||
syntax: SyntaxType::Uint32,
|
syntax: SyntaxType::Uint32,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -619,7 +735,6 @@ pub static ref SCHEMA_ATTR_KEY_PROVIDER_DL6: SchemaAttribute = SchemaAttribute {
|
||||||
name: Attribute::KeyProvider,
|
name: Attribute::KeyProvider,
|
||||||
description: "".to_string(),
|
description: "".to_string(),
|
||||||
multivalue: false,
|
multivalue: false,
|
||||||
indexed: true,
|
|
||||||
syntax: SyntaxType::ReferenceUuid,
|
syntax: SyntaxType::ReferenceUuid,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -685,7 +800,6 @@ pub static ref SCHEMA_ATTR_REFERS_DL7: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_REFERS,
|
uuid: UUID_SCHEMA_ATTR_REFERS,
|
||||||
name: Attribute::Refers,
|
name: Attribute::Refers,
|
||||||
description: "A reference to linked object".to_string(),
|
description: "A reference to linked object".to_string(),
|
||||||
indexed: true,
|
|
||||||
multivalue: false,
|
multivalue: false,
|
||||||
syntax: SyntaxType::ReferenceUuid,
|
syntax: SyntaxType::ReferenceUuid,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -695,8 +809,8 @@ pub static ref SCHEMA_ATTR_LINKED_GROUP_DL8: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_LINKED_GROUP,
|
uuid: UUID_SCHEMA_ATTR_LINKED_GROUP,
|
||||||
name: Attribute::LinkedGroup,
|
name: Attribute::LinkedGroup,
|
||||||
description: "A reference linking a group to an entry".to_string(),
|
description: "A reference linking a group to an entry".to_string(),
|
||||||
|
|
||||||
multivalue: false,
|
multivalue: false,
|
||||||
indexed: true,
|
|
||||||
syntax: SyntaxType::ReferenceUuid,
|
syntax: SyntaxType::ReferenceUuid,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -705,6 +819,7 @@ pub static ref SCHEMA_ATTR_ALLOW_PRIMARY_CRED_FALLBACK_DL8: SchemaAttribute = Sc
|
||||||
uuid: UUID_SCHEMA_ATTR_ALLOW_PRIMARY_CRED_FALLBACK,
|
uuid: UUID_SCHEMA_ATTR_ALLOW_PRIMARY_CRED_FALLBACK,
|
||||||
name: Attribute::AllowPrimaryCredFallback,
|
name: Attribute::AllowPrimaryCredFallback,
|
||||||
description: "Allow fallback to primary password if no POSIX password exists".to_string(),
|
description: "Allow fallback to primary password if no POSIX password exists".to_string(),
|
||||||
|
|
||||||
multivalue: false,
|
multivalue: false,
|
||||||
syntax: SyntaxType::Boolean,
|
syntax: SyntaxType::Boolean,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -723,13 +838,57 @@ pub static ref SCHEMA_ATTR_APPLICATION_PASSWORD_DL8: SchemaAttribute = SchemaAtt
|
||||||
uuid: UUID_SCHEMA_ATTR_APPLICATION_PASSWORD,
|
uuid: UUID_SCHEMA_ATTR_APPLICATION_PASSWORD,
|
||||||
name: Attribute::ApplicationPassword,
|
name: Attribute::ApplicationPassword,
|
||||||
description: "A set of application passwords".to_string(),
|
description: "A set of application passwords".to_string(),
|
||||||
|
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
indexed: true,
|
|
||||||
syntax: SyntaxType::ApplicationPassword,
|
syntax: SyntaxType::ApplicationPassword,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
// === classes ===
|
// === classes ===
|
||||||
|
|
||||||
|
pub static ref SCHEMA_CLASS_PERSON: SchemaClass = SchemaClass {
|
||||||
|
uuid: UUID_SCHEMA_CLASS_PERSON,
|
||||||
|
name: EntryClass::Person.into(),
|
||||||
|
description: "Object representation of a person".to_string(),
|
||||||
|
|
||||||
|
sync_allowed: true,
|
||||||
|
systemmay: vec![
|
||||||
|
Attribute::Mail,
|
||||||
|
Attribute::LegalName,
|
||||||
|
],
|
||||||
|
systemmust: vec![
|
||||||
|
Attribute::DisplayName,
|
||||||
|
Attribute::Name,
|
||||||
|
Attribute::IdVerificationEcKey],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
pub static ref SCHEMA_CLASS_PERSON_DL5: SchemaClass = SchemaClass {
|
||||||
|
uuid: UUID_SCHEMA_CLASS_PERSON,
|
||||||
|
name: EntryClass::Person.into(),
|
||||||
|
description: "Object representation of a person".to_string(),
|
||||||
|
|
||||||
|
sync_allowed: true,
|
||||||
|
systemmay: vec![
|
||||||
|
Attribute::PrimaryCredential,
|
||||||
|
Attribute::PassKeys,
|
||||||
|
Attribute::AttestedPasskeys,
|
||||||
|
Attribute::CredentialUpdateIntentToken,
|
||||||
|
Attribute::SshPublicKey,
|
||||||
|
Attribute::RadiusSecret,
|
||||||
|
Attribute::OAuth2ConsentScopeMap,
|
||||||
|
Attribute::UserAuthTokenSession,
|
||||||
|
Attribute::OAuth2Session,
|
||||||
|
Attribute::Mail,
|
||||||
|
Attribute::LegalName,
|
||||||
|
],
|
||||||
|
systemmust: vec![
|
||||||
|
Attribute::IdVerificationEcKey
|
||||||
|
],
|
||||||
|
systemexcludes: vec![EntryClass::ServiceAccount.into(), EntryClass::Application.into()],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
pub static ref SCHEMA_CLASS_PERSON_DL8: SchemaClass = SchemaClass {
|
pub static ref SCHEMA_CLASS_PERSON_DL8: SchemaClass = SchemaClass {
|
||||||
uuid: UUID_SCHEMA_CLASS_PERSON,
|
uuid: UUID_SCHEMA_CLASS_PERSON,
|
||||||
name: EntryClass::Person.into(),
|
name: EntryClass::Person.into(),
|
||||||
|
@ -802,6 +961,24 @@ pub static ref SCHEMA_CLASS_DYNGROUP: SchemaClass = SchemaClass {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub static ref SCHEMA_CLASS_ACCOUNT_POLICY_DL6: SchemaClass = SchemaClass {
|
||||||
|
uuid: UUID_SCHEMA_CLASS_ACCOUNT_POLICY,
|
||||||
|
name: EntryClass::AccountPolicy.into(),
|
||||||
|
description: "Policies applied to accounts that are members of a group".to_string(),
|
||||||
|
|
||||||
|
systemmay: vec![
|
||||||
|
Attribute::AuthSessionExpiry,
|
||||||
|
Attribute::PrivilegeExpiry,
|
||||||
|
Attribute::AuthPasswordMinimumLength,
|
||||||
|
Attribute::CredentialTypeMinimum,
|
||||||
|
Attribute::WebauthnAttestationCaList,
|
||||||
|
Attribute::LimitSearchMaxResults,
|
||||||
|
Attribute::LimitSearchMaxFilterTest,
|
||||||
|
],
|
||||||
|
systemsupplements: vec![Attribute::Group.into()],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
pub static ref SCHEMA_CLASS_ACCOUNT_POLICY_DL8: SchemaClass = SchemaClass {
|
pub static ref SCHEMA_CLASS_ACCOUNT_POLICY_DL8: SchemaClass = SchemaClass {
|
||||||
uuid: UUID_SCHEMA_CLASS_ACCOUNT_POLICY,
|
uuid: UUID_SCHEMA_CLASS_ACCOUNT_POLICY,
|
||||||
name: EntryClass::AccountPolicy.into(),
|
name: EntryClass::AccountPolicy.into(),
|
||||||
|
@ -821,6 +998,40 @@ pub static ref SCHEMA_CLASS_ACCOUNT_POLICY_DL8: SchemaClass = SchemaClass {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub static ref SCHEMA_CLASS_ACCOUNT: SchemaClass = SchemaClass {
|
||||||
|
uuid: UUID_SCHEMA_CLASS_ACCOUNT,
|
||||||
|
name: EntryClass::Account.into(),
|
||||||
|
description: "Object representation of an account".to_string(),
|
||||||
|
|
||||||
|
sync_allowed: true,
|
||||||
|
systemmay: vec![
|
||||||
|
Attribute::PrimaryCredential,
|
||||||
|
Attribute::PassKeys,
|
||||||
|
Attribute::AttestedPasskeys,
|
||||||
|
Attribute::CredentialUpdateIntentToken,
|
||||||
|
Attribute::SshPublicKey,
|
||||||
|
Attribute::RadiusSecret,
|
||||||
|
Attribute::AccountExpire,
|
||||||
|
Attribute::AccountValidFrom,
|
||||||
|
Attribute::Mail,
|
||||||
|
Attribute::OAuth2ConsentScopeMap,
|
||||||
|
Attribute::UserAuthTokenSession,
|
||||||
|
Attribute::OAuth2Session,
|
||||||
|
Attribute::Description,
|
||||||
|
Attribute::NameHistory,
|
||||||
|
],
|
||||||
|
systemmust: vec![
|
||||||
|
Attribute::DisplayName,
|
||||||
|
Attribute::Name,
|
||||||
|
Attribute::Spn
|
||||||
|
],
|
||||||
|
systemsupplements: vec![
|
||||||
|
EntryClass::Person.into(),
|
||||||
|
EntryClass::ServiceAccount.into(),
|
||||||
|
],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
pub static ref SCHEMA_CLASS_ACCOUNT_DL5: SchemaClass = SchemaClass {
|
pub static ref SCHEMA_CLASS_ACCOUNT_DL5: SchemaClass = SchemaClass {
|
||||||
uuid: UUID_SCHEMA_CLASS_ACCOUNT,
|
uuid: UUID_SCHEMA_CLASS_ACCOUNT,
|
||||||
name: EntryClass::Account.into(),
|
name: EntryClass::Account.into(),
|
||||||
|
@ -845,6 +1056,29 @@ pub static ref SCHEMA_CLASS_ACCOUNT_DL5: SchemaClass = SchemaClass {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub static ref SCHEMA_CLASS_SERVICE_ACCOUNT_DL6: SchemaClass = SchemaClass {
|
||||||
|
uuid: UUID_SCHEMA_CLASS_SERVICE_ACCOUNT,
|
||||||
|
name: EntryClass::ServiceAccount.into(),
|
||||||
|
description: "Object representation of service account".to_string(),
|
||||||
|
|
||||||
|
sync_allowed: true,
|
||||||
|
systemmay: vec![
|
||||||
|
Attribute::SshPublicKey,
|
||||||
|
Attribute::UserAuthTokenSession,
|
||||||
|
Attribute::OAuth2Session,
|
||||||
|
Attribute::OAuth2ConsentScopeMap,
|
||||||
|
Attribute::Description,
|
||||||
|
|
||||||
|
Attribute::Mail,
|
||||||
|
Attribute::PrimaryCredential,
|
||||||
|
Attribute::ApiTokenSession,
|
||||||
|
|
||||||
|
Attribute::JwsEs256PrivateKey,
|
||||||
|
],
|
||||||
|
systemexcludes: vec![EntryClass::Person.into()],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
pub static ref SCHEMA_CLASS_SERVICE_ACCOUNT_DL7: SchemaClass = SchemaClass {
|
pub static ref SCHEMA_CLASS_SERVICE_ACCOUNT_DL7: SchemaClass = SchemaClass {
|
||||||
uuid: UUID_SCHEMA_CLASS_SERVICE_ACCOUNT,
|
uuid: UUID_SCHEMA_CLASS_SERVICE_ACCOUNT,
|
||||||
name: EntryClass::ServiceAccount.into(),
|
name: EntryClass::ServiceAccount.into(),
|
||||||
|
@ -866,6 +1100,23 @@ pub static ref SCHEMA_CLASS_SERVICE_ACCOUNT_DL7: SchemaClass = SchemaClass {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub static ref SCHEMA_CLASS_SYNC_ACCOUNT_DL6: SchemaClass = SchemaClass {
|
||||||
|
uuid: UUID_SCHEMA_CLASS_SYNC_ACCOUNT,
|
||||||
|
name: EntryClass::SyncAccount.into(),
|
||||||
|
description: "Object representation of sync account".to_string(),
|
||||||
|
|
||||||
|
systemmust: vec![Attribute::Name],
|
||||||
|
systemmay: vec![
|
||||||
|
Attribute::SyncTokenSession,
|
||||||
|
Attribute::SyncCookie,
|
||||||
|
Attribute::SyncCredentialPortal,
|
||||||
|
Attribute::SyncYieldAuthority,
|
||||||
|
Attribute::JwsEs256PrivateKey,
|
||||||
|
],
|
||||||
|
systemexcludes: vec![EntryClass::Account.into()],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
pub static ref SCHEMA_CLASS_SYNC_ACCOUNT_DL7: SchemaClass = SchemaClass {
|
pub static ref SCHEMA_CLASS_SYNC_ACCOUNT_DL7: SchemaClass = SchemaClass {
|
||||||
uuid: UUID_SCHEMA_CLASS_SYNC_ACCOUNT,
|
uuid: UUID_SCHEMA_CLASS_SYNC_ACCOUNT,
|
||||||
name: EntryClass::SyncAccount.into(),
|
name: EntryClass::SyncAccount.into(),
|
||||||
|
@ -882,6 +1133,100 @@ pub static ref SCHEMA_CLASS_SYNC_ACCOUNT_DL7: SchemaClass = SchemaClass {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub static ref SCHEMA_CLASS_DOMAIN_INFO_DL6: SchemaClass = SchemaClass {
|
||||||
|
uuid: UUID_SCHEMA_CLASS_DOMAIN_INFO,
|
||||||
|
name: EntryClass::DomainInfo.into(),
|
||||||
|
description: "Local domain information and configuration".to_string(),
|
||||||
|
|
||||||
|
systemmay: vec![
|
||||||
|
Attribute::DomainSsid,
|
||||||
|
Attribute::DomainLdapBasedn,
|
||||||
|
Attribute::LdapAllowUnixPwBind,
|
||||||
|
Attribute::PrivateCookieKey,
|
||||||
|
Attribute::FernetPrivateKeyStr,
|
||||||
|
Attribute::Es256PrivateKeyDer,
|
||||||
|
Attribute::PatchLevel,
|
||||||
|
Attribute::DomainDevelopmentTaint,
|
||||||
|
],
|
||||||
|
systemmust: vec![
|
||||||
|
Attribute::Name,
|
||||||
|
Attribute::DomainUuid,
|
||||||
|
Attribute::DomainName,
|
||||||
|
Attribute::DomainDisplayName,
|
||||||
|
Attribute::Version,
|
||||||
|
],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
pub static ref SCHEMA_CLASS_DOMAIN_INFO_DL7: SchemaClass = SchemaClass {
|
||||||
|
uuid: UUID_SCHEMA_CLASS_DOMAIN_INFO,
|
||||||
|
name: EntryClass::DomainInfo.into(),
|
||||||
|
description: "Local domain information and configuration".to_string(),
|
||||||
|
|
||||||
|
systemmay: vec![
|
||||||
|
Attribute::DomainSsid,
|
||||||
|
Attribute::DomainLdapBasedn,
|
||||||
|
Attribute::LdapAllowUnixPwBind,
|
||||||
|
Attribute::PatchLevel,
|
||||||
|
Attribute::DomainDevelopmentTaint,
|
||||||
|
],
|
||||||
|
systemmust: vec![
|
||||||
|
Attribute::Name,
|
||||||
|
Attribute::DomainUuid,
|
||||||
|
Attribute::DomainName,
|
||||||
|
Attribute::DomainDisplayName,
|
||||||
|
Attribute::Version,
|
||||||
|
],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
pub static ref SCHEMA_CLASS_DOMAIN_INFO_DL8: SchemaClass = SchemaClass {
|
||||||
|
uuid: UUID_SCHEMA_CLASS_DOMAIN_INFO,
|
||||||
|
name: EntryClass::DomainInfo.into(),
|
||||||
|
description: "Local domain information and configuration".to_string(),
|
||||||
|
|
||||||
|
systemmay: vec![
|
||||||
|
Attribute::DomainSsid,
|
||||||
|
Attribute::DomainLdapBasedn,
|
||||||
|
Attribute::LdapAllowUnixPwBind,
|
||||||
|
Attribute::Image,
|
||||||
|
Attribute::PatchLevel,
|
||||||
|
Attribute::DomainDevelopmentTaint,
|
||||||
|
],
|
||||||
|
systemmust: vec![
|
||||||
|
Attribute::Name,
|
||||||
|
Attribute::DomainUuid,
|
||||||
|
Attribute::DomainName,
|
||||||
|
Attribute::DomainDisplayName,
|
||||||
|
Attribute::Version,
|
||||||
|
],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
pub static ref SCHEMA_CLASS_DOMAIN_INFO_DL9: SchemaClass = SchemaClass {
|
||||||
|
uuid: UUID_SCHEMA_CLASS_DOMAIN_INFO,
|
||||||
|
name: EntryClass::DomainInfo.into(),
|
||||||
|
description: "Local domain information and configuration".to_string(),
|
||||||
|
|
||||||
|
systemmay: vec![
|
||||||
|
Attribute::DomainSsid,
|
||||||
|
Attribute::DomainLdapBasedn,
|
||||||
|
Attribute::LdapAllowUnixPwBind,
|
||||||
|
Attribute::Image,
|
||||||
|
Attribute::PatchLevel,
|
||||||
|
Attribute::DomainDevelopmentTaint,
|
||||||
|
Attribute::DomainAllowEasterEggs,
|
||||||
|
],
|
||||||
|
systemmust: vec![
|
||||||
|
Attribute::Name,
|
||||||
|
Attribute::DomainUuid,
|
||||||
|
Attribute::DomainName,
|
||||||
|
Attribute::DomainDisplayName,
|
||||||
|
Attribute::Version,
|
||||||
|
],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
pub static ref SCHEMA_CLASS_DOMAIN_INFO_DL10: SchemaClass = SchemaClass {
|
pub static ref SCHEMA_CLASS_DOMAIN_INFO_DL10: SchemaClass = SchemaClass {
|
||||||
uuid: UUID_SCHEMA_CLASS_DOMAIN_INFO,
|
uuid: UUID_SCHEMA_CLASS_DOMAIN_INFO,
|
||||||
name: EntryClass::DomainInfo.into(),
|
name: EntryClass::DomainInfo.into(),
|
||||||
|
@ -945,6 +1290,83 @@ pub static ref SCHEMA_CLASS_SYSTEM_CONFIG: SchemaClass = SchemaClass {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub static ref SCHEMA_CLASS_OAUTH2_RS_DL4: SchemaClass = SchemaClass {
|
||||||
|
uuid: UUID_SCHEMA_CLASS_OAUTH2_RS,
|
||||||
|
name: EntryClass::OAuth2ResourceServer.into(),
|
||||||
|
description: "The class representing a configured Oauth2 Resource Server".to_string(),
|
||||||
|
|
||||||
|
systemmay: vec![
|
||||||
|
Attribute::Description,
|
||||||
|
Attribute::OAuth2RsScopeMap,
|
||||||
|
Attribute::OAuth2RsSupScopeMap,
|
||||||
|
Attribute::Rs256PrivateKeyDer,
|
||||||
|
Attribute::OAuth2JwtLegacyCryptoEnable,
|
||||||
|
Attribute::OAuth2PreferShortUsername,
|
||||||
|
Attribute::OAuth2RsOriginLanding,
|
||||||
|
Attribute::Image,
|
||||||
|
Attribute::OAuth2RsClaimMap,
|
||||||
|
],
|
||||||
|
systemmust: vec![
|
||||||
|
Attribute::OAuth2RsName,
|
||||||
|
Attribute::DisplayName,
|
||||||
|
Attribute::OAuth2RsOrigin,
|
||||||
|
Attribute::OAuth2RsTokenKey,
|
||||||
|
Attribute::Es256PrivateKeyDer,
|
||||||
|
],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
pub static ref SCHEMA_CLASS_OAUTH2_RS_DL5: SchemaClass = SchemaClass {
|
||||||
|
uuid: UUID_SCHEMA_CLASS_OAUTH2_RS,
|
||||||
|
name: EntryClass::OAuth2ResourceServer.into(),
|
||||||
|
description: "The class representing a configured Oauth2 Resource Server".to_string(),
|
||||||
|
|
||||||
|
systemmay: vec![
|
||||||
|
Attribute::Description,
|
||||||
|
Attribute::OAuth2RsScopeMap,
|
||||||
|
Attribute::OAuth2RsSupScopeMap,
|
||||||
|
Attribute::Rs256PrivateKeyDer,
|
||||||
|
Attribute::OAuth2JwtLegacyCryptoEnable,
|
||||||
|
Attribute::OAuth2PreferShortUsername,
|
||||||
|
Attribute::OAuth2RsOriginLanding,
|
||||||
|
Attribute::Image,
|
||||||
|
Attribute::OAuth2RsClaimMap,
|
||||||
|
Attribute::OAuth2Session,
|
||||||
|
],
|
||||||
|
systemmust: vec![
|
||||||
|
Attribute::OAuth2RsOrigin,
|
||||||
|
Attribute::OAuth2RsTokenKey,
|
||||||
|
Attribute::Es256PrivateKeyDer,
|
||||||
|
],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
pub static ref SCHEMA_CLASS_OAUTH2_RS_DL7: SchemaClass = SchemaClass {
|
||||||
|
uuid: UUID_SCHEMA_CLASS_OAUTH2_RS,
|
||||||
|
name: EntryClass::OAuth2ResourceServer.into(),
|
||||||
|
description: "The class representing a configured OAuth2 Client".to_string(),
|
||||||
|
|
||||||
|
systemmay: vec![
|
||||||
|
Attribute::Description,
|
||||||
|
Attribute::OAuth2RsScopeMap,
|
||||||
|
Attribute::OAuth2RsSupScopeMap,
|
||||||
|
Attribute::Rs256PrivateKeyDer,
|
||||||
|
Attribute::OAuth2JwtLegacyCryptoEnable,
|
||||||
|
Attribute::OAuth2PreferShortUsername,
|
||||||
|
Attribute::Image,
|
||||||
|
Attribute::OAuth2RsClaimMap,
|
||||||
|
Attribute::OAuth2Session,
|
||||||
|
Attribute::OAuth2RsOrigin,
|
||||||
|
Attribute::OAuth2StrictRedirectUri,
|
||||||
|
],
|
||||||
|
systemmust: vec![
|
||||||
|
Attribute::OAuth2RsOriginLanding,
|
||||||
|
Attribute::OAuth2RsTokenKey,
|
||||||
|
Attribute::Es256PrivateKeyDer,
|
||||||
|
],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
pub static ref SCHEMA_CLASS_OAUTH2_RS_DL9: SchemaClass = SchemaClass {
|
pub static ref SCHEMA_CLASS_OAUTH2_RS_DL9: SchemaClass = SchemaClass {
|
||||||
uuid: UUID_SCHEMA_CLASS_OAUTH2_RS,
|
uuid: UUID_SCHEMA_CLASS_OAUTH2_RS,
|
||||||
name: EntryClass::OAuth2ResourceServer.into(),
|
name: EntryClass::OAuth2ResourceServer.into(),
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
use crate::constants::entries::{Attribute, EntryClass};
|
use crate::constants::entries::{Attribute, EntryClass};
|
||||||
use crate::constants::uuids::*;
|
use crate::constants::uuids::*;
|
||||||
use crate::schema::{SchemaAttribute, SchemaClass};
|
use crate::schema::{SchemaAttribute, SchemaClass};
|
||||||
|
use crate::value::IndexType;
|
||||||
use crate::value::SyntaxType;
|
use crate::value::SyntaxType;
|
||||||
|
|
||||||
lazy_static!(
|
lazy_static!(
|
||||||
|
@ -10,6 +11,8 @@ pub static ref SCHEMA_ATTR_DISPLAYNAME: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_DISPLAYNAME,
|
uuid: UUID_SCHEMA_ATTR_DISPLAYNAME,
|
||||||
name: Attribute::DisplayName,
|
name: Attribute::DisplayName,
|
||||||
description: "The publicly visible display name of this person".to_string(),
|
description: "The publicly visible display name of this person".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::Utf8String,
|
syntax: SyntaxType::Utf8String,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -19,6 +22,8 @@ pub static ref SCHEMA_ATTR_DISPLAYNAME_DL7: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_DISPLAYNAME,
|
uuid: UUID_SCHEMA_ATTR_DISPLAYNAME,
|
||||||
name: Attribute::DisplayName,
|
name: Attribute::DisplayName,
|
||||||
description: "The publicly visible display name of this person".to_string(),
|
description: "The publicly visible display name of this person".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality, IndexType::SubString],
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::Utf8String,
|
syntax: SyntaxType::Utf8String,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -28,6 +33,8 @@ pub static ref SCHEMA_ATTR_MAIL: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_MAIL,
|
uuid: UUID_SCHEMA_ATTR_MAIL,
|
||||||
name: Attribute::Mail,
|
name: Attribute::Mail,
|
||||||
description: "Mail addresses of the object".to_string(),
|
description: "Mail addresses of the object".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
unique: true,
|
unique: true,
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
|
@ -39,6 +46,8 @@ pub static ref SCHEMA_ATTR_MAIL_DL7: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_MAIL,
|
uuid: UUID_SCHEMA_ATTR_MAIL,
|
||||||
name: Attribute::Mail,
|
name: Attribute::Mail,
|
||||||
description: "Mail addresses of the object".to_string(),
|
description: "Mail addresses of the object".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality, IndexType::SubString],
|
||||||
unique: true,
|
unique: true,
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
|
@ -50,6 +59,8 @@ pub static ref SCHEMA_ATTR_EC_KEY_PRIVATE: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_EC_KEY_PRIVATE,
|
uuid: UUID_SCHEMA_ATTR_EC_KEY_PRIVATE,
|
||||||
name: Attribute::IdVerificationEcKey,
|
name: Attribute::IdVerificationEcKey,
|
||||||
description: "Account verification private key".to_string(),
|
description: "Account verification private key".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Presence],
|
||||||
unique: false,
|
unique: false,
|
||||||
sync_allowed: false,
|
sync_allowed: false,
|
||||||
syntax: SyntaxType::EcKeyPrivate,
|
syntax: SyntaxType::EcKeyPrivate,
|
||||||
|
@ -71,6 +82,8 @@ pub static ref SCHEMA_ATTR_PRIMARY_CREDENTIAL: SchemaAttribute = SchemaAttribute
|
||||||
uuid: UUID_SCHEMA_ATTR_PRIMARY_CREDENTIAL,
|
uuid: UUID_SCHEMA_ATTR_PRIMARY_CREDENTIAL,
|
||||||
name: Attribute::PrimaryCredential,
|
name: Attribute::PrimaryCredential,
|
||||||
description: "Primary credential material of the account for authentication interactively".to_string(),
|
description: "Primary credential material of the account for authentication interactively".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Presence],
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::Credential,
|
syntax: SyntaxType::Credential,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -80,6 +93,8 @@ pub static ref SCHEMA_ATTR_LEGALNAME: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_LEGALNAME,
|
uuid: UUID_SCHEMA_ATTR_LEGALNAME,
|
||||||
name: Attribute::LegalName,
|
name: Attribute::LegalName,
|
||||||
description: "The private and sensitive legal name of this person".to_string(),
|
description: "The private and sensitive legal name of this person".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::Utf8String,
|
syntax: SyntaxType::Utf8String,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -89,6 +104,8 @@ pub static ref SCHEMA_ATTR_LEGALNAME_DL7: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_LEGALNAME,
|
uuid: UUID_SCHEMA_ATTR_LEGALNAME,
|
||||||
name: Attribute::LegalName,
|
name: Attribute::LegalName,
|
||||||
description: "The private and sensitive legal name of this person".to_string(),
|
description: "The private and sensitive legal name of this person".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality, IndexType::SubString],
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::Utf8String,
|
syntax: SyntaxType::Utf8String,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -98,6 +115,8 @@ pub static ref SCHEMA_ATTR_NAME_HISTORY: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_NAME_HISTORY,
|
uuid: UUID_SCHEMA_ATTR_NAME_HISTORY,
|
||||||
name: Attribute::NameHistory,
|
name: Attribute::NameHistory,
|
||||||
description: "The history of names that a person has had".to_string(),
|
description: "The history of names that a person has had".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::AuditLogString,
|
syntax: SyntaxType::AuditLogString,
|
||||||
|
@ -118,6 +137,8 @@ pub static ref SCHEMA_ATTR_DOMAIN_NAME: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_DOMAIN_NAME,
|
uuid: UUID_SCHEMA_ATTR_DOMAIN_NAME,
|
||||||
name: Attribute::DomainName,
|
name: Attribute::DomainName,
|
||||||
description: "The domain's DNS name for webauthn and SPN generation purposes".to_string(),
|
description: "The domain's DNS name for webauthn and SPN generation purposes".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality, IndexType::Presence],
|
||||||
unique: true,
|
unique: true,
|
||||||
syntax: SyntaxType::Utf8StringIname,
|
syntax: SyntaxType::Utf8StringIname,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -147,6 +168,8 @@ pub static ref SCHEMA_ATTR_DOMAIN_DISPLAY_NAME: SchemaAttribute = SchemaAttribut
|
||||||
uuid: UUID_SCHEMA_ATTR_DOMAIN_DISPLAY_NAME,
|
uuid: UUID_SCHEMA_ATTR_DOMAIN_DISPLAY_NAME,
|
||||||
name: Attribute::DomainDisplayName,
|
name: Attribute::DomainDisplayName,
|
||||||
description: "The user-facing display name of the Kanidm domain".to_string(),
|
description: "The user-facing display name of the Kanidm domain".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
syntax: SyntaxType::Utf8String,
|
syntax: SyntaxType::Utf8String,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -155,6 +178,8 @@ pub static ref SCHEMA_ATTR_DOMAIN_UUID: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_DOMAIN_UUID,
|
uuid: UUID_SCHEMA_ATTR_DOMAIN_UUID,
|
||||||
name: Attribute::DomainUuid,
|
name: Attribute::DomainUuid,
|
||||||
description: "The domain's uuid, used in CSN and trust relationships".to_string(),
|
description: "The domain's uuid, used in CSN and trust relationships".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
unique: true,
|
unique: true,
|
||||||
syntax: SyntaxType::Uuid,
|
syntax: SyntaxType::Uuid,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -164,6 +189,8 @@ pub static ref SCHEMA_ATTR_DOMAIN_SSID: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_DOMAIN_SSID,
|
uuid: UUID_SCHEMA_ATTR_DOMAIN_SSID,
|
||||||
name: Attribute::DomainSsid,
|
name: Attribute::DomainSsid,
|
||||||
description: "The domains site-wide SSID for device autoconfiguration of wireless".to_string(),
|
description: "The domains site-wide SSID for device autoconfiguration of wireless".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
unique: true,
|
unique: true,
|
||||||
syntax: SyntaxType::Utf8String,
|
syntax: SyntaxType::Utf8String,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -210,6 +237,8 @@ pub static ref SCHEMA_ATTR_GIDNUMBER: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_GIDNUMBER,
|
uuid: UUID_SCHEMA_ATTR_GIDNUMBER,
|
||||||
name: Attribute::GidNumber,
|
name: Attribute::GidNumber,
|
||||||
description: "The groupid (uid) number of a group or account.to_string(). This is the same value as the UID number on posix accounts for security reasons".to_string(),
|
description: "The groupid (uid) number of a group or account.to_string(). This is the same value as the UID number on posix accounts for security reasons".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
unique: true,
|
unique: true,
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::Uint32,
|
syntax: SyntaxType::Uint32,
|
||||||
|
@ -267,6 +296,8 @@ pub static ref SCHEMA_ATTR_UNIX_PASSWORD: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_UNIX_PASSWORD,
|
uuid: UUID_SCHEMA_ATTR_UNIX_PASSWORD,
|
||||||
name: Attribute::UnixPassword,
|
name: Attribute::UnixPassword,
|
||||||
description: "A POSIX user's UNIX login password".to_string(),
|
description: "A POSIX user's UNIX login password".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Presence],
|
||||||
syntax: SyntaxType::Credential,
|
syntax: SyntaxType::Credential,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -275,6 +306,8 @@ pub static ref SCHEMA_ATTR_NSUNIQUEID: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_NSUNIQUEID,
|
uuid: UUID_SCHEMA_ATTR_NSUNIQUEID,
|
||||||
name: Attribute::NsUniqueId,
|
name: Attribute::NsUniqueId,
|
||||||
description: "A unique id compatibility for 389-ds/dsee".to_string(),
|
description: "A unique id compatibility for 389-ds/dsee".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
unique: true,
|
unique: true,
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::NsUniqueId,
|
syntax: SyntaxType::NsUniqueId,
|
||||||
|
@ -315,6 +348,8 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_NAME: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_NAME,
|
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_NAME,
|
||||||
name: Attribute::OAuth2RsName,
|
name: Attribute::OAuth2RsName,
|
||||||
description: "The unique name of an external Oauth2 resource".to_string(),
|
description: "The unique name of an external Oauth2 resource".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
unique: true,
|
unique: true,
|
||||||
syntax: SyntaxType::Utf8StringIname,
|
syntax: SyntaxType::Utf8StringIname,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -362,6 +397,8 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_CLAIM_MAP_DL4: SchemaAttribute = SchemaAttr
|
||||||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_CLAIM_MAP,
|
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_CLAIM_MAP,
|
||||||
name: Attribute::OAuth2RsClaimMap,
|
name: Attribute::OAuth2RsClaimMap,
|
||||||
description: "A set of custom claims mapped to group memberships of accounts".to_string(),
|
description: "A set of custom claims mapped to group memberships of accounts".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
// CHANGE ME
|
// CHANGE ME
|
||||||
syntax: SyntaxType::OauthClaimMap,
|
syntax: SyntaxType::OauthClaimMap,
|
||||||
|
@ -372,6 +409,8 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_SCOPE_MAP: SchemaAttribute = SchemaAttribut
|
||||||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_SCOPE_MAP,
|
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_SCOPE_MAP,
|
||||||
name: Attribute::OAuth2RsScopeMap,
|
name: Attribute::OAuth2RsScopeMap,
|
||||||
description: "A reference to a group mapped to scopes for the associated oauth2 resource server".to_string(),
|
description: "A reference to a group mapped to scopes for the associated oauth2 resource server".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
syntax: SyntaxType::OauthScopeMap,
|
syntax: SyntaxType::OauthScopeMap,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -381,6 +420,8 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_SUP_SCOPE_MAP: SchemaAttribute = SchemaAttr
|
||||||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_SUP_SCOPE_MAP,
|
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_SUP_SCOPE_MAP,
|
||||||
name: Attribute::OAuth2RsSupScopeMap,
|
name: Attribute::OAuth2RsSupScopeMap,
|
||||||
description: "A reference to a group mapped to scopes for the associated oauth2 resource server".to_string(),
|
description: "A reference to a group mapped to scopes for the associated oauth2 resource server".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
syntax: SyntaxType::OauthScopeMap,
|
syntax: SyntaxType::OauthScopeMap,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -418,6 +459,8 @@ pub static ref SCHEMA_ATTR_OAUTH2_CONSENT_SCOPE_MAP: SchemaAttribute = SchemaAtt
|
||||||
uuid: UUID_SCHEMA_ATTR_OAUTH2_CONSENT_SCOPE_MAP,
|
uuid: UUID_SCHEMA_ATTR_OAUTH2_CONSENT_SCOPE_MAP,
|
||||||
name: Attribute::OAuth2ConsentScopeMap,
|
name: Attribute::OAuth2ConsentScopeMap,
|
||||||
description: "A set of scopes mapped from a relying server to a user, where the user has previously consented to the following. If changed or deleted, consent will be re-sought".to_string(),
|
description: "A set of scopes mapped from a relying server to a user, where the user has previously consented to the following. If changed or deleted, consent will be re-sought".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
syntax: SyntaxType::OauthScopeMap,
|
syntax: SyntaxType::OauthScopeMap,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -464,6 +507,8 @@ pub static ref SCHEMA_ATTR_JWS_ES256_PRIVATE_KEY: SchemaAttribute = SchemaAttrib
|
||||||
uuid: UUID_SCHEMA_ATTR_JWS_ES256_PRIVATE_KEY,
|
uuid: UUID_SCHEMA_ATTR_JWS_ES256_PRIVATE_KEY,
|
||||||
name: Attribute::JwsEs256PrivateKey,
|
name: Attribute::JwsEs256PrivateKey,
|
||||||
description: "An es256 private key for jws".to_string(),
|
description: "An es256 private key for jws".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
unique: true,
|
unique: true,
|
||||||
syntax: SyntaxType::JwsKeyEs256,
|
syntax: SyntaxType::JwsKeyEs256,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -501,6 +546,8 @@ pub static ref SCHEMA_ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN: SchemaAttribute = Sch
|
||||||
uuid: UUID_SCHEMA_ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN,
|
uuid: UUID_SCHEMA_ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN,
|
||||||
name: Attribute::CredentialUpdateIntentToken,
|
name: Attribute::CredentialUpdateIntentToken,
|
||||||
description: "The status of a credential update intent token".to_string(),
|
description: "The status of a credential update intent token".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
syntax: SyntaxType::IntentToken,
|
syntax: SyntaxType::IntentToken,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -510,6 +557,8 @@ pub static ref SCHEMA_ATTR_PASSKEYS: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_PASSKEYS,
|
uuid: UUID_SCHEMA_ATTR_PASSKEYS,
|
||||||
name: Attribute::PassKeys,
|
name: Attribute::PassKeys,
|
||||||
description: "A set of registered passkeys".to_string(),
|
description: "A set of registered passkeys".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::Passkey,
|
syntax: SyntaxType::Passkey,
|
||||||
|
@ -520,6 +569,8 @@ pub static ref SCHEMA_ATTR_ATTESTED_PASSKEYS: SchemaAttribute = SchemaAttribute
|
||||||
uuid: UUID_SCHEMA_ATTR_ATTESTED_PASSKEYS,
|
uuid: UUID_SCHEMA_ATTR_ATTESTED_PASSKEYS,
|
||||||
name: Attribute::AttestedPasskeys,
|
name: Attribute::AttestedPasskeys,
|
||||||
description: "A set of registered device keys".to_string(),
|
description: "A set of registered device keys".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::AttestedPasskey,
|
syntax: SyntaxType::AttestedPasskey,
|
||||||
|
@ -548,6 +599,8 @@ pub static ref SCHEMA_ATTR_API_TOKEN_SESSION: SchemaAttribute = SchemaAttribute
|
||||||
uuid: UUID_SCHEMA_ATTR_API_TOKEN_SESSION,
|
uuid: UUID_SCHEMA_ATTR_API_TOKEN_SESSION,
|
||||||
name: Attribute::ApiTokenSession,
|
name: Attribute::ApiTokenSession,
|
||||||
description: "A session entry related to an issued API token".to_string(),
|
description: "A session entry related to an issued API token".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
unique: true,
|
unique: true,
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
syntax: SyntaxType::ApiToken,
|
syntax: SyntaxType::ApiToken,
|
||||||
|
@ -558,6 +611,8 @@ pub static ref SCHEMA_ATTR_USER_AUTH_TOKEN_SESSION: SchemaAttribute = SchemaAttr
|
||||||
uuid: UUID_SCHEMA_ATTR_USER_AUTH_TOKEN_SESSION,
|
uuid: UUID_SCHEMA_ATTR_USER_AUTH_TOKEN_SESSION,
|
||||||
name: Attribute::UserAuthTokenSession,
|
name: Attribute::UserAuthTokenSession,
|
||||||
description: "A session entry related to an issued user auth token".to_string(),
|
description: "A session entry related to an issued user auth token".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
unique: true,
|
unique: true,
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
syntax: SyntaxType::Session,
|
syntax: SyntaxType::Session,
|
||||||
|
@ -568,6 +623,8 @@ pub static ref SCHEMA_ATTR_OAUTH2_SESSION: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_OAUTH2_SESSION,
|
uuid: UUID_SCHEMA_ATTR_OAUTH2_SESSION,
|
||||||
name: Attribute::OAuth2Session,
|
name: Attribute::OAuth2Session,
|
||||||
description: "A session entry to an active oauth2 session, bound to a parent user auth token".to_string(),
|
description: "A session entry to an active oauth2 session, bound to a parent user auth token".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
syntax: SyntaxType::Oauth2Session,
|
syntax: SyntaxType::Oauth2Session,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -577,6 +634,8 @@ pub static ref SCHEMA_ATTR_SYNC_TOKEN_SESSION: SchemaAttribute = SchemaAttribute
|
||||||
uuid: UUID_SCHEMA_ATTR_SYNC_TOKEN_SESSION,
|
uuid: UUID_SCHEMA_ATTR_SYNC_TOKEN_SESSION,
|
||||||
name: Attribute::SyncTokenSession,
|
name: Attribute::SyncTokenSession,
|
||||||
description: "A session entry related to an issued sync token".to_string(),
|
description: "A session entry related to an issued sync token".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
unique: true,
|
unique: true,
|
||||||
syntax: SyntaxType::ApiToken,
|
syntax: SyntaxType::ApiToken,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -595,6 +654,8 @@ pub static ref SCHEMA_ATTR_GRANT_UI_HINT: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_GRANT_UI_HINT,
|
uuid: UUID_SCHEMA_ATTR_GRANT_UI_HINT,
|
||||||
name: Attribute::GrantUiHint,
|
name: Attribute::GrantUiHint,
|
||||||
description: "A UI hint that is granted via membership to a group".to_string(),
|
description: "A UI hint that is granted via membership to a group".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
syntax: SyntaxType::UiHint,
|
syntax: SyntaxType::UiHint,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
use crate::constants::entries::{Attribute, EntryClass};
|
use crate::constants::entries::{Attribute, EntryClass};
|
||||||
use crate::constants::uuids::*;
|
use crate::constants::uuids::*;
|
||||||
use crate::schema::{SchemaAttribute, SchemaClass};
|
use crate::schema::{SchemaAttribute, SchemaClass};
|
||||||
|
use crate::value::IndexType;
|
||||||
use crate::value::SyntaxType;
|
use crate::value::SyntaxType;
|
||||||
|
|
||||||
lazy_static!(
|
lazy_static!(
|
||||||
|
@ -10,6 +11,8 @@ pub static ref SCHEMA_ATTR_DISPLAYNAME: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_DISPLAYNAME,
|
uuid: UUID_SCHEMA_ATTR_DISPLAYNAME,
|
||||||
name: Attribute::DisplayName,
|
name: Attribute::DisplayName,
|
||||||
description: "The publicly visible display name of this person".to_string(),
|
description: "The publicly visible display name of this person".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::Utf8String,
|
syntax: SyntaxType::Utf8String,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -19,6 +22,8 @@ pub static ref SCHEMA_ATTR_DISPLAYNAME_DL7: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_DISPLAYNAME,
|
uuid: UUID_SCHEMA_ATTR_DISPLAYNAME,
|
||||||
name: Attribute::DisplayName,
|
name: Attribute::DisplayName,
|
||||||
description: "The publicly visible display name of this person".to_string(),
|
description: "The publicly visible display name of this person".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality, IndexType::SubString],
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::Utf8String,
|
syntax: SyntaxType::Utf8String,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -28,6 +33,8 @@ pub static ref SCHEMA_ATTR_MAIL: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_MAIL,
|
uuid: UUID_SCHEMA_ATTR_MAIL,
|
||||||
name: Attribute::Mail,
|
name: Attribute::Mail,
|
||||||
description: "Mail addresses of the object".to_string(),
|
description: "Mail addresses of the object".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
unique: true,
|
unique: true,
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
|
@ -39,6 +46,8 @@ pub static ref SCHEMA_ATTR_MAIL_DL7: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_MAIL,
|
uuid: UUID_SCHEMA_ATTR_MAIL,
|
||||||
name: Attribute::Mail,
|
name: Attribute::Mail,
|
||||||
description: "Mail addresses of the object".to_string(),
|
description: "Mail addresses of the object".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality, IndexType::SubString],
|
||||||
unique: true,
|
unique: true,
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
|
@ -50,6 +59,8 @@ pub static ref SCHEMA_ATTR_EC_KEY_PRIVATE: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_EC_KEY_PRIVATE,
|
uuid: UUID_SCHEMA_ATTR_EC_KEY_PRIVATE,
|
||||||
name: Attribute::IdVerificationEcKey,
|
name: Attribute::IdVerificationEcKey,
|
||||||
description: "Account verification private key".to_string(),
|
description: "Account verification private key".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Presence],
|
||||||
unique: false,
|
unique: false,
|
||||||
sync_allowed: false,
|
sync_allowed: false,
|
||||||
syntax: SyntaxType::EcKeyPrivate,
|
syntax: SyntaxType::EcKeyPrivate,
|
||||||
|
@ -71,6 +82,8 @@ pub static ref SCHEMA_ATTR_PRIMARY_CREDENTIAL: SchemaAttribute = SchemaAttribute
|
||||||
uuid: UUID_SCHEMA_ATTR_PRIMARY_CREDENTIAL,
|
uuid: UUID_SCHEMA_ATTR_PRIMARY_CREDENTIAL,
|
||||||
name: Attribute::PrimaryCredential,
|
name: Attribute::PrimaryCredential,
|
||||||
description: "Primary credential material of the account for authentication interactively".to_string(),
|
description: "Primary credential material of the account for authentication interactively".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Presence],
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::Credential,
|
syntax: SyntaxType::Credential,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -80,6 +93,8 @@ pub static ref SCHEMA_ATTR_LEGALNAME: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_LEGALNAME,
|
uuid: UUID_SCHEMA_ATTR_LEGALNAME,
|
||||||
name: Attribute::LegalName,
|
name: Attribute::LegalName,
|
||||||
description: "The private and sensitive legal name of this person".to_string(),
|
description: "The private and sensitive legal name of this person".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::Utf8String,
|
syntax: SyntaxType::Utf8String,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -89,6 +104,8 @@ pub static ref SCHEMA_ATTR_LEGALNAME_DL7: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_LEGALNAME,
|
uuid: UUID_SCHEMA_ATTR_LEGALNAME,
|
||||||
name: Attribute::LegalName,
|
name: Attribute::LegalName,
|
||||||
description: "The private and sensitive legal name of this person".to_string(),
|
description: "The private and sensitive legal name of this person".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality, IndexType::SubString],
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::Utf8String,
|
syntax: SyntaxType::Utf8String,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -98,6 +115,8 @@ pub static ref SCHEMA_ATTR_NAME_HISTORY: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_NAME_HISTORY,
|
uuid: UUID_SCHEMA_ATTR_NAME_HISTORY,
|
||||||
name: Attribute::NameHistory,
|
name: Attribute::NameHistory,
|
||||||
description: "The history of names that a person has had".to_string(),
|
description: "The history of names that a person has had".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::AuditLogString,
|
syntax: SyntaxType::AuditLogString,
|
||||||
|
@ -118,6 +137,8 @@ pub static ref SCHEMA_ATTR_DOMAIN_NAME: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_DOMAIN_NAME,
|
uuid: UUID_SCHEMA_ATTR_DOMAIN_NAME,
|
||||||
name: Attribute::DomainName,
|
name: Attribute::DomainName,
|
||||||
description: "The domain's DNS name for webauthn and SPN generation purposes".to_string(),
|
description: "The domain's DNS name for webauthn and SPN generation purposes".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality, IndexType::Presence],
|
||||||
unique: true,
|
unique: true,
|
||||||
syntax: SyntaxType::Utf8StringIname,
|
syntax: SyntaxType::Utf8StringIname,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -147,6 +168,8 @@ pub static ref SCHEMA_ATTR_DOMAIN_DISPLAY_NAME: SchemaAttribute = SchemaAttribut
|
||||||
uuid: UUID_SCHEMA_ATTR_DOMAIN_DISPLAY_NAME,
|
uuid: UUID_SCHEMA_ATTR_DOMAIN_DISPLAY_NAME,
|
||||||
name: Attribute::DomainDisplayName,
|
name: Attribute::DomainDisplayName,
|
||||||
description: "The user-facing display name of the Kanidm domain".to_string(),
|
description: "The user-facing display name of the Kanidm domain".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
syntax: SyntaxType::Utf8String,
|
syntax: SyntaxType::Utf8String,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -155,6 +178,8 @@ pub static ref SCHEMA_ATTR_DOMAIN_UUID: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_DOMAIN_UUID,
|
uuid: UUID_SCHEMA_ATTR_DOMAIN_UUID,
|
||||||
name: Attribute::DomainUuid,
|
name: Attribute::DomainUuid,
|
||||||
description: "The domain's uuid, used in CSN and trust relationships".to_string(),
|
description: "The domain's uuid, used in CSN and trust relationships".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
unique: true,
|
unique: true,
|
||||||
syntax: SyntaxType::Uuid,
|
syntax: SyntaxType::Uuid,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -164,6 +189,8 @@ pub static ref SCHEMA_ATTR_DOMAIN_SSID: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_DOMAIN_SSID,
|
uuid: UUID_SCHEMA_ATTR_DOMAIN_SSID,
|
||||||
name: Attribute::DomainSsid,
|
name: Attribute::DomainSsid,
|
||||||
description: "The domains site-wide SSID for device autoconfiguration of wireless".to_string(),
|
description: "The domains site-wide SSID for device autoconfiguration of wireless".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
unique: true,
|
unique: true,
|
||||||
syntax: SyntaxType::Utf8String,
|
syntax: SyntaxType::Utf8String,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -210,6 +237,8 @@ pub static ref SCHEMA_ATTR_GIDNUMBER: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_GIDNUMBER,
|
uuid: UUID_SCHEMA_ATTR_GIDNUMBER,
|
||||||
name: Attribute::GidNumber,
|
name: Attribute::GidNumber,
|
||||||
description: "The groupid (uid) number of a group or account.to_string(). This is the same value as the UID number on posix accounts for security reasons".to_string(),
|
description: "The groupid (uid) number of a group or account.to_string(). This is the same value as the UID number on posix accounts for security reasons".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
unique: true,
|
unique: true,
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::Uint32,
|
syntax: SyntaxType::Uint32,
|
||||||
|
@ -267,6 +296,8 @@ pub static ref SCHEMA_ATTR_UNIX_PASSWORD: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_UNIX_PASSWORD,
|
uuid: UUID_SCHEMA_ATTR_UNIX_PASSWORD,
|
||||||
name: Attribute::UnixPassword,
|
name: Attribute::UnixPassword,
|
||||||
description: "A POSIX user's UNIX login password".to_string(),
|
description: "A POSIX user's UNIX login password".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Presence],
|
||||||
syntax: SyntaxType::Credential,
|
syntax: SyntaxType::Credential,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -275,6 +306,8 @@ pub static ref SCHEMA_ATTR_NSUNIQUEID: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_NSUNIQUEID,
|
uuid: UUID_SCHEMA_ATTR_NSUNIQUEID,
|
||||||
name: Attribute::NsUniqueId,
|
name: Attribute::NsUniqueId,
|
||||||
description: "A unique id compatibility for 389-ds/dsee".to_string(),
|
description: "A unique id compatibility for 389-ds/dsee".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
unique: true,
|
unique: true,
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::NsUniqueId,
|
syntax: SyntaxType::NsUniqueId,
|
||||||
|
@ -315,6 +348,8 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_NAME: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_NAME,
|
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_NAME,
|
||||||
name: Attribute::OAuth2RsName,
|
name: Attribute::OAuth2RsName,
|
||||||
description: "The unique name of an external Oauth2 resource".to_string(),
|
description: "The unique name of an external Oauth2 resource".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
unique: true,
|
unique: true,
|
||||||
syntax: SyntaxType::Utf8StringIname,
|
syntax: SyntaxType::Utf8StringIname,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -362,6 +397,8 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_CLAIM_MAP_DL4: SchemaAttribute = SchemaAttr
|
||||||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_CLAIM_MAP,
|
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_CLAIM_MAP,
|
||||||
name: Attribute::OAuth2RsClaimMap,
|
name: Attribute::OAuth2RsClaimMap,
|
||||||
description: "A set of custom claims mapped to group memberships of accounts".to_string(),
|
description: "A set of custom claims mapped to group memberships of accounts".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
// CHANGE ME
|
// CHANGE ME
|
||||||
syntax: SyntaxType::OauthClaimMap,
|
syntax: SyntaxType::OauthClaimMap,
|
||||||
|
@ -372,6 +409,8 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_SCOPE_MAP: SchemaAttribute = SchemaAttribut
|
||||||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_SCOPE_MAP,
|
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_SCOPE_MAP,
|
||||||
name: Attribute::OAuth2RsScopeMap,
|
name: Attribute::OAuth2RsScopeMap,
|
||||||
description: "A reference to a group mapped to scopes for the associated oauth2 resource server".to_string(),
|
description: "A reference to a group mapped to scopes for the associated oauth2 resource server".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
syntax: SyntaxType::OauthScopeMap,
|
syntax: SyntaxType::OauthScopeMap,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -381,6 +420,8 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_SUP_SCOPE_MAP: SchemaAttribute = SchemaAttr
|
||||||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_SUP_SCOPE_MAP,
|
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_SUP_SCOPE_MAP,
|
||||||
name: Attribute::OAuth2RsSupScopeMap,
|
name: Attribute::OAuth2RsSupScopeMap,
|
||||||
description: "A reference to a group mapped to scopes for the associated oauth2 resource server".to_string(),
|
description: "A reference to a group mapped to scopes for the associated oauth2 resource server".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
syntax: SyntaxType::OauthScopeMap,
|
syntax: SyntaxType::OauthScopeMap,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -418,6 +459,8 @@ pub static ref SCHEMA_ATTR_OAUTH2_CONSENT_SCOPE_MAP: SchemaAttribute = SchemaAtt
|
||||||
uuid: UUID_SCHEMA_ATTR_OAUTH2_CONSENT_SCOPE_MAP,
|
uuid: UUID_SCHEMA_ATTR_OAUTH2_CONSENT_SCOPE_MAP,
|
||||||
name: Attribute::OAuth2ConsentScopeMap,
|
name: Attribute::OAuth2ConsentScopeMap,
|
||||||
description: "A set of scopes mapped from a relying server to a user, where the user has previously consented to the following. If changed or deleted, consent will be re-sought".to_string(),
|
description: "A set of scopes mapped from a relying server to a user, where the user has previously consented to the following. If changed or deleted, consent will be re-sought".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
syntax: SyntaxType::OauthScopeMap,
|
syntax: SyntaxType::OauthScopeMap,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -464,6 +507,8 @@ pub static ref SCHEMA_ATTR_JWS_ES256_PRIVATE_KEY: SchemaAttribute = SchemaAttrib
|
||||||
uuid: UUID_SCHEMA_ATTR_JWS_ES256_PRIVATE_KEY,
|
uuid: UUID_SCHEMA_ATTR_JWS_ES256_PRIVATE_KEY,
|
||||||
name: Attribute::JwsEs256PrivateKey,
|
name: Attribute::JwsEs256PrivateKey,
|
||||||
description: "An es256 private key for jws".to_string(),
|
description: "An es256 private key for jws".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
unique: true,
|
unique: true,
|
||||||
syntax: SyntaxType::JwsKeyEs256,
|
syntax: SyntaxType::JwsKeyEs256,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -501,6 +546,8 @@ pub static ref SCHEMA_ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN: SchemaAttribute = Sch
|
||||||
uuid: UUID_SCHEMA_ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN,
|
uuid: UUID_SCHEMA_ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN,
|
||||||
name: Attribute::CredentialUpdateIntentToken,
|
name: Attribute::CredentialUpdateIntentToken,
|
||||||
description: "The status of a credential update intent token".to_string(),
|
description: "The status of a credential update intent token".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
syntax: SyntaxType::IntentToken,
|
syntax: SyntaxType::IntentToken,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -510,6 +557,8 @@ pub static ref SCHEMA_ATTR_PASSKEYS: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_PASSKEYS,
|
uuid: UUID_SCHEMA_ATTR_PASSKEYS,
|
||||||
name: Attribute::PassKeys,
|
name: Attribute::PassKeys,
|
||||||
description: "A set of registered passkeys".to_string(),
|
description: "A set of registered passkeys".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::Passkey,
|
syntax: SyntaxType::Passkey,
|
||||||
|
@ -520,6 +569,8 @@ pub static ref SCHEMA_ATTR_ATTESTED_PASSKEYS: SchemaAttribute = SchemaAttribute
|
||||||
uuid: UUID_SCHEMA_ATTR_ATTESTED_PASSKEYS,
|
uuid: UUID_SCHEMA_ATTR_ATTESTED_PASSKEYS,
|
||||||
name: Attribute::AttestedPasskeys,
|
name: Attribute::AttestedPasskeys,
|
||||||
description: "A set of registered device keys".to_string(),
|
description: "A set of registered device keys".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
sync_allowed: true,
|
sync_allowed: true,
|
||||||
syntax: SyntaxType::AttestedPasskey,
|
syntax: SyntaxType::AttestedPasskey,
|
||||||
|
@ -548,6 +599,8 @@ pub static ref SCHEMA_ATTR_API_TOKEN_SESSION: SchemaAttribute = SchemaAttribute
|
||||||
uuid: UUID_SCHEMA_ATTR_API_TOKEN_SESSION,
|
uuid: UUID_SCHEMA_ATTR_API_TOKEN_SESSION,
|
||||||
name: Attribute::ApiTokenSession,
|
name: Attribute::ApiTokenSession,
|
||||||
description: "A session entry related to an issued API token".to_string(),
|
description: "A session entry related to an issued API token".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
unique: true,
|
unique: true,
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
syntax: SyntaxType::ApiToken,
|
syntax: SyntaxType::ApiToken,
|
||||||
|
@ -558,6 +611,8 @@ pub static ref SCHEMA_ATTR_USER_AUTH_TOKEN_SESSION: SchemaAttribute = SchemaAttr
|
||||||
uuid: UUID_SCHEMA_ATTR_USER_AUTH_TOKEN_SESSION,
|
uuid: UUID_SCHEMA_ATTR_USER_AUTH_TOKEN_SESSION,
|
||||||
name: Attribute::UserAuthTokenSession,
|
name: Attribute::UserAuthTokenSession,
|
||||||
description: "A session entry related to an issued user auth token".to_string(),
|
description: "A session entry related to an issued user auth token".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
unique: true,
|
unique: true,
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
syntax: SyntaxType::Session,
|
syntax: SyntaxType::Session,
|
||||||
|
@ -568,6 +623,8 @@ pub static ref SCHEMA_ATTR_OAUTH2_SESSION: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_OAUTH2_SESSION,
|
uuid: UUID_SCHEMA_ATTR_OAUTH2_SESSION,
|
||||||
name: Attribute::OAuth2Session,
|
name: Attribute::OAuth2Session,
|
||||||
description: "A session entry to an active oauth2 session, bound to a parent user auth token".to_string(),
|
description: "A session entry to an active oauth2 session, bound to a parent user auth token".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
syntax: SyntaxType::Oauth2Session,
|
syntax: SyntaxType::Oauth2Session,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -577,6 +634,8 @@ pub static ref SCHEMA_ATTR_SYNC_TOKEN_SESSION: SchemaAttribute = SchemaAttribute
|
||||||
uuid: UUID_SCHEMA_ATTR_SYNC_TOKEN_SESSION,
|
uuid: UUID_SCHEMA_ATTR_SYNC_TOKEN_SESSION,
|
||||||
name: Attribute::SyncTokenSession,
|
name: Attribute::SyncTokenSession,
|
||||||
description: "A session entry related to an issued sync token".to_string(),
|
description: "A session entry related to an issued sync token".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
unique: true,
|
unique: true,
|
||||||
syntax: SyntaxType::ApiToken,
|
syntax: SyntaxType::ApiToken,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -595,6 +654,8 @@ pub static ref SCHEMA_ATTR_GRANT_UI_HINT: SchemaAttribute = SchemaAttribute {
|
||||||
uuid: UUID_SCHEMA_ATTR_GRANT_UI_HINT,
|
uuid: UUID_SCHEMA_ATTR_GRANT_UI_HINT,
|
||||||
name: Attribute::GrantUiHint,
|
name: Attribute::GrantUiHint,
|
||||||
description: "A UI hint that is granted via membership to a group".to_string(),
|
description: "A UI hint that is granted via membership to a group".to_string(),
|
||||||
|
|
||||||
|
index: vec![IndexType::Equality],
|
||||||
multivalue: true,
|
multivalue: true,
|
||||||
syntax: SyntaxType::UiHint,
|
syntax: SyntaxType::UiHint,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
|
@ -695,6 +695,7 @@ mod tests {
|
||||||
|
|
||||||
let e = entry_init!(
|
let e = entry_init!(
|
||||||
(Attribute::Class, EntryClass::Person.to_value()),
|
(Attribute::Class, EntryClass::Person.to_value()),
|
||||||
|
(Attribute::Class, EntryClass::System.to_value()),
|
||||||
(Attribute::Name, Value::new_iname("testperson")),
|
(Attribute::Name, Value::new_iname("testperson")),
|
||||||
(Attribute::DisplayName, Value::new_iname("testperson")),
|
(Attribute::DisplayName, Value::new_iname("testperson")),
|
||||||
(
|
(
|
||||||
|
@ -725,6 +726,7 @@ mod tests {
|
||||||
|
|
||||||
let e = entry_init!(
|
let e = entry_init!(
|
||||||
(Attribute::Class, EntryClass::Person.to_value()),
|
(Attribute::Class, EntryClass::Person.to_value()),
|
||||||
|
(Attribute::Class, EntryClass::System.to_value()),
|
||||||
(Attribute::Name, Value::new_iname("testperson")),
|
(Attribute::Name, Value::new_iname("testperson")),
|
||||||
(Attribute::DisplayName, Value::new_iname("testperson")),
|
(Attribute::DisplayName, Value::new_iname("testperson")),
|
||||||
(
|
(
|
||||||
|
|
|
@ -109,7 +109,7 @@ impl DynGroup {
|
||||||
nd_group.purge_ava(Attribute::DynMember);
|
nd_group.purge_ava(Attribute::DynMember);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert it to the dyngroup cache with the parsed filter for
|
// Insert it to the dyngroup cache with the compiled/resolved filter for
|
||||||
// fast matching in other paths.
|
// fast matching in other paths.
|
||||||
if dyn_groups.insts.insert(uuid, scope_i).is_none() == expect {
|
if dyn_groups.insts.insert(uuid, scope_i).is_none() == expect {
|
||||||
error!("{} cache uuid conflict {}", Attribute::DynGroup, uuid);
|
error!("{} cache uuid conflict {}", Attribute::DynGroup, uuid);
|
||||||
|
@ -175,11 +175,6 @@ impl DynGroup {
|
||||||
) -> Result<BTreeSet<Uuid>, OperationError> {
|
) -> Result<BTreeSet<Uuid>, OperationError> {
|
||||||
let mut affected_uuids = BTreeSet::new();
|
let mut affected_uuids = BTreeSet::new();
|
||||||
|
|
||||||
if qs.get_phase() < ServerPhase::SchemaReady {
|
|
||||||
debug!("Server is not ready to apply dyngroups");
|
|
||||||
return Ok(affected_uuids);
|
|
||||||
}
|
|
||||||
|
|
||||||
let ident_internal = Identity::from_internal();
|
let ident_internal = Identity::from_internal();
|
||||||
|
|
||||||
let (n_dyn_groups, entries): (Vec<&Entry<_, _>>, Vec<_>) = cand.iter().partition(|entry| {
|
let (n_dyn_groups, entries): (Vec<&Entry<_, _>>, Vec<_>) = cand.iter().partition(|entry| {
|
||||||
|
@ -207,7 +202,9 @@ impl DynGroup {
|
||||||
let dg_filter_valid = dg_filter
|
let dg_filter_valid = dg_filter
|
||||||
.validate(qs.get_schema())
|
.validate(qs.get_schema())
|
||||||
.map_err(OperationError::SchemaViolation)
|
.map_err(OperationError::SchemaViolation)
|
||||||
.and_then(|f| f.resolve(&ident_internal, None, qs.get_resolve_filter_cache()))?;
|
.and_then(|f| {
|
||||||
|
f.resolve(&ident_internal, None, Some(qs.get_resolve_filter_cache()))
|
||||||
|
})?;
|
||||||
|
|
||||||
// Did any of our modified entries match our dyn group filter?
|
// Did any of our modified entries match our dyn group filter?
|
||||||
let matches: Vec<_> = entries
|
let matches: Vec<_> = entries
|
||||||
|
@ -294,11 +291,6 @@ impl DynGroup {
|
||||||
) -> Result<BTreeSet<Uuid>, OperationError> {
|
) -> Result<BTreeSet<Uuid>, OperationError> {
|
||||||
let mut affected_uuids = BTreeSet::new();
|
let mut affected_uuids = BTreeSet::new();
|
||||||
|
|
||||||
if qs.get_phase() < ServerPhase::SchemaReady {
|
|
||||||
debug!("Server is not ready to apply dyngroups");
|
|
||||||
return Ok(affected_uuids);
|
|
||||||
}
|
|
||||||
|
|
||||||
let ident_internal = Identity::from_internal();
|
let ident_internal = Identity::from_internal();
|
||||||
|
|
||||||
// Probably should be filter here instead.
|
// Probably should be filter here instead.
|
||||||
|
@ -346,7 +338,9 @@ impl DynGroup {
|
||||||
let dg_filter_valid = dg_filter
|
let dg_filter_valid = dg_filter
|
||||||
.validate(qs.get_schema())
|
.validate(qs.get_schema())
|
||||||
.map_err(OperationError::SchemaViolation)
|
.map_err(OperationError::SchemaViolation)
|
||||||
.and_then(|f| f.resolve(&ident_internal, None, qs.get_resolve_filter_cache()))?;
|
.and_then(|f| {
|
||||||
|
f.resolve(&ident_internal, None, Some(qs.get_resolve_filter_cache()))
|
||||||
|
})?;
|
||||||
|
|
||||||
let matches: Vec<_> = pre_entries
|
let matches: Vec<_> = pre_entries
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -22,6 +22,7 @@ mod jwskeygen;
|
||||||
mod keyobject;
|
mod keyobject;
|
||||||
mod memberof;
|
mod memberof;
|
||||||
mod namehistory;
|
mod namehistory;
|
||||||
|
mod protected;
|
||||||
mod refint;
|
mod refint;
|
||||||
mod session;
|
mod session;
|
||||||
mod spn;
|
mod spn;
|
||||||
|
@ -43,7 +44,6 @@ trait Plugin {
|
||||||
Err(OperationError::InvalidState)
|
Err(OperationError::InvalidState)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn pre_create(
|
fn pre_create(
|
||||||
_qs: &mut QueryServerWriteTransaction,
|
_qs: &mut QueryServerWriteTransaction,
|
||||||
// List of what we will commit that is valid?
|
// List of what we will commit that is valid?
|
||||||
|
@ -243,13 +243,13 @@ impl Plugins {
|
||||||
attrunique::AttrUnique::pre_create_transform(qs, cand, ce)
|
attrunique::AttrUnique::pre_create_transform(qs, cand, ce)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "trace", name = "plugins::run_pre_create", skip_all)]
|
#[instrument(level = "debug", name = "plugins::run_pre_create", skip_all)]
|
||||||
pub fn run_pre_create(
|
pub fn run_pre_create(
|
||||||
_qs: &mut QueryServerWriteTransaction,
|
qs: &mut QueryServerWriteTransaction,
|
||||||
_cand: &[Entry<EntrySealed, EntryNew>],
|
cand: &[Entry<EntrySealed, EntryNew>],
|
||||||
_ce: &CreateEvent,
|
ce: &CreateEvent,
|
||||||
) -> Result<(), OperationError> {
|
) -> Result<(), OperationError> {
|
||||||
Ok(())
|
protected::Protected::pre_create(qs, cand, ce)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "debug", name = "plugins::run_post_create", skip_all)]
|
#[instrument(level = "debug", name = "plugins::run_post_create", skip_all)]
|
||||||
|
@ -269,6 +269,7 @@ impl Plugins {
|
||||||
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
|
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
|
||||||
me: &ModifyEvent,
|
me: &ModifyEvent,
|
||||||
) -> Result<(), OperationError> {
|
) -> Result<(), OperationError> {
|
||||||
|
protected::Protected::pre_modify(qs, pre_cand, cand, me)?;
|
||||||
base::Base::pre_modify(qs, pre_cand, cand, me)?;
|
base::Base::pre_modify(qs, pre_cand, cand, me)?;
|
||||||
valuedeny::ValueDeny::pre_modify(qs, pre_cand, cand, me)?;
|
valuedeny::ValueDeny::pre_modify(qs, pre_cand, cand, me)?;
|
||||||
cred_import::CredImport::pre_modify(qs, pre_cand, cand, me)?;
|
cred_import::CredImport::pre_modify(qs, pre_cand, cand, me)?;
|
||||||
|
@ -304,6 +305,7 @@ impl Plugins {
|
||||||
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
|
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
|
||||||
me: &BatchModifyEvent,
|
me: &BatchModifyEvent,
|
||||||
) -> Result<(), OperationError> {
|
) -> Result<(), OperationError> {
|
||||||
|
protected::Protected::pre_batch_modify(qs, pre_cand, cand, me)?;
|
||||||
base::Base::pre_batch_modify(qs, pre_cand, cand, me)?;
|
base::Base::pre_batch_modify(qs, pre_cand, cand, me)?;
|
||||||
valuedeny::ValueDeny::pre_batch_modify(qs, pre_cand, cand, me)?;
|
valuedeny::ValueDeny::pre_batch_modify(qs, pre_cand, cand, me)?;
|
||||||
cred_import::CredImport::pre_batch_modify(qs, pre_cand, cand, me)?;
|
cred_import::CredImport::pre_batch_modify(qs, pre_cand, cand, me)?;
|
||||||
|
@ -338,6 +340,7 @@ impl Plugins {
|
||||||
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
|
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
|
||||||
de: &DeleteEvent,
|
de: &DeleteEvent,
|
||||||
) -> Result<(), OperationError> {
|
) -> Result<(), OperationError> {
|
||||||
|
protected::Protected::pre_delete(qs, cand, de)?;
|
||||||
memberof::MemberOf::pre_delete(qs, cand, de)
|
memberof::MemberOf::pre_delete(qs, cand, de)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
690
server/lib/src/plugins/protected.rs
Normal file
690
server/lib/src/plugins/protected.rs
Normal file
|
@ -0,0 +1,690 @@
|
||||||
|
// System protected objects. Items matching specific requirements
|
||||||
|
// may only have certain modifications performed.
|
||||||
|
|
||||||
|
use hashbrown::HashSet;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::event::{CreateEvent, DeleteEvent, ModifyEvent};
|
||||||
|
use crate::modify::Modify;
|
||||||
|
use crate::plugins::Plugin;
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
pub struct Protected {}
|
||||||
|
|
||||||
|
// Here is the declaration of all the attrs that can be altered by
|
||||||
|
// a call on a system object. We trust they are allowed because
|
||||||
|
// schema will have checked this, and we don't allow class changes!
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref ALLOWED_ATTRS: HashSet<Attribute> = {
|
||||||
|
let attrs = vec![
|
||||||
|
// Allow modification of some schema class types to allow local extension
|
||||||
|
// of schema types.
|
||||||
|
Attribute::Must,
|
||||||
|
Attribute::May,
|
||||||
|
// modification of some domain info types for local configuratiomn.
|
||||||
|
Attribute::DomainSsid,
|
||||||
|
Attribute::DomainLdapBasedn,
|
||||||
|
Attribute::LdapMaxQueryableAttrs,
|
||||||
|
Attribute::LdapAllowUnixPwBind,
|
||||||
|
Attribute::FernetPrivateKeyStr,
|
||||||
|
Attribute::Es256PrivateKeyDer,
|
||||||
|
Attribute::KeyActionRevoke,
|
||||||
|
Attribute::KeyActionRotate,
|
||||||
|
Attribute::IdVerificationEcKey,
|
||||||
|
Attribute::BadlistPassword,
|
||||||
|
Attribute::DeniedName,
|
||||||
|
Attribute::DomainDisplayName,
|
||||||
|
Attribute::Image,
|
||||||
|
// modification of account policy values for dyngroup.
|
||||||
|
Attribute::AuthSessionExpiry,
|
||||||
|
Attribute::AuthPasswordMinimumLength,
|
||||||
|
Attribute::CredentialTypeMinimum,
|
||||||
|
Attribute::PrivilegeExpiry,
|
||||||
|
Attribute::WebauthnAttestationCaList,
|
||||||
|
Attribute::LimitSearchMaxResults,
|
||||||
|
Attribute::LimitSearchMaxFilterTest,
|
||||||
|
Attribute::AllowPrimaryCredFallback,
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut m = HashSet::with_capacity(attrs.len());
|
||||||
|
m.extend(attrs);
|
||||||
|
|
||||||
|
m
|
||||||
|
};
|
||||||
|
|
||||||
|
static ref PROTECTED_ENTRYCLASSES: Vec<EntryClass> =
|
||||||
|
vec![
|
||||||
|
EntryClass::System,
|
||||||
|
EntryClass::DomainInfo,
|
||||||
|
EntryClass::SystemInfo,
|
||||||
|
EntryClass::SystemConfig,
|
||||||
|
EntryClass::DynGroup,
|
||||||
|
EntryClass::SyncObject,
|
||||||
|
EntryClass::Tombstone,
|
||||||
|
EntryClass::Recycled,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Plugin for Protected {
|
||||||
|
fn id() -> &'static str {
|
||||||
|
"plugin_protected"
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "debug", name = "protected_pre_create", skip_all)]
|
||||||
|
fn pre_create(
|
||||||
|
_qs: &mut QueryServerWriteTransaction,
|
||||||
|
// List of what we will commit that is valid?
|
||||||
|
cand: &[Entry<EntrySealed, EntryNew>],
|
||||||
|
ce: &CreateEvent,
|
||||||
|
) -> Result<(), OperationError> {
|
||||||
|
if ce.ident.is_internal() {
|
||||||
|
trace!("Internal operation, not enforcing system object protection");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
cand.iter().try_fold((), |(), cand| {
|
||||||
|
if PROTECTED_ENTRYCLASSES
|
||||||
|
.iter()
|
||||||
|
.any(|c| cand.attribute_equality(Attribute::Class, &c.to_partialvalue()))
|
||||||
|
{
|
||||||
|
trace!("Rejecting operation during pre_create check");
|
||||||
|
Err(OperationError::SystemProtectedObject)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "debug", name = "protected_pre_modify", skip_all)]
|
||||||
|
fn pre_modify(
|
||||||
|
_qs: &mut QueryServerWriteTransaction,
|
||||||
|
_pre_cand: &[Arc<EntrySealedCommitted>],
|
||||||
|
cand: &mut Vec<EntryInvalidCommitted>,
|
||||||
|
me: &ModifyEvent,
|
||||||
|
) -> Result<(), OperationError> {
|
||||||
|
if me.ident.is_internal() {
|
||||||
|
trace!("Internal operation, not enforcing system object protection");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
// Prevent adding class: system, domain_info, tombstone, or recycled.
|
||||||
|
me.modlist.iter().try_fold((), |(), m| match m {
|
||||||
|
Modify::Present(a, v) => {
|
||||||
|
if a == Attribute::Class.as_ref()
|
||||||
|
&& PROTECTED_ENTRYCLASSES.iter().any(|c| v == &c.to_value())
|
||||||
|
{
|
||||||
|
trace!("Rejecting operation during pre_modify check");
|
||||||
|
Err(OperationError::SystemProtectedObject)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Ok(()),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// HARD block mods on tombstone or recycle. We soft block on the rest as they may
|
||||||
|
// have some allowed attrs.
|
||||||
|
cand.iter().try_fold((), |(), cand| {
|
||||||
|
if cand.attribute_equality(Attribute::Class, &EntryClass::Tombstone.into())
|
||||||
|
|| cand.attribute_equality(Attribute::Class, &EntryClass::Recycled.into())
|
||||||
|
{
|
||||||
|
Err(OperationError::SystemProtectedObject)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// if class: system, check the mods are "allowed"
|
||||||
|
let system_pres = cand.iter().any(|c| {
|
||||||
|
// We don't need to check for domain info here because domain_info has a class
|
||||||
|
// system also. We just need to block it from being created.
|
||||||
|
c.attribute_equality(Attribute::Class, &EntryClass::System.into())
|
||||||
|
});
|
||||||
|
|
||||||
|
trace!("class: system -> {}", system_pres);
|
||||||
|
// No system types being altered, return.
|
||||||
|
if !system_pres {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Something altered is system, check if it's allowed.
|
||||||
|
me.modlist.into_iter().try_fold((), |(), m| {
|
||||||
|
// Already hit an error, move on.
|
||||||
|
let a = match m {
|
||||||
|
Modify::Present(a, _)
|
||||||
|
| Modify::Removed(a, _)
|
||||||
|
| Modify::Set(a, _)
|
||||||
|
| Modify::Purged(a) => Some(a),
|
||||||
|
Modify::Assert(_, _) => None,
|
||||||
|
};
|
||||||
|
if let Some(attr) = a {
|
||||||
|
match ALLOWED_ATTRS.contains(attr) {
|
||||||
|
true => Ok(()),
|
||||||
|
false => {
|
||||||
|
trace!("If you're getting this, you need to modify the ALLOWED_ATTRS list");
|
||||||
|
Err(OperationError::SystemProtectedObject)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Was not a mod needing checking
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "debug", name = "protected_pre_batch_modify", skip_all)]
|
||||||
|
fn pre_batch_modify(
|
||||||
|
_qs: &mut QueryServerWriteTransaction,
|
||||||
|
_pre_cand: &[Arc<EntrySealedCommitted>],
|
||||||
|
cand: &mut Vec<EntryInvalidCommitted>,
|
||||||
|
me: &BatchModifyEvent,
|
||||||
|
) -> Result<(), OperationError> {
|
||||||
|
if me.ident.is_internal() {
|
||||||
|
trace!("Internal operation, not enforcing system object protection");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
me.modset
|
||||||
|
.values()
|
||||||
|
.flat_map(|ml| ml.iter())
|
||||||
|
.try_fold((), |(), m| match m {
|
||||||
|
Modify::Present(a, v) => {
|
||||||
|
if a == Attribute::Class.as_ref()
|
||||||
|
&& PROTECTED_ENTRYCLASSES.iter().any(|c| v == &c.to_value())
|
||||||
|
{
|
||||||
|
trace!("Rejecting operation during pre_batch_modify check");
|
||||||
|
Err(OperationError::SystemProtectedObject)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Ok(()),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// HARD block mods on tombstone or recycle. We soft block on the rest as they may
|
||||||
|
// have some allowed attrs.
|
||||||
|
cand.iter().try_fold((), |(), cand| {
|
||||||
|
if cand.attribute_equality(Attribute::Class, &EntryClass::Tombstone.into())
|
||||||
|
|| cand.attribute_equality(Attribute::Class, &EntryClass::Recycled.into())
|
||||||
|
{
|
||||||
|
Err(OperationError::SystemProtectedObject)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// if class: system, check the mods are "allowed"
|
||||||
|
let system_pres = cand.iter().any(|c| {
|
||||||
|
// We don't need to check for domain info here because domain_info has a class
|
||||||
|
// system also. We just need to block it from being created.
|
||||||
|
c.attribute_equality(Attribute::Class, &EntryClass::System.into())
|
||||||
|
});
|
||||||
|
|
||||||
|
trace!("{}: system -> {}", Attribute::Class, system_pres);
|
||||||
|
// No system types being altered, return.
|
||||||
|
if !system_pres {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Something altered is system, check if it's allowed.
|
||||||
|
me.modset
|
||||||
|
.values()
|
||||||
|
.flat_map(|ml| ml.iter())
|
||||||
|
.try_fold((), |(), m| {
|
||||||
|
// Already hit an error, move on.
|
||||||
|
let a = match m {
|
||||||
|
Modify::Present(a, _) | Modify::Removed(a, _) | Modify::Set(a, _) | Modify::Purged(a) => Some(a),
|
||||||
|
Modify::Assert(_, _) => None,
|
||||||
|
};
|
||||||
|
if let Some(attr) = a {
|
||||||
|
match ALLOWED_ATTRS.contains(attr) {
|
||||||
|
true => Ok(()),
|
||||||
|
false => {
|
||||||
|
|
||||||
|
trace!("Rejecting operation during pre_batch_modify check, if you're getting this check ALLOWED_ATTRS");
|
||||||
|
Err(OperationError::SystemProtectedObject)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Was not a mod needing checking
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "debug", name = "protected_pre_delete", skip_all)]
|
||||||
|
fn pre_delete(
|
||||||
|
_qs: &mut QueryServerWriteTransaction,
|
||||||
|
// Should these be EntrySealed
|
||||||
|
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
|
||||||
|
de: &DeleteEvent,
|
||||||
|
) -> Result<(), OperationError> {
|
||||||
|
if de.ident.is_internal() {
|
||||||
|
trace!("Internal operation, not enforcing system object protection");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
cand.iter().try_fold((), |(), cand| {
|
||||||
|
if PROTECTED_ENTRYCLASSES
|
||||||
|
.iter()
|
||||||
|
.any(|c| cand.attribute_equality(Attribute::Class, &c.to_partialvalue()))
|
||||||
|
{
|
||||||
|
trace!("Rejecting operation during pre_delete check");
|
||||||
|
Err(OperationError::SystemProtectedObject)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::prelude::*;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
const UUID_TEST_ACCOUNT: Uuid = uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
|
||||||
|
const UUID_TEST_GROUP: Uuid = uuid::uuid!("81ec1640-3637-4a2f-8a52-874fa3c3c92f");
|
||||||
|
const UUID_TEST_ACP: Uuid = uuid::uuid!("acae81d6-5ea7-4bd8-8f7f-fcec4c0dd647");
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref TEST_ACCOUNT: EntryInitNew = entry_init!(
|
||||||
|
(Attribute::Class, EntryClass::Account.to_value()),
|
||||||
|
(Attribute::Class, EntryClass::ServiceAccount.to_value()),
|
||||||
|
(Attribute::Class, EntryClass::MemberOf.to_value()),
|
||||||
|
(Attribute::Name, Value::new_iname("test_account_1")),
|
||||||
|
(Attribute::DisplayName, Value::new_utf8s("test_account_1")),
|
||||||
|
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT)),
|
||||||
|
(Attribute::MemberOf, Value::Refer(UUID_TEST_GROUP))
|
||||||
|
);
|
||||||
|
pub static ref TEST_GROUP: EntryInitNew = entry_init!(
|
||||||
|
(Attribute::Class, EntryClass::Group.to_value()),
|
||||||
|
(Attribute::Name, Value::new_iname("test_group_a")),
|
||||||
|
(Attribute::Uuid, Value::Uuid(UUID_TEST_GROUP)),
|
||||||
|
(Attribute::Member, Value::Refer(UUID_TEST_ACCOUNT))
|
||||||
|
);
|
||||||
|
pub static ref ALLOW_ALL: EntryInitNew = entry_init!(
|
||||||
|
(Attribute::Class, EntryClass::Object.to_value()),
|
||||||
|
(
|
||||||
|
Attribute::Class,
|
||||||
|
EntryClass::AccessControlProfile.to_value()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Attribute::Class,
|
||||||
|
EntryClass::AccessControlTargetScope.to_value()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Attribute::Class,
|
||||||
|
EntryClass::AccessControlReceiverGroup.to_value()
|
||||||
|
),
|
||||||
|
(Attribute::Class, EntryClass::AccessControlModify.to_value()),
|
||||||
|
(Attribute::Class, EntryClass::AccessControlCreate.to_value()),
|
||||||
|
(Attribute::Class, EntryClass::AccessControlDelete.to_value()),
|
||||||
|
(Attribute::Class, EntryClass::AccessControlSearch.to_value()),
|
||||||
|
(
|
||||||
|
Attribute::Name,
|
||||||
|
Value::new_iname("idm_admins_acp_allow_all_test")
|
||||||
|
),
|
||||||
|
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACP)),
|
||||||
|
(Attribute::AcpReceiverGroup, Value::Refer(UUID_TEST_GROUP)),
|
||||||
|
(
|
||||||
|
Attribute::AcpTargetScope,
|
||||||
|
Value::new_json_filter_s("{\"pres\":\"class\"}").expect("filter")
|
||||||
|
),
|
||||||
|
(Attribute::AcpSearchAttr, Value::from(Attribute::Name)),
|
||||||
|
(Attribute::AcpSearchAttr, Value::from(Attribute::Class)),
|
||||||
|
(Attribute::AcpSearchAttr, Value::from(Attribute::Uuid)),
|
||||||
|
(Attribute::AcpSearchAttr, Value::new_iutf8("classname")),
|
||||||
|
(
|
||||||
|
Attribute::AcpSearchAttr,
|
||||||
|
Value::new_iutf8(Attribute::AttributeName.as_ref())
|
||||||
|
),
|
||||||
|
(Attribute::AcpModifyClass, EntryClass::System.to_value()),
|
||||||
|
(Attribute::AcpModifyClass, Value::new_iutf8("domain_info")),
|
||||||
|
(
|
||||||
|
Attribute::AcpModifyRemovedAttr,
|
||||||
|
Value::from(Attribute::Class)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Attribute::AcpModifyRemovedAttr,
|
||||||
|
Value::from(Attribute::DisplayName)
|
||||||
|
),
|
||||||
|
(Attribute::AcpModifyRemovedAttr, Value::from(Attribute::May)),
|
||||||
|
(
|
||||||
|
Attribute::AcpModifyRemovedAttr,
|
||||||
|
Value::from(Attribute::Must)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Attribute::AcpModifyRemovedAttr,
|
||||||
|
Value::from(Attribute::DomainName)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Attribute::AcpModifyRemovedAttr,
|
||||||
|
Value::from(Attribute::DomainDisplayName)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Attribute::AcpModifyRemovedAttr,
|
||||||
|
Value::from(Attribute::DomainUuid)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Attribute::AcpModifyRemovedAttr,
|
||||||
|
Value::from(Attribute::DomainSsid)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Attribute::AcpModifyRemovedAttr,
|
||||||
|
Value::from(Attribute::FernetPrivateKeyStr)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Attribute::AcpModifyRemovedAttr,
|
||||||
|
Value::from(Attribute::Es256PrivateKeyDer)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Attribute::AcpModifyRemovedAttr,
|
||||||
|
Value::from(Attribute::PrivateCookieKey)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Attribute::AcpModifyPresentAttr,
|
||||||
|
Value::from(Attribute::Class)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Attribute::AcpModifyPresentAttr,
|
||||||
|
Value::from(Attribute::DisplayName)
|
||||||
|
),
|
||||||
|
(Attribute::AcpModifyPresentAttr, Value::from(Attribute::May)),
|
||||||
|
(
|
||||||
|
Attribute::AcpModifyPresentAttr,
|
||||||
|
Value::from(Attribute::Must)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Attribute::AcpModifyPresentAttr,
|
||||||
|
Value::from(Attribute::DomainName)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Attribute::AcpModifyPresentAttr,
|
||||||
|
Value::from(Attribute::DomainDisplayName)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Attribute::AcpModifyPresentAttr,
|
||||||
|
Value::from(Attribute::DomainUuid)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Attribute::AcpModifyPresentAttr,
|
||||||
|
Value::from(Attribute::DomainSsid)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Attribute::AcpModifyPresentAttr,
|
||||||
|
Value::from(Attribute::FernetPrivateKeyStr)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Attribute::AcpModifyPresentAttr,
|
||||||
|
Value::from(Attribute::Es256PrivateKeyDer)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Attribute::AcpModifyPresentAttr,
|
||||||
|
Value::from(Attribute::PrivateCookieKey)
|
||||||
|
),
|
||||||
|
(Attribute::AcpCreateClass, EntryClass::Object.to_value()),
|
||||||
|
(Attribute::AcpCreateClass, EntryClass::Account.to_value()),
|
||||||
|
(Attribute::AcpCreateClass, EntryClass::Person.to_value()),
|
||||||
|
(Attribute::AcpCreateClass, EntryClass::System.to_value()),
|
||||||
|
(Attribute::AcpCreateClass, EntryClass::DomainInfo.to_value()),
|
||||||
|
(Attribute::AcpCreateAttr, Value::from(Attribute::Name)),
|
||||||
|
(Attribute::AcpCreateAttr, EntryClass::Class.to_value(),),
|
||||||
|
(
|
||||||
|
Attribute::AcpCreateAttr,
|
||||||
|
Value::from(Attribute::Description),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Attribute::AcpCreateAttr,
|
||||||
|
Value::from(Attribute::DisplayName),
|
||||||
|
),
|
||||||
|
(Attribute::AcpCreateAttr, Value::from(Attribute::DomainName),),
|
||||||
|
(
|
||||||
|
Attribute::AcpCreateAttr,
|
||||||
|
Value::from(Attribute::DomainDisplayName)
|
||||||
|
),
|
||||||
|
(Attribute::AcpCreateAttr, Value::from(Attribute::DomainUuid)),
|
||||||
|
(Attribute::AcpCreateAttr, Value::from(Attribute::DomainSsid)),
|
||||||
|
(Attribute::AcpCreateAttr, Value::from(Attribute::Uuid)),
|
||||||
|
(
|
||||||
|
Attribute::AcpCreateAttr,
|
||||||
|
Value::from(Attribute::FernetPrivateKeyStr)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Attribute::AcpCreateAttr,
|
||||||
|
Value::from(Attribute::Es256PrivateKeyDer)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Attribute::AcpCreateAttr,
|
||||||
|
Value::from(Attribute::PrivateCookieKey)
|
||||||
|
),
|
||||||
|
(Attribute::AcpCreateAttr, Value::from(Attribute::Version))
|
||||||
|
);
|
||||||
|
pub static ref PRELOAD: Vec<EntryInitNew> =
|
||||||
|
vec![TEST_ACCOUNT.clone(), TEST_GROUP.clone(), ALLOW_ALL.clone()];
|
||||||
|
pub static ref E_TEST_ACCOUNT: Arc<EntrySealedCommitted> =
|
||||||
|
Arc::new(TEST_ACCOUNT.clone().into_sealed_committed());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pre_create_deny() {
|
||||||
|
// Test creating with class: system is rejected.
|
||||||
|
let e = entry_init!(
|
||||||
|
(Attribute::Class, EntryClass::Account.to_value()),
|
||||||
|
(Attribute::Class, EntryClass::Person.to_value()),
|
||||||
|
(Attribute::Class, EntryClass::System.to_value()),
|
||||||
|
(Attribute::Name, Value::new_iname("testperson")),
|
||||||
|
(
|
||||||
|
Attribute::DisplayName,
|
||||||
|
Value::Utf8("testperson".to_string())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
let create = vec![e];
|
||||||
|
let preload = PRELOAD.clone();
|
||||||
|
|
||||||
|
run_create_test!(
|
||||||
|
Err(OperationError::SystemProtectedObject),
|
||||||
|
preload,
|
||||||
|
create,
|
||||||
|
Some(E_TEST_ACCOUNT.clone()),
|
||||||
|
|_| {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pre_modify_system_deny() {
|
||||||
|
// Test modify of class to a system is denied
|
||||||
|
let e = entry_init!(
|
||||||
|
(Attribute::Class, EntryClass::Account.to_value()),
|
||||||
|
(Attribute::Class, EntryClass::Person.to_value()),
|
||||||
|
(Attribute::Class, EntryClass::System.to_value()),
|
||||||
|
(Attribute::Name, Value::new_iname("testperson")),
|
||||||
|
(
|
||||||
|
Attribute::DisplayName,
|
||||||
|
Value::Utf8("testperson".to_string())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut preload = PRELOAD.clone();
|
||||||
|
preload.push(e);
|
||||||
|
|
||||||
|
run_modify_test!(
|
||||||
|
Err(OperationError::SystemProtectedObject),
|
||||||
|
preload,
|
||||||
|
filter!(f_eq(Attribute::Name, PartialValue::new_iname("testperson"))),
|
||||||
|
modlist!([
|
||||||
|
m_purge(Attribute::DisplayName),
|
||||||
|
m_pres(Attribute::DisplayName, &Value::new_utf8s("system test")),
|
||||||
|
]),
|
||||||
|
Some(E_TEST_ACCOUNT.clone()),
|
||||||
|
|_| {},
|
||||||
|
|_| {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pre_modify_class_add_deny() {
|
||||||
|
// Show that adding a system class is denied
|
||||||
|
// TODO: replace this with a `SchemaClass` object
|
||||||
|
let e = entry_init!(
|
||||||
|
(Attribute::Class, EntryClass::Object.to_value()),
|
||||||
|
(Attribute::Class, EntryClass::ClassType.to_value()),
|
||||||
|
(Attribute::ClassName, Value::new_iutf8("testclass")),
|
||||||
|
(
|
||||||
|
Attribute::Uuid,
|
||||||
|
Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Attribute::Description,
|
||||||
|
Value::Utf8("class test".to_string())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let mut preload = PRELOAD.clone();
|
||||||
|
preload.push(e);
|
||||||
|
|
||||||
|
run_modify_test!(
|
||||||
|
Ok(()),
|
||||||
|
preload,
|
||||||
|
filter!(f_eq(
|
||||||
|
Attribute::ClassName,
|
||||||
|
PartialValue::new_iutf8("testclass")
|
||||||
|
)),
|
||||||
|
modlist!([
|
||||||
|
m_pres(Attribute::May, &Value::from(Attribute::Name)),
|
||||||
|
m_pres(Attribute::Must, &Value::from(Attribute::Name)),
|
||||||
|
]),
|
||||||
|
Some(E_TEST_ACCOUNT.clone()),
|
||||||
|
|_| {},
|
||||||
|
|_| {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pre_delete_deny() {
|
||||||
|
// Test deleting with class: system is rejected.
|
||||||
|
let e = entry_init!(
|
||||||
|
(Attribute::Class, EntryClass::Account.to_value()),
|
||||||
|
(Attribute::Class, EntryClass::Person.to_value()),
|
||||||
|
(Attribute::Class, EntryClass::System.to_value()),
|
||||||
|
(Attribute::Name, Value::new_iname("testperson")),
|
||||||
|
(
|
||||||
|
Attribute::DisplayName,
|
||||||
|
Value::Utf8("testperson".to_string())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut preload = PRELOAD.clone();
|
||||||
|
preload.push(e);
|
||||||
|
|
||||||
|
run_delete_test!(
|
||||||
|
Err(OperationError::SystemProtectedObject),
|
||||||
|
preload,
|
||||||
|
filter!(f_eq(Attribute::Name, PartialValue::new_iname("testperson"))),
|
||||||
|
Some(E_TEST_ACCOUNT.clone()),
|
||||||
|
|_| {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_modify_domain() {
|
||||||
|
// Can edit *my* domain_ssid and domain_name
|
||||||
|
// Show that adding a system class is denied
|
||||||
|
let e = entry_init!(
|
||||||
|
(Attribute::Class, EntryClass::DomainInfo.to_value()),
|
||||||
|
(Attribute::Name, Value::new_iname("domain_example.net.au")),
|
||||||
|
(Attribute::Uuid, Value::Uuid(uuid::uuid!("96fd1112-28bc-48ae-9dda-5acb4719aaba"))),
|
||||||
|
(
|
||||||
|
Attribute::Description,
|
||||||
|
Value::new_utf8s("Demonstration of a remote domain's info being created for uuid generation in test_modify_domain")
|
||||||
|
),
|
||||||
|
(Attribute::DomainUuid, Value::Uuid(uuid::uuid!("96fd1112-28bc-48ae-9dda-5acb4719aaba"))),
|
||||||
|
(Attribute::DomainName, Value::new_iname("example.net.au")),
|
||||||
|
(Attribute::DomainDisplayName, Value::Utf8("example.net.au".to_string())),
|
||||||
|
(Attribute::DomainSsid, Value::Utf8("Example_Wifi".to_string())),
|
||||||
|
(Attribute::Version, Value::Uint32(1))
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut preload = PRELOAD.clone();
|
||||||
|
preload.push(e);
|
||||||
|
|
||||||
|
run_modify_test!(
|
||||||
|
Ok(()),
|
||||||
|
preload,
|
||||||
|
filter!(f_eq(
|
||||||
|
Attribute::Name,
|
||||||
|
PartialValue::new_iname("domain_example.net.au")
|
||||||
|
)),
|
||||||
|
modlist!([
|
||||||
|
m_purge(Attribute::DomainSsid),
|
||||||
|
m_pres(Attribute::DomainSsid, &Value::new_utf8s("NewExampleWifi")),
|
||||||
|
]),
|
||||||
|
Some(E_TEST_ACCOUNT.clone()),
|
||||||
|
|_| {},
|
||||||
|
|_| {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ext_create_domain() {
|
||||||
|
// can not add a domain_info type - note the lack of class: system
|
||||||
|
let e = entry_init!(
|
||||||
|
(Attribute::Class, EntryClass::DomainInfo.to_value()),
|
||||||
|
(Attribute::Name, Value::new_iname("domain_example.net.au")),
|
||||||
|
(Attribute::Uuid, Value::Uuid(uuid::uuid!("96fd1112-28bc-48ae-9dda-5acb4719aaba"))),
|
||||||
|
(
|
||||||
|
Attribute::Description,
|
||||||
|
Value::new_utf8s("Demonstration of a remote domain's info being created for uuid generation in test_modify_domain")
|
||||||
|
),
|
||||||
|
(Attribute::DomainUuid, Value::Uuid(uuid::uuid!("96fd1112-28bc-48ae-9dda-5acb4719aaba"))),
|
||||||
|
(Attribute::DomainName, Value::new_iname("example.net.au")),
|
||||||
|
(Attribute::DomainDisplayName, Value::Utf8("example.net.au".to_string())),
|
||||||
|
(Attribute::DomainSsid, Value::Utf8("Example_Wifi".to_string())),
|
||||||
|
(Attribute::Version, Value::Uint32(1))
|
||||||
|
);
|
||||||
|
|
||||||
|
let create = vec![e];
|
||||||
|
let preload = PRELOAD.clone();
|
||||||
|
|
||||||
|
run_create_test!(
|
||||||
|
Err(OperationError::SystemProtectedObject),
|
||||||
|
preload,
|
||||||
|
create,
|
||||||
|
Some(E_TEST_ACCOUNT.clone()),
|
||||||
|
|_| {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_domain() {
|
||||||
|
// On the real thing we have a class: system, but to prove the point ...
|
||||||
|
let e = entry_init!(
|
||||||
|
(Attribute::Class, EntryClass::DomainInfo.to_value()),
|
||||||
|
(Attribute::Name, Value::new_iname("domain_example.net.au")),
|
||||||
|
(Attribute::Uuid, Value::Uuid(uuid::uuid!("96fd1112-28bc-48ae-9dda-5acb4719aaba"))),
|
||||||
|
(
|
||||||
|
Attribute::Description,
|
||||||
|
Value::new_utf8s("Demonstration of a remote domain's info being created for uuid generation in test_modify_domain")
|
||||||
|
),
|
||||||
|
(Attribute::DomainUuid, Value::Uuid(uuid::uuid!("96fd1112-28bc-48ae-9dda-5acb4719aaba"))),
|
||||||
|
(Attribute::DomainName, Value::new_iname("example.net.au")),
|
||||||
|
(Attribute::DomainDisplayName, Value::Utf8("example.net.au".to_string())),
|
||||||
|
(Attribute::DomainSsid, Value::Utf8("Example_Wifi".to_string())),
|
||||||
|
(Attribute::Version, Value::Uint32(1))
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut preload = PRELOAD.clone();
|
||||||
|
preload.push(e);
|
||||||
|
|
||||||
|
run_delete_test!(
|
||||||
|
Err(OperationError::SystemProtectedObject),
|
||||||
|
preload,
|
||||||
|
filter!(f_eq(
|
||||||
|
Attribute::Name,
|
||||||
|
PartialValue::new_iname("domain_example.net.au")
|
||||||
|
)),
|
||||||
|
Some(E_TEST_ACCOUNT.clone()),
|
||||||
|
|_| {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
use super::proto::*;
|
use super::proto::*;
|
||||||
use crate::plugins::Plugins;
|
use crate::plugins::Plugins;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::server::{ChangeFlag, ServerPhase};
|
use crate::server::ChangeFlag;
|
||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
@ -343,7 +343,7 @@ impl QueryServerWriteTransaction<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "info", skip_all)]
|
#[instrument(level = "debug", skip_all)]
|
||||||
fn consumer_apply_changes_v1(
|
fn consumer_apply_changes_v1(
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx_domain_version: DomainVersion,
|
ctx_domain_version: DomainVersion,
|
||||||
|
@ -548,7 +548,7 @@ impl QueryServerWriteTransaction<'_> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "info", skip_all)]
|
#[instrument(level = "debug", skip_all)]
|
||||||
fn consumer_apply_refresh_v1(
|
fn consumer_apply_refresh_v1(
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx_domain_version: DomainVersion,
|
ctx_domain_version: DomainVersion,
|
||||||
|
@ -583,7 +583,6 @@ impl QueryServerWriteTransaction<'_> {
|
||||||
};
|
};
|
||||||
|
|
||||||
// == ⚠️ Below this point we begin to make changes! ==
|
// == ⚠️ Below this point we begin to make changes! ==
|
||||||
self.set_phase_bootstrap();
|
|
||||||
|
|
||||||
// Update the d_uuid. This is what defines us as being part of this repl topology!
|
// Update the d_uuid. This is what defines us as being part of this repl topology!
|
||||||
self.be_txn
|
self.be_txn
|
||||||
|
@ -598,6 +597,7 @@ impl QueryServerWriteTransaction<'_> {
|
||||||
self.reset_server_uuid()?;
|
self.reset_server_uuid()?;
|
||||||
|
|
||||||
// Delete all entries - *proper delete, not just tombstone!*
|
// Delete all entries - *proper delete, not just tombstone!*
|
||||||
|
|
||||||
self.be_txn
|
self.be_txn
|
||||||
.danger_delete_all_db_content()
|
.danger_delete_all_db_content()
|
||||||
.inspect_err(|err| {
|
.inspect_err(|err| {
|
||||||
|
@ -609,12 +609,6 @@ impl QueryServerWriteTransaction<'_> {
|
||||||
error!(?err, "Failed to reset in memory schema to clean state");
|
error!(?err, "Failed to reset in memory schema to clean state");
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Reindex now to force some basic indexes to exist as we consume the schema
|
|
||||||
// from our replica.
|
|
||||||
self.reindex(false).inspect_err(|err| {
|
|
||||||
error!(?err, "Failed to reload schema");
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Apply the schema entries first. This is the foundation that everything
|
// Apply the schema entries first. This is the foundation that everything
|
||||||
// else will build upon!
|
// else will build upon!
|
||||||
self.consumer_refresh_create_entries(ctx_schema_entries)
|
self.consumer_refresh_create_entries(ctx_schema_entries)
|
||||||
|
@ -627,9 +621,6 @@ impl QueryServerWriteTransaction<'_> {
|
||||||
error!(?err, "Failed to reload schema");
|
error!(?err, "Failed to reload schema");
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Schema is now ready
|
|
||||||
self.set_phase(ServerPhase::SchemaReady);
|
|
||||||
|
|
||||||
// We have to reindex to force all the existing indexes to be dumped
|
// We have to reindex to force all the existing indexes to be dumped
|
||||||
// and recreated before we start to import.
|
// and recreated before we start to import.
|
||||||
self.reindex(false).inspect_err(|err| {
|
self.reindex(false).inspect_err(|err| {
|
||||||
|
@ -661,10 +652,7 @@ impl QueryServerWriteTransaction<'_> {
|
||||||
| ChangeFlag::KEY_MATERIAL,
|
| ChangeFlag::KEY_MATERIAL,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Domain info is now ready.
|
// That's it! We are GOOD to go!
|
||||||
self.set_phase(ServerPhase::DomainInfoReady);
|
|
||||||
|
|
||||||
// ==== That's it! We are GOOD to go! ====
|
|
||||||
|
|
||||||
// Create all the entries. Note we don't hit plugins here beside post repl plugs.
|
// Create all the entries. Note we don't hit plugins here beside post repl plugs.
|
||||||
self.consumer_refresh_create_entries(ctx_entries)
|
self.consumer_refresh_create_entries(ctx_entries)
|
||||||
|
@ -684,9 +672,6 @@ impl QueryServerWriteTransaction<'_> {
|
||||||
error!(?err, "Unable to update RUV with supplier ranges.");
|
error!(?err, "Unable to update RUV with supplier ranges.");
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Refresh complete
|
|
||||||
self.set_phase(ServerPhase::Running);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,17 +1,16 @@
|
||||||
use super::profiles::{
|
use super::profiles::{
|
||||||
AccessControlCreateResolved, AccessControlReceiverCondition, AccessControlTargetCondition,
|
AccessControlCreateResolved, AccessControlReceiverCondition, AccessControlTargetCondition,
|
||||||
};
|
};
|
||||||
use super::protected::PROTECTED_ENTRY_CLASSES;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
pub(super) enum CreateResult {
|
pub(super) enum CreateResult {
|
||||||
Deny,
|
Denied,
|
||||||
Grant,
|
Grant,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum IResult {
|
enum IResult {
|
||||||
Deny,
|
Denied,
|
||||||
Grant,
|
Grant,
|
||||||
Ignore,
|
Ignore,
|
||||||
}
|
}
|
||||||
|
@ -26,25 +25,25 @@ pub(super) fn apply_create_access<'a>(
|
||||||
|
|
||||||
// This module can never yield a grant.
|
// This module can never yield a grant.
|
||||||
match protected_filter_entry(ident, entry) {
|
match protected_filter_entry(ident, entry) {
|
||||||
IResult::Deny => denied = true,
|
IResult::Denied => denied = true,
|
||||||
IResult::Grant | IResult::Ignore => {}
|
IResult::Grant | IResult::Ignore => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
match create_filter_entry(ident, related_acp, entry) {
|
match create_filter_entry(ident, related_acp, entry) {
|
||||||
IResult::Deny => denied = true,
|
IResult::Denied => denied = true,
|
||||||
IResult::Grant => grant = true,
|
IResult::Grant => grant = true,
|
||||||
IResult::Ignore => {}
|
IResult::Ignore => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if denied {
|
if denied {
|
||||||
// Something explicitly said no.
|
// Something explicitly said no.
|
||||||
CreateResult::Deny
|
CreateResult::Denied
|
||||||
} else if grant {
|
} else if grant {
|
||||||
// Something said yes
|
// Something said yes
|
||||||
CreateResult::Grant
|
CreateResult::Grant
|
||||||
} else {
|
} else {
|
||||||
// Nothing said yes.
|
// Nothing said yes.
|
||||||
CreateResult::Deny
|
CreateResult::Denied
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +60,7 @@ fn create_filter_entry<'a>(
|
||||||
}
|
}
|
||||||
IdentType::Synch(_) => {
|
IdentType::Synch(_) => {
|
||||||
security_critical!("Blocking sync check");
|
security_critical!("Blocking sync check");
|
||||||
return IResult::Deny;
|
return IResult::Denied;
|
||||||
}
|
}
|
||||||
IdentType::User(_) => {}
|
IdentType::User(_) => {}
|
||||||
};
|
};
|
||||||
|
@ -70,7 +69,7 @@ fn create_filter_entry<'a>(
|
||||||
match ident.access_scope() {
|
match ident.access_scope() {
|
||||||
AccessScope::ReadOnly | AccessScope::Synchronise => {
|
AccessScope::ReadOnly | AccessScope::Synchronise => {
|
||||||
security_access!("denied ❌ - identity access scope is not permitted to create");
|
security_access!("denied ❌ - identity access scope is not permitted to create");
|
||||||
return IResult::Deny;
|
return IResult::Denied;
|
||||||
}
|
}
|
||||||
AccessScope::ReadWrite => {
|
AccessScope::ReadWrite => {
|
||||||
// As you were
|
// As you were
|
||||||
|
@ -97,7 +96,7 @@ fn create_filter_entry<'a>(
|
||||||
Some(s) => s.collect(),
|
Some(s) => s.collect(),
|
||||||
None => {
|
None => {
|
||||||
admin_error!("Class set failed to build - corrupted entry?");
|
admin_error!("Class set failed to build - corrupted entry?");
|
||||||
return IResult::Deny;
|
return IResult::Denied;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -174,22 +173,22 @@ fn protected_filter_entry(ident: &Identity, entry: &Entry<EntryInit, EntryNew>)
|
||||||
}
|
}
|
||||||
IdentType::Synch(_) => {
|
IdentType::Synch(_) => {
|
||||||
security_access!("sync agreements may not directly create entities");
|
security_access!("sync agreements may not directly create entities");
|
||||||
IResult::Deny
|
IResult::Denied
|
||||||
}
|
}
|
||||||
IdentType::User(_) => {
|
IdentType::User(_) => {
|
||||||
// Now check things ...
|
// Now check things ...
|
||||||
if let Some(classes) = entry.get_ava_as_iutf8(Attribute::Class) {
|
|
||||||
if classes.is_disjoint(&PROTECTED_ENTRY_CLASSES) {
|
// For now we just block create on sync object
|
||||||
// It's different, go ahead
|
if let Some(classes) = entry.get_ava_set(Attribute::Class) {
|
||||||
IResult::Ignore
|
if classes.contains(&EntryClass::SyncObject.into()) {
|
||||||
} else {
|
// Block the mod
|
||||||
// Block the mod, something is present
|
|
||||||
security_access!("attempt to create with protected class type");
|
security_access!("attempt to create with protected class type");
|
||||||
IResult::Deny
|
IResult::Denied
|
||||||
|
} else {
|
||||||
|
IResult::Ignore
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Nothing to check - this entry will fail to create anyway because it has
|
// Nothing to check.
|
||||||
// no classes
|
|
||||||
IResult::Ignore
|
IResult::Ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
use super::profiles::{
|
use super::profiles::{
|
||||||
AccessControlDeleteResolved, AccessControlReceiverCondition, AccessControlTargetCondition,
|
AccessControlDeleteResolved, AccessControlReceiverCondition, AccessControlTargetCondition,
|
||||||
};
|
};
|
||||||
use super::protected::PROTECTED_ENTRY_CLASSES;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub(super) enum DeleteResult {
|
pub(super) enum DeleteResult {
|
||||||
Deny,
|
Denied,
|
||||||
Grant,
|
Grant,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum IResult {
|
enum IResult {
|
||||||
Deny,
|
Denied,
|
||||||
Grant,
|
Grant,
|
||||||
Ignore,
|
Ignore,
|
||||||
}
|
}
|
||||||
|
@ -25,25 +24,25 @@ pub(super) fn apply_delete_access<'a>(
|
||||||
let mut grant = false;
|
let mut grant = false;
|
||||||
|
|
||||||
match protected_filter_entry(ident, entry) {
|
match protected_filter_entry(ident, entry) {
|
||||||
IResult::Deny => denied = true,
|
IResult::Denied => denied = true,
|
||||||
IResult::Grant | IResult::Ignore => {}
|
IResult::Grant | IResult::Ignore => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
match delete_filter_entry(ident, related_acp, entry) {
|
match delete_filter_entry(ident, related_acp, entry) {
|
||||||
IResult::Deny => denied = true,
|
IResult::Denied => denied = true,
|
||||||
IResult::Grant => grant = true,
|
IResult::Grant => grant = true,
|
||||||
IResult::Ignore => {}
|
IResult::Ignore => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if denied {
|
if denied {
|
||||||
// Something explicitly said no.
|
// Something explicitly said no.
|
||||||
DeleteResult::Deny
|
DeleteResult::Denied
|
||||||
} else if grant {
|
} else if grant {
|
||||||
// Something said yes
|
// Something said yes
|
||||||
DeleteResult::Grant
|
DeleteResult::Grant
|
||||||
} else {
|
} else {
|
||||||
// Nothing said yes.
|
// Nothing said yes.
|
||||||
DeleteResult::Deny
|
DeleteResult::Denied
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +59,7 @@ fn delete_filter_entry<'a>(
|
||||||
}
|
}
|
||||||
IdentType::Synch(_) => {
|
IdentType::Synch(_) => {
|
||||||
security_critical!("Blocking sync check");
|
security_critical!("Blocking sync check");
|
||||||
return IResult::Deny;
|
return IResult::Denied;
|
||||||
}
|
}
|
||||||
IdentType::User(_) => {}
|
IdentType::User(_) => {}
|
||||||
};
|
};
|
||||||
|
@ -69,7 +68,7 @@ fn delete_filter_entry<'a>(
|
||||||
match ident.access_scope() {
|
match ident.access_scope() {
|
||||||
AccessScope::ReadOnly | AccessScope::Synchronise => {
|
AccessScope::ReadOnly | AccessScope::Synchronise => {
|
||||||
security_access!("denied ❌ - identity access scope is not permitted to delete");
|
security_access!("denied ❌ - identity access scope is not permitted to delete");
|
||||||
return IResult::Deny;
|
return IResult::Denied;
|
||||||
}
|
}
|
||||||
AccessScope::ReadWrite => {
|
AccessScope::ReadWrite => {
|
||||||
// As you were
|
// As you were
|
||||||
|
@ -153,30 +152,28 @@ fn protected_filter_entry(ident: &Identity, entry: &Arc<EntrySealedCommitted>) -
|
||||||
}
|
}
|
||||||
IdentType::Synch(_) => {
|
IdentType::Synch(_) => {
|
||||||
security_access!("sync agreements may not directly delete entities");
|
security_access!("sync agreements may not directly delete entities");
|
||||||
IResult::Deny
|
IResult::Denied
|
||||||
}
|
}
|
||||||
IdentType::User(_) => {
|
IdentType::User(_) => {
|
||||||
|
// Now check things ...
|
||||||
|
|
||||||
|
// For now we just block create on sync object
|
||||||
|
if let Some(classes) = entry.get_ava_set(Attribute::Class) {
|
||||||
|
if classes.contains(&EntryClass::SyncObject.into()) {
|
||||||
|
// Block the mod
|
||||||
|
security_access!("attempt to delete with protected class type");
|
||||||
|
return IResult::Denied;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Prevent deletion of entries that exist in the system controlled entry range.
|
// Prevent deletion of entries that exist in the system controlled entry range.
|
||||||
if entry.get_uuid() <= UUID_ANONYMOUS {
|
if entry.get_uuid() <= UUID_ANONYMOUS {
|
||||||
security_access!("attempt to delete system builtin entry");
|
security_access!("attempt to delete system builtin entry");
|
||||||
return IResult::Deny;
|
return IResult::Denied;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent deleting some protected types.
|
// Checks exhausted, no more input from us
|
||||||
if let Some(classes) = entry.get_ava_as_iutf8(Attribute::Class) {
|
IResult::Ignore
|
||||||
if classes.is_disjoint(&PROTECTED_ENTRY_CLASSES) {
|
|
||||||
// It's different, go ahead
|
|
||||||
IResult::Ignore
|
|
||||||
} else {
|
|
||||||
// Block the mod, something is present
|
|
||||||
security_access!("attempt to create with protected class type");
|
|
||||||
IResult::Deny
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Nothing to check - this entry will fail to create anyway because it has
|
|
||||||
// no classes
|
|
||||||
IResult::Ignore
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,13 +50,12 @@ mod create;
|
||||||
mod delete;
|
mod delete;
|
||||||
mod modify;
|
mod modify;
|
||||||
pub mod profiles;
|
pub mod profiles;
|
||||||
mod protected;
|
|
||||||
mod search;
|
mod search;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum Access {
|
pub enum Access {
|
||||||
Grant,
|
Grant,
|
||||||
Deny,
|
Denied,
|
||||||
Allow(BTreeSet<Attribute>),
|
Allow(BTreeSet<Attribute>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +63,7 @@ impl From<&Access> for ScimAttributeEffectiveAccess {
|
||||||
fn from(value: &Access) -> Self {
|
fn from(value: &Access) -> Self {
|
||||||
match value {
|
match value {
|
||||||
Access::Grant => Self::Grant,
|
Access::Grant => Self::Grant,
|
||||||
Access::Deny => Self::Deny,
|
Access::Denied => Self::Denied,
|
||||||
Access::Allow(set) => Self::Allow(set.clone()),
|
Access::Allow(set) => Self::Allow(set.clone()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,7 +72,7 @@ impl From<&Access> for ScimAttributeEffectiveAccess {
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum AccessClass {
|
pub enum AccessClass {
|
||||||
Grant,
|
Grant,
|
||||||
Deny,
|
Denied,
|
||||||
Allow(BTreeSet<AttrString>),
|
Allow(BTreeSet<AttrString>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,22 +86,12 @@ pub struct AccessEffectivePermission {
|
||||||
pub search: Access,
|
pub search: Access,
|
||||||
pub modify_pres: Access,
|
pub modify_pres: Access,
|
||||||
pub modify_rem: Access,
|
pub modify_rem: Access,
|
||||||
pub modify_pres_class: AccessClass,
|
pub modify_class: AccessClass,
|
||||||
pub modify_rem_class: AccessClass,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum AccessBasicResult {
|
pub enum AccessResult {
|
||||||
// Deny this operation unconditionally.
|
// Deny this operation unconditionally.
|
||||||
Deny,
|
Denied,
|
||||||
// Unbounded allow, provided no deny state exists.
|
|
||||||
Grant,
|
|
||||||
// This module makes no decisions about this entry.
|
|
||||||
Ignore,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum AccessSrchResult {
|
|
||||||
// Deny this operation unconditionally.
|
|
||||||
Deny,
|
|
||||||
// Unbounded allow, provided no deny state exists.
|
// Unbounded allow, provided no deny state exists.
|
||||||
Grant,
|
Grant,
|
||||||
// This module makes no decisions about this entry.
|
// This module makes no decisions about this entry.
|
||||||
|
@ -110,37 +99,24 @@ pub enum AccessSrchResult {
|
||||||
// Limit the allowed attr set to this - this doesn't
|
// Limit the allowed attr set to this - this doesn't
|
||||||
// allow anything, it constrains what might be allowed
|
// allow anything, it constrains what might be allowed
|
||||||
// by a later module.
|
// by a later module.
|
||||||
/*
|
Constrain(BTreeSet<Attribute>),
|
||||||
Constrain {
|
// Allow these attributes within constraints.
|
||||||
attr: BTreeSet<Attribute>,
|
Allow(BTreeSet<Attribute>),
|
||||||
},
|
|
||||||
*/
|
|
||||||
Allow { attr: BTreeSet<Attribute> },
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum AccessModResult<'a> {
|
#[allow(dead_code)]
|
||||||
|
pub enum AccessResultClass<'a> {
|
||||||
// Deny this operation unconditionally.
|
// Deny this operation unconditionally.
|
||||||
Deny,
|
Denied,
|
||||||
// Unbounded allow, provided no deny state exists.
|
// Unbounded allow, provided no denied exists.
|
||||||
// Grant,
|
Grant,
|
||||||
// This module makes no decisions about this entry.
|
// This module makes no decisions about this entry.
|
||||||
Ignore,
|
Ignore,
|
||||||
// Limit the allowed attr set to this - this doesn't
|
// Limit the allowed attr set to this - this doesn't
|
||||||
// allow anything, it constrains what might be allowed
|
// allow anything, it constrains what might be allowed.
|
||||||
// by a later module.
|
Constrain(BTreeSet<&'a str>),
|
||||||
Constrain {
|
// Allow these attributes within constraints.
|
||||||
pres_attr: BTreeSet<Attribute>,
|
Allow(BTreeSet<&'a str>),
|
||||||
rem_attr: BTreeSet<Attribute>,
|
|
||||||
pres_cls: Option<BTreeSet<&'a str>>,
|
|
||||||
rem_cls: Option<BTreeSet<&'a str>>,
|
|
||||||
},
|
|
||||||
// Allow these modifications within constraints.
|
|
||||||
Allow {
|
|
||||||
pres_attr: BTreeSet<Attribute>,
|
|
||||||
rem_attr: BTreeSet<Attribute>,
|
|
||||||
pres_class: BTreeSet<&'a str>,
|
|
||||||
rem_class: BTreeSet<&'a str>,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
@ -327,7 +303,7 @@ pub trait AccessControlsTransaction<'a> {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|e| {
|
.filter(|e| {
|
||||||
match apply_search_access(ident, related_acp.as_slice(), e) {
|
match apply_search_access(ident, related_acp.as_slice(), e) {
|
||||||
SearchResult::Deny => false,
|
SearchResult::Denied => false,
|
||||||
SearchResult::Grant => true,
|
SearchResult::Grant => true,
|
||||||
SearchResult::Allow(allowed_attrs) => {
|
SearchResult::Allow(allowed_attrs) => {
|
||||||
// The allow set constrained.
|
// The allow set constrained.
|
||||||
|
@ -425,7 +401,7 @@ pub trait AccessControlsTransaction<'a> {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|entry| {
|
.filter_map(|entry| {
|
||||||
match apply_search_access(&se.ident, &search_related_acp, &entry) {
|
match apply_search_access(&se.ident, &search_related_acp, &entry) {
|
||||||
SearchResult::Deny => {
|
SearchResult::Denied => {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
SearchResult::Grant => {
|
SearchResult::Grant => {
|
||||||
|
@ -560,8 +536,7 @@ pub trait AccessControlsTransaction<'a> {
|
||||||
// Build the set of classes that we to work on, only in terms of "addition". To remove
|
// Build the set of classes that we to work on, only in terms of "addition". To remove
|
||||||
// I think we have no limit, but ... william of the future may find a problem with this
|
// I think we have no limit, but ... william of the future may find a problem with this
|
||||||
// policy.
|
// policy.
|
||||||
let mut requested_pres_classes: BTreeSet<&str> = Default::default();
|
let mut requested_classes: BTreeSet<&str> = Default::default();
|
||||||
let mut requested_rem_classes: BTreeSet<&str> = Default::default();
|
|
||||||
|
|
||||||
for modify in me.modlist.iter() {
|
for modify in me.modlist.iter() {
|
||||||
match modify {
|
match modify {
|
||||||
|
@ -573,33 +548,27 @@ pub trait AccessControlsTransaction<'a> {
|
||||||
// existence, and second, we would have failed the mod at schema checking
|
// existence, and second, we would have failed the mod at schema checking
|
||||||
// earlier in the process as these were not correctly type. As a result
|
// earlier in the process as these were not correctly type. As a result
|
||||||
// we can trust these to be correct here and not to be "None".
|
// we can trust these to be correct here and not to be "None".
|
||||||
requested_pres_classes.extend(v.to_str())
|
requested_classes.extend(v.to_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Modify::Removed(a, v) => {
|
Modify::Removed(a, v) => {
|
||||||
if a == Attribute::Class.as_ref() {
|
if a == Attribute::Class.as_ref() {
|
||||||
requested_rem_classes.extend(v.to_str())
|
requested_classes.extend(v.to_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Modify::Set(a, v) => {
|
Modify::Set(a, v) => {
|
||||||
if a == Attribute::Class.as_ref() {
|
if a == Attribute::Class.as_ref() {
|
||||||
// This is a reasonably complex case - we actually have to contemplate
|
// flatten to remove the option down to an iterator
|
||||||
// the difference between what exists and what doesn't, but that's per-entry.
|
requested_classes.extend(v.as_iutf8_iter().into_iter().flatten())
|
||||||
//
|
|
||||||
// for now, we treat this as both pres and rem, but I think that ultimately
|
|
||||||
// to fix this we need to make all modifies apply in terms of "batch mod"
|
|
||||||
requested_pres_classes.extend(v.as_iutf8_iter().into_iter().flatten());
|
|
||||||
requested_rem_classes.extend(v.as_iutf8_iter().into_iter().flatten());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!(?requested_pres, "Requested present attribute set");
|
debug!(?requested_pres, "Requested present set");
|
||||||
debug!(?requested_rem, "Requested remove attribute set");
|
debug!(?requested_rem, "Requested remove set");
|
||||||
debug!(?requested_pres_classes, "Requested present class set");
|
debug!(?requested_classes, "Requested class set");
|
||||||
debug!(?requested_rem_classes, "Requested remove class set");
|
|
||||||
|
|
||||||
let sync_agmts = self.get_sync_agreements();
|
let sync_agmts = self.get_sync_agreements();
|
||||||
|
|
||||||
|
@ -607,16 +576,9 @@ pub trait AccessControlsTransaction<'a> {
|
||||||
debug!(entry_id = %e.get_display_id());
|
debug!(entry_id = %e.get_display_id());
|
||||||
|
|
||||||
match apply_modify_access(&me.ident, related_acp.as_slice(), sync_agmts, e) {
|
match apply_modify_access(&me.ident, related_acp.as_slice(), sync_agmts, e) {
|
||||||
ModifyResult::Deny => false,
|
ModifyResult::Denied => false,
|
||||||
ModifyResult::Grant => true,
|
ModifyResult::Grant => true,
|
||||||
ModifyResult::Allow {
|
ModifyResult::Allow { pres, rem, cls } => {
|
||||||
pres,
|
|
||||||
rem,
|
|
||||||
pres_cls,
|
|
||||||
rem_cls,
|
|
||||||
} => {
|
|
||||||
let mut decision = true;
|
|
||||||
|
|
||||||
if !requested_pres.is_subset(&pres) {
|
if !requested_pres.is_subset(&pres) {
|
||||||
security_error!("requested_pres is not a subset of allowed");
|
security_error!("requested_pres is not a subset of allowed");
|
||||||
security_error!(
|
security_error!(
|
||||||
|
@ -624,41 +586,23 @@ pub trait AccessControlsTransaction<'a> {
|
||||||
requested_pres,
|
requested_pres,
|
||||||
pres
|
pres
|
||||||
);
|
);
|
||||||
decision = false
|
false
|
||||||
};
|
} else if !requested_rem.is_subset(&rem) {
|
||||||
|
|
||||||
if !requested_rem.is_subset(&rem) {
|
|
||||||
security_error!("requested_rem is not a subset of allowed");
|
security_error!("requested_rem is not a subset of allowed");
|
||||||
security_error!("requested_rem: {:?} !⊆ allowed: {:?}", requested_rem, rem);
|
security_error!("requested_rem: {:?} !⊆ allowed: {:?}", requested_rem, rem);
|
||||||
decision = false;
|
false
|
||||||
};
|
} else if !requested_classes.is_subset(&cls) {
|
||||||
|
security_error!("requested_classes is not a subset of allowed");
|
||||||
if !requested_pres_classes.is_subset(&pres_cls) {
|
|
||||||
security_error!("requested_pres_classes is not a subset of allowed");
|
|
||||||
security_error!(
|
security_error!(
|
||||||
"requested_pres_classes: {:?} !⊆ allowed: {:?}",
|
"requested_classes: {:?} !⊆ allowed: {:?}",
|
||||||
requested_pres_classes,
|
requested_classes,
|
||||||
pres_cls
|
cls
|
||||||
);
|
);
|
||||||
decision = false;
|
false
|
||||||
};
|
} else {
|
||||||
|
|
||||||
if !requested_rem_classes.is_subset(&rem_cls) {
|
|
||||||
security_error!("requested_rem_classes is not a subset of allowed");
|
|
||||||
security_error!(
|
|
||||||
"requested_rem_classes: {:?} !⊆ allowed: {:?}",
|
|
||||||
requested_rem_classes,
|
|
||||||
rem_cls
|
|
||||||
);
|
|
||||||
decision = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if decision {
|
|
||||||
debug!("passed pres, rem, classes check.");
|
debug!("passed pres, rem, classes check.");
|
||||||
}
|
true
|
||||||
|
} // if acc == false
|
||||||
// Yield the result
|
|
||||||
decision
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -724,55 +668,47 @@ pub trait AccessControlsTransaction<'a> {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mut requested_pres_classes: BTreeSet<&str> = Default::default();
|
// Build the set of classes that we to work on, only in terms of "addition". To remove
|
||||||
let mut requested_rem_classes: BTreeSet<&str> = Default::default();
|
// I think we have no limit, but ... william of the future may find a problem with this
|
||||||
|
// policy.
|
||||||
for modify in modlist.iter() {
|
let requested_classes: BTreeSet<&str> = modlist
|
||||||
match modify {
|
.iter()
|
||||||
|
.filter_map(|m| match m {
|
||||||
Modify::Present(a, v) => {
|
Modify::Present(a, v) => {
|
||||||
if a == Attribute::Class.as_ref() {
|
if a == Attribute::Class.as_ref() {
|
||||||
requested_pres_classes.extend(v.to_str())
|
// Here we have an option<&str> which could mean there is a risk of
|
||||||
|
// a malicious entity attempting to trick us by masking class mods
|
||||||
|
// in non-iutf8 types. However, the server first won't respect their
|
||||||
|
// existence, and second, we would have failed the mod at schema checking
|
||||||
|
// earlier in the process as these were not correctly type. As a result
|
||||||
|
// we can trust these to be correct here and not to be "None".
|
||||||
|
v.to_str()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Modify::Removed(a, v) => {
|
Modify::Removed(a, v) => {
|
||||||
if a == Attribute::Class.as_ref() {
|
if a == Attribute::Class.as_ref() {
|
||||||
requested_rem_classes.extend(v.to_str())
|
v.to_str()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Modify::Set(a, v) => {
|
_ => None,
|
||||||
if a == Attribute::Class.as_ref() {
|
})
|
||||||
// This is a reasonably complex case - we actually have to contemplate
|
.collect();
|
||||||
// the difference between what exists and what doesn't, but that's per-entry.
|
|
||||||
//
|
|
||||||
// for now, we treat this as both pres and rem, but I think that ultimately
|
|
||||||
// to fix this we need to make all modifies apply in terms of "batch mod"
|
|
||||||
requested_pres_classes.extend(v.as_iutf8_iter().into_iter().flatten());
|
|
||||||
requested_rem_classes.extend(v.as_iutf8_iter().into_iter().flatten());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!(?requested_pres, "Requested present set");
|
debug!(?requested_pres, "Requested present set");
|
||||||
debug!(?requested_rem, "Requested remove set");
|
debug!(?requested_rem, "Requested remove set");
|
||||||
debug!(?requested_pres_classes, "Requested present class set");
|
debug!(?requested_classes, "Requested class set");
|
||||||
debug!(?requested_rem_classes, "Requested remove class set");
|
|
||||||
debug!(entry_id = %e.get_display_id());
|
debug!(entry_id = %e.get_display_id());
|
||||||
|
|
||||||
let sync_agmts = self.get_sync_agreements();
|
let sync_agmts = self.get_sync_agreements();
|
||||||
|
|
||||||
match apply_modify_access(&me.ident, related_acp.as_slice(), sync_agmts, e) {
|
match apply_modify_access(&me.ident, related_acp.as_slice(), sync_agmts, e) {
|
||||||
ModifyResult::Deny => false,
|
ModifyResult::Denied => false,
|
||||||
ModifyResult::Grant => true,
|
ModifyResult::Grant => true,
|
||||||
ModifyResult::Allow {
|
ModifyResult::Allow { pres, rem, cls } => {
|
||||||
pres,
|
|
||||||
rem,
|
|
||||||
pres_cls,
|
|
||||||
rem_cls,
|
|
||||||
} => {
|
|
||||||
let mut decision = true;
|
|
||||||
|
|
||||||
if !requested_pres.is_subset(&pres) {
|
if !requested_pres.is_subset(&pres) {
|
||||||
security_error!("requested_pres is not a subset of allowed");
|
security_error!("requested_pres is not a subset of allowed");
|
||||||
security_error!(
|
security_error!(
|
||||||
|
@ -780,41 +716,23 @@ pub trait AccessControlsTransaction<'a> {
|
||||||
requested_pres,
|
requested_pres,
|
||||||
pres
|
pres
|
||||||
);
|
);
|
||||||
decision = false
|
false
|
||||||
};
|
} else if !requested_rem.is_subset(&rem) {
|
||||||
|
|
||||||
if !requested_rem.is_subset(&rem) {
|
|
||||||
security_error!("requested_rem is not a subset of allowed");
|
security_error!("requested_rem is not a subset of allowed");
|
||||||
security_error!("requested_rem: {:?} !⊆ allowed: {:?}", requested_rem, rem);
|
security_error!("requested_rem: {:?} !⊆ allowed: {:?}", requested_rem, rem);
|
||||||
decision = false;
|
false
|
||||||
};
|
} else if !requested_classes.is_subset(&cls) {
|
||||||
|
security_error!("requested_classes is not a subset of allowed");
|
||||||
if !requested_pres_classes.is_subset(&pres_cls) {
|
|
||||||
security_error!("requested_pres_classes is not a subset of allowed");
|
|
||||||
security_error!(
|
security_error!(
|
||||||
"requested_classes: {:?} !⊆ allowed: {:?}",
|
"requested_classes: {:?} !⊆ allowed: {:?}",
|
||||||
requested_pres_classes,
|
requested_classes,
|
||||||
pres_cls
|
cls
|
||||||
);
|
);
|
||||||
decision = false;
|
false
|
||||||
};
|
} else {
|
||||||
|
security_access!("passed pres, rem, classes check.");
|
||||||
if !requested_rem_classes.is_subset(&rem_cls) {
|
true
|
||||||
security_error!("requested_rem_classes is not a subset of allowed");
|
} // if acc == false
|
||||||
security_error!(
|
|
||||||
"requested_classes: {:?} !⊆ allowed: {:?}",
|
|
||||||
requested_rem_classes,
|
|
||||||
rem_cls
|
|
||||||
);
|
|
||||||
decision = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if decision {
|
|
||||||
debug!("passed pres, rem, classes check.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Yield the result
|
|
||||||
decision
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -862,7 +780,7 @@ pub trait AccessControlsTransaction<'a> {
|
||||||
// For each entry
|
// For each entry
|
||||||
let r = entries.iter().all(|e| {
|
let r = entries.iter().all(|e| {
|
||||||
match apply_create_access(&ce.ident, related_acp.as_slice(), e) {
|
match apply_create_access(&ce.ident, related_acp.as_slice(), e) {
|
||||||
CreateResult::Deny => false,
|
CreateResult::Denied => false,
|
||||||
CreateResult::Grant => true,
|
CreateResult::Grant => true,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -918,7 +836,7 @@ pub trait AccessControlsTransaction<'a> {
|
||||||
// For each entry
|
// For each entry
|
||||||
let r = entries.iter().all(|e| {
|
let r = entries.iter().all(|e| {
|
||||||
match apply_delete_access(&de.ident, related_acp.as_slice(), e) {
|
match apply_delete_access(&de.ident, related_acp.as_slice(), e) {
|
||||||
DeleteResult::Deny => false,
|
DeleteResult::Denied => false,
|
||||||
DeleteResult::Grant => true,
|
DeleteResult::Grant => true,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1007,7 +925,7 @@ pub trait AccessControlsTransaction<'a> {
|
||||||
) -> AccessEffectivePermission {
|
) -> AccessEffectivePermission {
|
||||||
// == search ==
|
// == search ==
|
||||||
let search_effective = match apply_search_access(ident, search_related_acp, entry) {
|
let search_effective = match apply_search_access(ident, search_related_acp, entry) {
|
||||||
SearchResult::Deny => Access::Deny,
|
SearchResult::Denied => Access::Denied,
|
||||||
SearchResult::Grant => Access::Grant,
|
SearchResult::Grant => Access::Grant,
|
||||||
SearchResult::Allow(allowed_attrs) => {
|
SearchResult::Allow(allowed_attrs) => {
|
||||||
// Bound by requested attrs?
|
// Bound by requested attrs?
|
||||||
|
@ -1016,30 +934,14 @@ pub trait AccessControlsTransaction<'a> {
|
||||||
};
|
};
|
||||||
|
|
||||||
// == modify ==
|
// == modify ==
|
||||||
let (modify_pres, modify_rem, modify_pres_class, modify_rem_class) =
|
let (modify_pres, modify_rem, modify_class) =
|
||||||
match apply_modify_access(ident, modify_related_acp, sync_agmts, entry) {
|
match apply_modify_access(ident, modify_related_acp, sync_agmts, entry) {
|
||||||
ModifyResult::Deny => (
|
ModifyResult::Denied => (Access::Denied, Access::Denied, AccessClass::Denied),
|
||||||
Access::Deny,
|
ModifyResult::Grant => (Access::Grant, Access::Grant, AccessClass::Grant),
|
||||||
Access::Deny,
|
ModifyResult::Allow { pres, rem, cls } => (
|
||||||
AccessClass::Deny,
|
|
||||||
AccessClass::Deny,
|
|
||||||
),
|
|
||||||
ModifyResult::Grant => (
|
|
||||||
Access::Grant,
|
|
||||||
Access::Grant,
|
|
||||||
AccessClass::Grant,
|
|
||||||
AccessClass::Grant,
|
|
||||||
),
|
|
||||||
ModifyResult::Allow {
|
|
||||||
pres,
|
|
||||||
rem,
|
|
||||||
pres_cls,
|
|
||||||
rem_cls,
|
|
||||||
} => (
|
|
||||||
Access::Allow(pres.into_iter().collect()),
|
Access::Allow(pres.into_iter().collect()),
|
||||||
Access::Allow(rem.into_iter().collect()),
|
Access::Allow(rem.into_iter().collect()),
|
||||||
AccessClass::Allow(pres_cls.into_iter().map(|s| s.into()).collect()),
|
AccessClass::Allow(cls.into_iter().map(|s| s.into()).collect()),
|
||||||
AccessClass::Allow(rem_cls.into_iter().map(|s| s.into()).collect()),
|
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1047,7 +949,7 @@ pub trait AccessControlsTransaction<'a> {
|
||||||
let delete_status = apply_delete_access(ident, delete_related_acp, entry);
|
let delete_status = apply_delete_access(ident, delete_related_acp, entry);
|
||||||
|
|
||||||
let delete = match delete_status {
|
let delete = match delete_status {
|
||||||
DeleteResult::Deny => false,
|
DeleteResult::Denied => false,
|
||||||
DeleteResult::Grant => true,
|
DeleteResult::Grant => true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1058,8 +960,7 @@ pub trait AccessControlsTransaction<'a> {
|
||||||
search: search_effective,
|
search: search_effective,
|
||||||
modify_pres,
|
modify_pres,
|
||||||
modify_rem,
|
modify_rem,
|
||||||
modify_pres_class,
|
modify_class,
|
||||||
modify_rem_class,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2265,8 +2166,6 @@ mod tests {
|
||||||
"name class",
|
"name class",
|
||||||
// And the class allowed is account
|
// And the class allowed is account
|
||||||
EntryClass::Account.into(),
|
EntryClass::Account.into(),
|
||||||
// And the class allowed is account
|
|
||||||
EntryClass::Account.into(),
|
|
||||||
);
|
);
|
||||||
// Allow member, class is group. IE not account
|
// Allow member, class is group. IE not account
|
||||||
let acp_deny = AccessControlModify::from_raw(
|
let acp_deny = AccessControlModify::from_raw(
|
||||||
|
@ -2283,8 +2182,8 @@ mod tests {
|
||||||
"member class",
|
"member class",
|
||||||
// Allow rem name and class
|
// Allow rem name and class
|
||||||
"member class",
|
"member class",
|
||||||
EntryClass::Group.into(),
|
// And the class allowed is account
|
||||||
EntryClass::Group.into(),
|
"group",
|
||||||
);
|
);
|
||||||
// Does not have a pres or rem class in attrs
|
// Does not have a pres or rem class in attrs
|
||||||
let acp_no_class = AccessControlModify::from_raw(
|
let acp_no_class = AccessControlModify::from_raw(
|
||||||
|
@ -2302,8 +2201,7 @@ mod tests {
|
||||||
// Allow rem name and class
|
// Allow rem name and class
|
||||||
"name class",
|
"name class",
|
||||||
// And the class allowed is NOT an account ...
|
// And the class allowed is NOT an account ...
|
||||||
EntryClass::Group.into(),
|
"group",
|
||||||
EntryClass::Group.into(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Test allowed pres
|
// Test allowed pres
|
||||||
|
@ -2389,7 +2287,6 @@ mod tests {
|
||||||
"name class",
|
"name class",
|
||||||
// And the class allowed is account
|
// And the class allowed is account
|
||||||
EntryClass::Account.into(),
|
EntryClass::Account.into(),
|
||||||
EntryClass::Account.into(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
test_acp_modify!(&me_pres_ro, vec![acp_allow.clone()], &r_set, false);
|
test_acp_modify!(&me_pres_ro, vec![acp_allow.clone()], &r_set, false);
|
||||||
|
@ -2717,8 +2614,7 @@ mod tests {
|
||||||
search: Access::Allow(btreeset![Attribute::Name]),
|
search: Access::Allow(btreeset![Attribute::Name]),
|
||||||
modify_pres: Access::Allow(BTreeSet::new()),
|
modify_pres: Access::Allow(BTreeSet::new()),
|
||||||
modify_rem: Access::Allow(BTreeSet::new()),
|
modify_rem: Access::Allow(BTreeSet::new()),
|
||||||
modify_pres_class: AccessClass::Allow(BTreeSet::new()),
|
modify_class: AccessClass::Allow(BTreeSet::new()),
|
||||||
modify_rem_class: AccessClass::Allow(BTreeSet::new()),
|
|
||||||
}]
|
}]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -2751,7 +2647,6 @@ mod tests {
|
||||||
Attribute::Name.as_ref(),
|
Attribute::Name.as_ref(),
|
||||||
Attribute::Name.as_ref(),
|
Attribute::Name.as_ref(),
|
||||||
EntryClass::Object.into(),
|
EntryClass::Object.into(),
|
||||||
EntryClass::Object.into(),
|
|
||||||
)],
|
)],
|
||||||
&r_set,
|
&r_set,
|
||||||
vec![AccessEffectivePermission {
|
vec![AccessEffectivePermission {
|
||||||
|
@ -2761,8 +2656,7 @@ mod tests {
|
||||||
search: Access::Allow(BTreeSet::new()),
|
search: Access::Allow(BTreeSet::new()),
|
||||||
modify_pres: Access::Allow(btreeset![Attribute::Name]),
|
modify_pres: Access::Allow(btreeset![Attribute::Name]),
|
||||||
modify_rem: Access::Allow(btreeset![Attribute::Name]),
|
modify_rem: Access::Allow(btreeset![Attribute::Name]),
|
||||||
modify_pres_class: AccessClass::Allow(btreeset![EntryClass::Object.into()]),
|
modify_class: AccessClass::Allow(btreeset![EntryClass::Object.into()]),
|
||||||
modify_rem_class: AccessClass::Allow(btreeset![EntryClass::Object.into()]),
|
|
||||||
}]
|
}]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -2902,7 +2796,6 @@ mod tests {
|
||||||
&format!("{} {}", Attribute::UserAuthTokenSession, Attribute::Name),
|
&format!("{} {}", Attribute::UserAuthTokenSession, Attribute::Name),
|
||||||
// And the class allowed is account, we don't use it though.
|
// And the class allowed is account, we don't use it though.
|
||||||
EntryClass::Account.into(),
|
EntryClass::Account.into(),
|
||||||
EntryClass::Account.into(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// NOTE! Syntax doesn't matter here, we just need to assert if the attr exists
|
// NOTE! Syntax doesn't matter here, we just need to assert if the attr exists
|
||||||
|
@ -3403,7 +3296,6 @@ mod tests {
|
||||||
"name class",
|
"name class",
|
||||||
// And the class allowed is account
|
// And the class allowed is account
|
||||||
EntryClass::Account.into(),
|
EntryClass::Account.into(),
|
||||||
EntryClass::Account.into(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Test allowed pres
|
// Test allowed pres
|
||||||
|
@ -3532,185 +3424,4 @@ mod tests {
|
||||||
// Finally test it!
|
// Finally test it!
|
||||||
test_acp_search_reduce!(&se_anon_ro, vec![acp], r_set, ex_anon_some);
|
test_acp_search_reduce!(&se_anon_ro, vec![acp], r_set, ex_anon_some);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_access_protected_deny_create() {
|
|
||||||
sketching::test_init();
|
|
||||||
|
|
||||||
let ev1 = entry_init!(
|
|
||||||
(Attribute::Class, EntryClass::Account.to_value()),
|
|
||||||
(Attribute::Name, Value::new_iname("testperson1")),
|
|
||||||
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
|
|
||||||
);
|
|
||||||
let r1_set = vec![ev1];
|
|
||||||
|
|
||||||
let ev2 = entry_init!(
|
|
||||||
(Attribute::Class, EntryClass::Account.to_value()),
|
|
||||||
(Attribute::Class, EntryClass::System.to_value()),
|
|
||||||
(Attribute::Name, Value::new_iname("testperson1")),
|
|
||||||
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
|
|
||||||
);
|
|
||||||
|
|
||||||
let r2_set = vec![ev2];
|
|
||||||
|
|
||||||
let ce_admin = CreateEvent::new_impersonate_identity(
|
|
||||||
Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone()),
|
|
||||||
vec![],
|
|
||||||
);
|
|
||||||
|
|
||||||
let acp = AccessControlCreate::from_raw(
|
|
||||||
"test_create",
|
|
||||||
Uuid::new_v4(),
|
|
||||||
// Apply to admin
|
|
||||||
UUID_TEST_GROUP_1,
|
|
||||||
// To create matching filter testperson
|
|
||||||
// Can this be empty?
|
|
||||||
filter_valid!(f_eq(
|
|
||||||
Attribute::Name,
|
|
||||||
PartialValue::new_iname("testperson1")
|
|
||||||
)),
|
|
||||||
// classes
|
|
||||||
EntryClass::Account.into(),
|
|
||||||
// attrs
|
|
||||||
"class name uuid",
|
|
||||||
);
|
|
||||||
|
|
||||||
// Test allowed to create
|
|
||||||
test_acp_create!(&ce_admin, vec![acp.clone()], &r1_set, true);
|
|
||||||
// Test reject create (not allowed attr)
|
|
||||||
test_acp_create!(&ce_admin, vec![acp.clone()], &r2_set, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_access_protected_deny_delete() {
|
|
||||||
sketching::test_init();
|
|
||||||
|
|
||||||
let ev1 = entry_init!(
|
|
||||||
(Attribute::Class, EntryClass::Account.to_value()),
|
|
||||||
(Attribute::Name, Value::new_iname("testperson1")),
|
|
||||||
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
|
|
||||||
)
|
|
||||||
.into_sealed_committed();
|
|
||||||
let r1_set = vec![Arc::new(ev1)];
|
|
||||||
|
|
||||||
let ev2 = entry_init!(
|
|
||||||
(Attribute::Class, EntryClass::Account.to_value()),
|
|
||||||
(Attribute::Class, EntryClass::System.to_value()),
|
|
||||||
(Attribute::Name, Value::new_iname("testperson1")),
|
|
||||||
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
|
|
||||||
)
|
|
||||||
.into_sealed_committed();
|
|
||||||
|
|
||||||
let r2_set = vec![Arc::new(ev2)];
|
|
||||||
|
|
||||||
let de = DeleteEvent::new_impersonate_entry(
|
|
||||||
E_TEST_ACCOUNT_1.clone(),
|
|
||||||
filter_all!(f_eq(
|
|
||||||
Attribute::Name,
|
|
||||||
PartialValue::new_iname("testperson1")
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
let acp = AccessControlDelete::from_raw(
|
|
||||||
"test_delete",
|
|
||||||
Uuid::new_v4(),
|
|
||||||
// Apply to admin
|
|
||||||
UUID_TEST_GROUP_1,
|
|
||||||
// To delete testperson
|
|
||||||
filter_valid!(f_eq(
|
|
||||||
Attribute::Name,
|
|
||||||
PartialValue::new_iname("testperson1")
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Test allowed to delete
|
|
||||||
test_acp_delete!(&de, vec![acp.clone()], &r1_set, true);
|
|
||||||
// Test not allowed to delete
|
|
||||||
test_acp_delete!(&de, vec![acp.clone()], &r2_set, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_access_protected_deny_modify() {
|
|
||||||
sketching::test_init();
|
|
||||||
|
|
||||||
let ev1 = entry_init!(
|
|
||||||
(Attribute::Class, EntryClass::Account.to_value()),
|
|
||||||
(Attribute::Name, Value::new_iname("testperson1")),
|
|
||||||
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
|
|
||||||
)
|
|
||||||
.into_sealed_committed();
|
|
||||||
let r1_set = vec![Arc::new(ev1)];
|
|
||||||
|
|
||||||
let ev2 = entry_init!(
|
|
||||||
(Attribute::Class, EntryClass::Account.to_value()),
|
|
||||||
(Attribute::Class, EntryClass::System.to_value()),
|
|
||||||
(Attribute::Name, Value::new_iname("testperson1")),
|
|
||||||
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
|
|
||||||
)
|
|
||||||
.into_sealed_committed();
|
|
||||||
|
|
||||||
let r2_set = vec![Arc::new(ev2)];
|
|
||||||
|
|
||||||
// Allow name and class, class is account
|
|
||||||
let acp_allow = AccessControlModify::from_raw(
|
|
||||||
"test_modify_allow",
|
|
||||||
Uuid::new_v4(),
|
|
||||||
// Apply to admin
|
|
||||||
UUID_TEST_GROUP_1,
|
|
||||||
// To modify testperson
|
|
||||||
filter_valid!(f_eq(
|
|
||||||
Attribute::Name,
|
|
||||||
PartialValue::new_iname("testperson1")
|
|
||||||
)),
|
|
||||||
// Allow pres disp name and class
|
|
||||||
"displayname class",
|
|
||||||
// Allow rem disp name and class
|
|
||||||
"displayname class",
|
|
||||||
// And the classes allowed to add/rem are as such
|
|
||||||
"system recycled",
|
|
||||||
"system recycled",
|
|
||||||
);
|
|
||||||
|
|
||||||
let me_pres = ModifyEvent::new_impersonate_entry(
|
|
||||||
E_TEST_ACCOUNT_1.clone(),
|
|
||||||
filter_all!(f_eq(
|
|
||||||
Attribute::Name,
|
|
||||||
PartialValue::new_iname("testperson1")
|
|
||||||
)),
|
|
||||||
modlist!([m_pres(Attribute::DisplayName, &Value::new_utf8s("value"))]),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Test allowed pres
|
|
||||||
test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r1_set, true);
|
|
||||||
|
|
||||||
// Test not allowed pres (due to system class)
|
|
||||||
test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r2_set, false);
|
|
||||||
|
|
||||||
// Test that we can not remove class::system
|
|
||||||
let me_rem_sys = ModifyEvent::new_impersonate_entry(
|
|
||||||
E_TEST_ACCOUNT_1.clone(),
|
|
||||||
filter_all!(f_eq(
|
|
||||||
Attribute::Class,
|
|
||||||
PartialValue::new_iname("testperson1")
|
|
||||||
)),
|
|
||||||
modlist!([m_remove(
|
|
||||||
Attribute::Class,
|
|
||||||
&EntryClass::System.to_partialvalue()
|
|
||||||
)]),
|
|
||||||
);
|
|
||||||
|
|
||||||
test_acp_modify!(&me_rem_sys, vec![acp_allow.clone()], &r2_set, false);
|
|
||||||
|
|
||||||
// Ensure that we can't add recycled.
|
|
||||||
let me_pres = ModifyEvent::new_impersonate_entry(
|
|
||||||
E_TEST_ACCOUNT_1.clone(),
|
|
||||||
filter_all!(f_eq(
|
|
||||||
Attribute::Name,
|
|
||||||
PartialValue::new_iname("testperson1")
|
|
||||||
)),
|
|
||||||
modlist!([m_pres(Attribute::Class, &EntryClass::Recycled.to_value())]),
|
|
||||||
);
|
|
||||||
|
|
||||||
test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r1_set, false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,21 @@
|
||||||
|
use crate::prelude::*;
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
use super::profiles::{
|
use super::profiles::{
|
||||||
AccessControlModify, AccessControlModifyResolved, AccessControlReceiverCondition,
|
AccessControlModify, AccessControlModifyResolved, AccessControlReceiverCondition,
|
||||||
AccessControlTargetCondition,
|
AccessControlTargetCondition,
|
||||||
};
|
};
|
||||||
use super::protected::{
|
use super::{AccessResult, AccessResultClass};
|
||||||
LOCKED_ENTRY_CLASSES, PROTECTED_MOD_ENTRY_CLASSES, PROTECTED_MOD_PRES_ENTRY_CLASSES,
|
|
||||||
PROTECTED_MOD_REM_ENTRY_CLASSES,
|
|
||||||
};
|
|
||||||
use super::{AccessBasicResult, AccessModResult};
|
|
||||||
use crate::prelude::*;
|
|
||||||
use hashbrown::HashMap;
|
|
||||||
use std::collections::BTreeSet;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub(super) enum ModifyResult<'a> {
|
pub(super) enum ModifyResult<'a> {
|
||||||
Deny,
|
Denied,
|
||||||
Grant,
|
Grant,
|
||||||
Allow {
|
Allow {
|
||||||
pres: BTreeSet<Attribute>,
|
pres: BTreeSet<Attribute>,
|
||||||
rem: BTreeSet<Attribute>,
|
rem: BTreeSet<Attribute>,
|
||||||
pres_cls: BTreeSet<&'a str>,
|
cls: BTreeSet<&'a str>,
|
||||||
rem_cls: BTreeSet<&'a str>,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,17 +27,12 @@ pub(super) fn apply_modify_access<'a>(
|
||||||
) -> ModifyResult<'a> {
|
) -> ModifyResult<'a> {
|
||||||
let mut denied = false;
|
let mut denied = false;
|
||||||
let mut grant = false;
|
let mut grant = false;
|
||||||
|
|
||||||
let mut constrain_pres = BTreeSet::default();
|
let mut constrain_pres = BTreeSet::default();
|
||||||
let mut allow_pres = BTreeSet::default();
|
let mut allow_pres = BTreeSet::default();
|
||||||
let mut constrain_rem = BTreeSet::default();
|
let mut constrain_rem = BTreeSet::default();
|
||||||
let mut allow_rem = BTreeSet::default();
|
let mut allow_rem = BTreeSet::default();
|
||||||
|
let mut constrain_cls = BTreeSet::default();
|
||||||
let mut constrain_pres_cls = BTreeSet::default();
|
let mut allow_cls = BTreeSet::default();
|
||||||
let mut allow_pres_cls = BTreeSet::default();
|
|
||||||
|
|
||||||
let mut constrain_rem_cls = BTreeSet::default();
|
|
||||||
let mut allow_rem_cls = BTreeSet::default();
|
|
||||||
|
|
||||||
// Some useful references.
|
// Some useful references.
|
||||||
// - needed for checking entry manager conditions.
|
// - needed for checking entry manager conditions.
|
||||||
|
@ -52,53 +43,28 @@ pub(super) fn apply_modify_access<'a>(
|
||||||
// kind of being three operations all in one.
|
// kind of being three operations all in one.
|
||||||
|
|
||||||
match modify_ident_test(ident) {
|
match modify_ident_test(ident) {
|
||||||
AccessBasicResult::Deny => denied = true,
|
AccessResult::Denied => denied = true,
|
||||||
AccessBasicResult::Grant => grant = true,
|
AccessResult::Grant => grant = true,
|
||||||
AccessBasicResult::Ignore => {}
|
AccessResult::Ignore => {}
|
||||||
}
|
AccessResult::Constrain(mut set) => constrain_pres.append(&mut set),
|
||||||
|
AccessResult::Allow(mut set) => allow_pres.append(&mut set),
|
||||||
// Check with protected if we should proceed.
|
|
||||||
match modify_protected_attrs(ident, entry) {
|
|
||||||
AccessModResult::Deny => denied = true,
|
|
||||||
AccessModResult::Constrain {
|
|
||||||
mut pres_attr,
|
|
||||||
mut rem_attr,
|
|
||||||
pres_cls,
|
|
||||||
rem_cls,
|
|
||||||
} => {
|
|
||||||
constrain_rem.append(&mut rem_attr);
|
|
||||||
constrain_pres.append(&mut pres_attr);
|
|
||||||
|
|
||||||
if let Some(mut pres_cls) = pres_cls {
|
|
||||||
constrain_pres_cls.append(&mut pres_cls);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(mut rem_cls) = rem_cls {
|
|
||||||
constrain_rem_cls.append(&mut rem_cls);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Can't grant.
|
|
||||||
// AccessModResult::Grant |
|
|
||||||
// Can't allow
|
|
||||||
AccessModResult::Allow { .. } | AccessModResult::Ignore => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !grant && !denied {
|
if !grant && !denied {
|
||||||
|
// Check with protected if we should proceed.
|
||||||
|
|
||||||
// If it's a sync entry, constrain it.
|
// If it's a sync entry, constrain it.
|
||||||
match modify_sync_constrain(ident, entry, sync_agreements) {
|
match modify_sync_constrain(ident, entry, sync_agreements) {
|
||||||
AccessModResult::Deny => denied = true,
|
AccessResult::Denied => denied = true,
|
||||||
AccessModResult::Constrain {
|
AccessResult::Constrain(mut set) => {
|
||||||
mut pres_attr,
|
constrain_rem.extend(set.iter().cloned());
|
||||||
mut rem_attr,
|
constrain_pres.append(&mut set)
|
||||||
..
|
|
||||||
} => {
|
|
||||||
constrain_rem.append(&mut rem_attr);
|
|
||||||
constrain_pres.append(&mut pres_attr);
|
|
||||||
}
|
}
|
||||||
// Can't grant.
|
// Can't grant.
|
||||||
// AccessModResult::Grant |
|
AccessResult::Grant |
|
||||||
// Can't allow
|
// Can't allow
|
||||||
AccessModResult::Allow { .. } | AccessModResult::Ignore => {}
|
AccessResult::Allow(_) |
|
||||||
|
AccessResult::Ignore => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup the acp's here
|
// Setup the acp's here
|
||||||
|
@ -156,27 +122,35 @@ pub(super) fn apply_modify_access<'a>(
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
match modify_pres_test(scoped_acp.as_slice()) {
|
match modify_pres_test(scoped_acp.as_slice()) {
|
||||||
AccessModResult::Deny => denied = true,
|
AccessResult::Denied => denied = true,
|
||||||
// Can never return a unilateral grant.
|
// Can never return a unilateral grant.
|
||||||
// AccessModResult::Grant => {}
|
AccessResult::Grant => {}
|
||||||
AccessModResult::Ignore => {}
|
AccessResult::Ignore => {}
|
||||||
AccessModResult::Constrain { .. } => {}
|
AccessResult::Constrain(mut set) => constrain_pres.append(&mut set),
|
||||||
AccessModResult::Allow {
|
AccessResult::Allow(mut set) => allow_pres.append(&mut set),
|
||||||
mut pres_attr,
|
}
|
||||||
mut rem_attr,
|
|
||||||
mut pres_class,
|
match modify_rem_test(scoped_acp.as_slice()) {
|
||||||
mut rem_class,
|
AccessResult::Denied => denied = true,
|
||||||
} => {
|
// Can never return a unilateral grant.
|
||||||
allow_pres.append(&mut pres_attr);
|
AccessResult::Grant => {}
|
||||||
allow_rem.append(&mut rem_attr);
|
AccessResult::Ignore => {}
|
||||||
allow_pres_cls.append(&mut pres_class);
|
AccessResult::Constrain(mut set) => constrain_rem.append(&mut set),
|
||||||
allow_rem_cls.append(&mut rem_class);
|
AccessResult::Allow(mut set) => allow_rem.append(&mut set),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match modify_cls_test(scoped_acp.as_slice()) {
|
||||||
|
AccessResultClass::Denied => denied = true,
|
||||||
|
// Can never return a unilateral grant.
|
||||||
|
AccessResultClass::Grant => {}
|
||||||
|
AccessResultClass::Ignore => {}
|
||||||
|
AccessResultClass::Constrain(mut set) => constrain_cls.append(&mut set),
|
||||||
|
AccessResultClass::Allow(mut set) => allow_cls.append(&mut set),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if denied {
|
if denied {
|
||||||
ModifyResult::Deny
|
ModifyResult::Denied
|
||||||
} else if grant {
|
} else if grant {
|
||||||
ModifyResult::Grant
|
ModifyResult::Grant
|
||||||
} else {
|
} else {
|
||||||
|
@ -194,48 +168,31 @@ pub(super) fn apply_modify_access<'a>(
|
||||||
allow_rem
|
allow_rem
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut allowed_pres_cls = if !constrain_pres_cls.is_empty() {
|
let allowed_cls = if !constrain_cls.is_empty() {
|
||||||
// bit_and
|
// bit_and
|
||||||
&constrain_pres_cls & &allow_pres_cls
|
&constrain_cls & &allow_cls
|
||||||
} else {
|
} else {
|
||||||
allow_pres_cls
|
allow_cls
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut allowed_rem_cls = if !constrain_rem_cls.is_empty() {
|
|
||||||
// bit_and
|
|
||||||
&constrain_rem_cls & &allow_rem_cls
|
|
||||||
} else {
|
|
||||||
allow_rem_cls
|
|
||||||
};
|
|
||||||
|
|
||||||
// Deny these classes from being part of any addition or removal to an entry
|
|
||||||
for protected_cls in PROTECTED_MOD_PRES_ENTRY_CLASSES.iter() {
|
|
||||||
allowed_pres_cls.remove(protected_cls.as_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
for protected_cls in PROTECTED_MOD_REM_ENTRY_CLASSES.iter() {
|
|
||||||
allowed_rem_cls.remove(protected_cls.as_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
ModifyResult::Allow {
|
ModifyResult::Allow {
|
||||||
pres: allowed_pres,
|
pres: allowed_pres,
|
||||||
rem: allowed_rem,
|
rem: allowed_rem,
|
||||||
pres_cls: allowed_pres_cls,
|
cls: allowed_cls,
|
||||||
rem_cls: allowed_rem_cls,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn modify_ident_test(ident: &Identity) -> AccessBasicResult {
|
fn modify_ident_test(ident: &Identity) -> AccessResult {
|
||||||
match &ident.origin {
|
match &ident.origin {
|
||||||
IdentType::Internal => {
|
IdentType::Internal => {
|
||||||
trace!("Internal operation, bypassing access check");
|
trace!("Internal operation, bypassing access check");
|
||||||
// No need to check ACS
|
// No need to check ACS
|
||||||
return AccessBasicResult::Grant;
|
return AccessResult::Grant;
|
||||||
}
|
}
|
||||||
IdentType::Synch(_) => {
|
IdentType::Synch(_) => {
|
||||||
security_critical!("Blocking sync check");
|
security_critical!("Blocking sync check");
|
||||||
return AccessBasicResult::Deny;
|
return AccessResult::Denied;
|
||||||
}
|
}
|
||||||
IdentType::User(_) => {}
|
IdentType::User(_) => {}
|
||||||
};
|
};
|
||||||
|
@ -244,56 +201,53 @@ fn modify_ident_test(ident: &Identity) -> AccessBasicResult {
|
||||||
match ident.access_scope() {
|
match ident.access_scope() {
|
||||||
AccessScope::ReadOnly | AccessScope::Synchronise => {
|
AccessScope::ReadOnly | AccessScope::Synchronise => {
|
||||||
security_access!("denied ❌ - identity access scope is not permitted to modify");
|
security_access!("denied ❌ - identity access scope is not permitted to modify");
|
||||||
return AccessBasicResult::Deny;
|
return AccessResult::Denied;
|
||||||
}
|
}
|
||||||
AccessScope::ReadWrite => {
|
AccessScope::ReadWrite => {
|
||||||
// As you were
|
// As you were
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
AccessBasicResult::Ignore
|
AccessResult::Ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
fn modify_pres_test<'a>(scoped_acp: &[&'a AccessControlModify]) -> AccessModResult<'a> {
|
fn modify_pres_test(scoped_acp: &[&AccessControlModify]) -> AccessResult {
|
||||||
let pres_attr: BTreeSet<Attribute> = scoped_acp
|
let allowed_pres: BTreeSet<Attribute> = scoped_acp
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|acp| acp.presattrs.iter().cloned())
|
.flat_map(|acp| acp.presattrs.iter().cloned())
|
||||||
.collect();
|
.collect();
|
||||||
|
AccessResult::Allow(allowed_pres)
|
||||||
|
}
|
||||||
|
|
||||||
let rem_attr: BTreeSet<Attribute> = scoped_acp
|
fn modify_rem_test(scoped_acp: &[&AccessControlModify]) -> AccessResult {
|
||||||
|
let allowed_rem: BTreeSet<Attribute> = scoped_acp
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|acp| acp.remattrs.iter().cloned())
|
.flat_map(|acp| acp.remattrs.iter().cloned())
|
||||||
.collect();
|
.collect();
|
||||||
|
AccessResult::Allow(allowed_rem)
|
||||||
let pres_class: BTreeSet<&'a str> = scoped_acp
|
|
||||||
.iter()
|
|
||||||
.flat_map(|acp| acp.pres_classes.iter().map(|s| s.as_str()))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let rem_class: BTreeSet<&'a str> = scoped_acp
|
|
||||||
.iter()
|
|
||||||
.flat_map(|acp| acp.rem_classes.iter().map(|s| s.as_str()))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
AccessModResult::Allow {
|
|
||||||
pres_attr,
|
|
||||||
rem_attr,
|
|
||||||
pres_class,
|
|
||||||
rem_class,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn modify_sync_constrain<'a>(
|
// TODO: Should this be reverted to the Str borrow method? Or do we try to change
|
||||||
|
// to EntryClass?
|
||||||
|
fn modify_cls_test<'a>(scoped_acp: &[&'a AccessControlModify]) -> AccessResultClass<'a> {
|
||||||
|
let allowed_classes: BTreeSet<&'a str> = scoped_acp
|
||||||
|
.iter()
|
||||||
|
.flat_map(|acp| acp.classes.iter().map(|s| s.as_str()))
|
||||||
|
.collect();
|
||||||
|
AccessResultClass::Allow(allowed_classes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn modify_sync_constrain(
|
||||||
ident: &Identity,
|
ident: &Identity,
|
||||||
entry: &Arc<EntrySealedCommitted>,
|
entry: &Arc<EntrySealedCommitted>,
|
||||||
sync_agreements: &HashMap<Uuid, BTreeSet<Attribute>>,
|
sync_agreements: &HashMap<Uuid, BTreeSet<Attribute>>,
|
||||||
) -> AccessModResult<'a> {
|
) -> AccessResult {
|
||||||
match &ident.origin {
|
match &ident.origin {
|
||||||
IdentType::Internal => AccessModResult::Ignore,
|
IdentType::Internal => AccessResult::Ignore,
|
||||||
IdentType::Synch(_) => {
|
IdentType::Synch(_) => {
|
||||||
// Allowed to mod sync objects. Later we'll probably need to check the limits of what
|
// Allowed to mod sync objects. Later we'll probably need to check the limits of what
|
||||||
// it can do if we go that way.
|
// it can do if we go that way.
|
||||||
AccessModResult::Ignore
|
AccessResult::Ignore
|
||||||
}
|
}
|
||||||
IdentType::User(_) => {
|
IdentType::User(_) => {
|
||||||
// We need to meet these conditions.
|
// We need to meet these conditions.
|
||||||
|
@ -305,7 +259,7 @@ fn modify_sync_constrain<'a>(
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
|
|
||||||
if !is_sync {
|
if !is_sync {
|
||||||
return AccessModResult::Ignore;
|
return AccessResult::Ignore;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(sync_uuid) = entry.get_ava_single_refer(Attribute::SyncParentUuid) {
|
if let Some(sync_uuid) = entry.get_ava_single_refer(Attribute::SyncParentUuid) {
|
||||||
|
@ -320,115 +274,11 @@ fn modify_sync_constrain<'a>(
|
||||||
set.extend(sync_yield_authority.iter().cloned())
|
set.extend(sync_yield_authority.iter().cloned())
|
||||||
}
|
}
|
||||||
|
|
||||||
AccessModResult::Constrain {
|
AccessResult::Constrain(set)
|
||||||
pres_attr: set.clone(),
|
|
||||||
rem_attr: set,
|
|
||||||
pres_cls: None,
|
|
||||||
rem_cls: None,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
warn!(entry = ?entry.get_uuid(), "sync_parent_uuid not found on sync object, preventing all access");
|
warn!(entry = ?entry.get_uuid(), "sync_parent_uuid not found on sync object, preventing all access");
|
||||||
AccessModResult::Deny
|
AccessResult::Denied
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify if the modification runs into limits that are defined by our protection rules.
|
|
||||||
fn modify_protected_attrs<'a>(
|
|
||||||
ident: &Identity,
|
|
||||||
entry: &Arc<EntrySealedCommitted>,
|
|
||||||
) -> AccessModResult<'a> {
|
|
||||||
match &ident.origin {
|
|
||||||
IdentType::Internal | IdentType::Synch(_) => {
|
|
||||||
// We don't constraint or influence these.
|
|
||||||
AccessModResult::Ignore
|
|
||||||
}
|
|
||||||
IdentType::User(_) => {
|
|
||||||
if let Some(classes) = entry.get_ava_as_iutf8(Attribute::Class) {
|
|
||||||
if classes.is_disjoint(&PROTECTED_MOD_ENTRY_CLASSES) {
|
|
||||||
// Not protected, go ahead
|
|
||||||
AccessModResult::Ignore
|
|
||||||
} else {
|
|
||||||
// Okay, the entry is protected, apply the full ruleset.
|
|
||||||
modify_protected_entry_attrs(classes)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Nothing to check - this entry will fail to modify anyway because it has
|
|
||||||
// no classes
|
|
||||||
AccessModResult::Ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn modify_protected_entry_attrs<'a>(classes: &BTreeSet<String>) -> AccessModResult<'a> {
|
|
||||||
// This is where the majority of the logic is - this contains the modification
|
|
||||||
// rules as they apply.
|
|
||||||
|
|
||||||
// First check for the hard-deny rules.
|
|
||||||
if !classes.is_disjoint(&LOCKED_ENTRY_CLASSES) {
|
|
||||||
// Hard deny attribute modifications to these types.
|
|
||||||
return AccessModResult::Deny;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut constrain_attrs = BTreeSet::default();
|
|
||||||
|
|
||||||
// Allows removal of the recycled class specifically on recycled entries.
|
|
||||||
if classes.contains(EntryClass::Recycled.into()) {
|
|
||||||
constrain_attrs.extend([Attribute::Class]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if classes.contains(EntryClass::ClassType.into()) {
|
|
||||||
constrain_attrs.extend([Attribute::May, Attribute::Must]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if classes.contains(EntryClass::SystemConfig.into()) {
|
|
||||||
constrain_attrs.extend([Attribute::BadlistPassword]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow domain settings.
|
|
||||||
if classes.contains(EntryClass::DomainInfo.into()) {
|
|
||||||
constrain_attrs.extend([
|
|
||||||
Attribute::DomainSsid,
|
|
||||||
Attribute::DomainLdapBasedn,
|
|
||||||
Attribute::LdapMaxQueryableAttrs,
|
|
||||||
Attribute::LdapAllowUnixPwBind,
|
|
||||||
Attribute::FernetPrivateKeyStr,
|
|
||||||
Attribute::Es256PrivateKeyDer,
|
|
||||||
Attribute::KeyActionRevoke,
|
|
||||||
Attribute::KeyActionRotate,
|
|
||||||
Attribute::IdVerificationEcKey,
|
|
||||||
Attribute::DeniedName,
|
|
||||||
Attribute::DomainDisplayName,
|
|
||||||
Attribute::Image,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow account policy related attributes to be changed on dyngroup
|
|
||||||
if classes.contains(EntryClass::DynGroup.into()) {
|
|
||||||
constrain_attrs.extend([
|
|
||||||
Attribute::AuthSessionExpiry,
|
|
||||||
Attribute::AuthPasswordMinimumLength,
|
|
||||||
Attribute::CredentialTypeMinimum,
|
|
||||||
Attribute::PrivilegeExpiry,
|
|
||||||
Attribute::WebauthnAttestationCaList,
|
|
||||||
Attribute::LimitSearchMaxResults,
|
|
||||||
Attribute::LimitSearchMaxFilterTest,
|
|
||||||
Attribute::AllowPrimaryCredFallback,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we don't constrain the attributes at all, we have to deny the change
|
|
||||||
// from proceeding.
|
|
||||||
if constrain_attrs.is_empty() {
|
|
||||||
AccessModResult::Deny
|
|
||||||
} else {
|
|
||||||
AccessModResult::Constrain {
|
|
||||||
pres_attr: constrain_attrs.clone(),
|
|
||||||
rem_attr: constrain_attrs,
|
|
||||||
pres_cls: None,
|
|
||||||
rem_cls: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -266,10 +266,9 @@ pub struct AccessControlModifyResolved<'a> {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct AccessControlModify {
|
pub struct AccessControlModify {
|
||||||
pub acp: AccessControlProfile,
|
pub acp: AccessControlProfile,
|
||||||
|
pub classes: Vec<AttrString>,
|
||||||
pub presattrs: Vec<Attribute>,
|
pub presattrs: Vec<Attribute>,
|
||||||
pub remattrs: Vec<Attribute>,
|
pub remattrs: Vec<Attribute>,
|
||||||
pub pres_classes: Vec<AttrString>,
|
|
||||||
pub rem_classes: Vec<AttrString>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AccessControlModify {
|
impl AccessControlModify {
|
||||||
|
@ -294,25 +293,14 @@ impl AccessControlModify {
|
||||||
.map(|i| i.map(Attribute::from).collect())
|
.map(|i| i.map(Attribute::from).collect())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let classes: Vec<AttrString> = value
|
let classes = value
|
||||||
.get_ava_iter_iutf8(Attribute::AcpModifyClass)
|
.get_ava_iter_iutf8(Attribute::AcpModifyClass)
|
||||||
.map(|i| i.map(AttrString::from).collect())
|
.map(|i| i.map(AttrString::from).collect())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let pres_classes = value
|
|
||||||
.get_ava_iter_iutf8(Attribute::AcpModifyPresentClass)
|
|
||||||
.map(|i| i.map(AttrString::from).collect())
|
|
||||||
.unwrap_or_else(|| classes.clone());
|
|
||||||
|
|
||||||
let rem_classes = value
|
|
||||||
.get_ava_iter_iutf8(Attribute::AcpModifyRemoveClass)
|
|
||||||
.map(|i| i.map(AttrString::from).collect())
|
|
||||||
.unwrap_or_else(|| classes);
|
|
||||||
|
|
||||||
Ok(AccessControlModify {
|
Ok(AccessControlModify {
|
||||||
acp: AccessControlProfile::try_from(qs, value)?,
|
acp: AccessControlProfile::try_from(qs, value)?,
|
||||||
pres_classes,
|
classes,
|
||||||
rem_classes,
|
|
||||||
presattrs,
|
presattrs,
|
||||||
remattrs,
|
remattrs,
|
||||||
})
|
})
|
||||||
|
@ -328,8 +316,7 @@ impl AccessControlModify {
|
||||||
targetscope: Filter<FilterValid>,
|
targetscope: Filter<FilterValid>,
|
||||||
presattrs: &str,
|
presattrs: &str,
|
||||||
remattrs: &str,
|
remattrs: &str,
|
||||||
pres_classes: &str,
|
classes: &str,
|
||||||
rem_classes: &str,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
AccessControlModify {
|
AccessControlModify {
|
||||||
acp: AccessControlProfile {
|
acp: AccessControlProfile {
|
||||||
|
@ -338,14 +325,7 @@ impl AccessControlModify {
|
||||||
receiver: AccessControlReceiver::Group(btreeset!(receiver)),
|
receiver: AccessControlReceiver::Group(btreeset!(receiver)),
|
||||||
target: AccessControlTarget::Scope(targetscope),
|
target: AccessControlTarget::Scope(targetscope),
|
||||||
},
|
},
|
||||||
pres_classes: pres_classes
|
classes: classes.split_whitespace().map(AttrString::from).collect(),
|
||||||
.split_whitespace()
|
|
||||||
.map(AttrString::from)
|
|
||||||
.collect(),
|
|
||||||
rem_classes: rem_classes
|
|
||||||
.split_whitespace()
|
|
||||||
.map(AttrString::from)
|
|
||||||
.collect(),
|
|
||||||
presattrs: presattrs.split_whitespace().map(Attribute::from).collect(),
|
presattrs: presattrs.split_whitespace().map(Attribute::from).collect(),
|
||||||
remattrs: remattrs.split_whitespace().map(Attribute::from).collect(),
|
remattrs: remattrs.split_whitespace().map(Attribute::from).collect(),
|
||||||
}
|
}
|
||||||
|
@ -360,8 +340,7 @@ impl AccessControlModify {
|
||||||
target: AccessControlTarget,
|
target: AccessControlTarget,
|
||||||
presattrs: &str,
|
presattrs: &str,
|
||||||
remattrs: &str,
|
remattrs: &str,
|
||||||
pres_classes: &str,
|
classes: &str,
|
||||||
rem_classes: &str,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
AccessControlModify {
|
AccessControlModify {
|
||||||
acp: AccessControlProfile {
|
acp: AccessControlProfile {
|
||||||
|
@ -370,14 +349,7 @@ impl AccessControlModify {
|
||||||
receiver: AccessControlReceiver::EntryManager,
|
receiver: AccessControlReceiver::EntryManager,
|
||||||
target,
|
target,
|
||||||
},
|
},
|
||||||
pres_classes: pres_classes
|
classes: classes.split_whitespace().map(AttrString::from).collect(),
|
||||||
.split_whitespace()
|
|
||||||
.map(AttrString::from)
|
|
||||||
.collect(),
|
|
||||||
rem_classes: rem_classes
|
|
||||||
.split_whitespace()
|
|
||||||
.map(AttrString::from)
|
|
||||||
.collect(),
|
|
||||||
presattrs: presattrs.split_whitespace().map(Attribute::from).collect(),
|
presattrs: presattrs.split_whitespace().map(Attribute::from).collect(),
|
||||||
remattrs: remattrs.split_whitespace().map(Attribute::from).collect(),
|
remattrs: remattrs.split_whitespace().map(Attribute::from).collect(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,83 +0,0 @@
|
||||||
use crate::prelude::EntryClass;
|
|
||||||
use std::collections::BTreeSet;
|
|
||||||
use std::sync::LazyLock;
|
|
||||||
|
|
||||||
/// These entry classes may not be created or deleted, and may invoke some protection rules
|
|
||||||
/// if on an entry.
|
|
||||||
pub static PROTECTED_ENTRY_CLASSES: LazyLock<BTreeSet<String>> = LazyLock::new(|| {
|
|
||||||
let classes = vec![
|
|
||||||
EntryClass::System,
|
|
||||||
EntryClass::DomainInfo,
|
|
||||||
EntryClass::SystemInfo,
|
|
||||||
EntryClass::SystemConfig,
|
|
||||||
EntryClass::DynGroup,
|
|
||||||
EntryClass::SyncObject,
|
|
||||||
EntryClass::Tombstone,
|
|
||||||
EntryClass::Recycled,
|
|
||||||
];
|
|
||||||
|
|
||||||
BTreeSet::from_iter(classes.into_iter().map(|ec| ec.into()))
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Entries with these classes are protected from modifications - not that
|
|
||||||
/// sync object is not present here as there are separate rules for that in
|
|
||||||
/// the modification access module.
|
|
||||||
///
|
|
||||||
/// Recycled is also not protected here as it needs to be able to be removed
|
|
||||||
/// by a recycle bin admin.
|
|
||||||
pub static PROTECTED_MOD_ENTRY_CLASSES: LazyLock<BTreeSet<String>> = LazyLock::new(|| {
|
|
||||||
let classes = vec![
|
|
||||||
EntryClass::System,
|
|
||||||
EntryClass::DomainInfo,
|
|
||||||
EntryClass::SystemInfo,
|
|
||||||
EntryClass::SystemConfig,
|
|
||||||
EntryClass::DynGroup,
|
|
||||||
// EntryClass::SyncObject,
|
|
||||||
EntryClass::Tombstone,
|
|
||||||
EntryClass::Recycled,
|
|
||||||
];
|
|
||||||
|
|
||||||
BTreeSet::from_iter(classes.into_iter().map(|ec| ec.into()))
|
|
||||||
});
|
|
||||||
|
|
||||||
/// These classes may NOT be added to ANY ENTRY
|
|
||||||
pub static PROTECTED_MOD_PRES_ENTRY_CLASSES: LazyLock<BTreeSet<String>> = LazyLock::new(|| {
|
|
||||||
let classes = vec![
|
|
||||||
EntryClass::System,
|
|
||||||
EntryClass::DomainInfo,
|
|
||||||
EntryClass::SystemInfo,
|
|
||||||
EntryClass::SystemConfig,
|
|
||||||
EntryClass::DynGroup,
|
|
||||||
EntryClass::SyncObject,
|
|
||||||
EntryClass::Tombstone,
|
|
||||||
EntryClass::Recycled,
|
|
||||||
];
|
|
||||||
|
|
||||||
BTreeSet::from_iter(classes.into_iter().map(|ec| ec.into()))
|
|
||||||
});
|
|
||||||
|
|
||||||
/// These classes may NOT be removed from ANY ENTRY
|
|
||||||
pub static PROTECTED_MOD_REM_ENTRY_CLASSES: LazyLock<BTreeSet<String>> = LazyLock::new(|| {
|
|
||||||
let classes = vec![
|
|
||||||
EntryClass::System,
|
|
||||||
EntryClass::DomainInfo,
|
|
||||||
EntryClass::SystemInfo,
|
|
||||||
EntryClass::SystemConfig,
|
|
||||||
EntryClass::DynGroup,
|
|
||||||
EntryClass::SyncObject,
|
|
||||||
EntryClass::Tombstone,
|
|
||||||
// EntryClass::Recycled,
|
|
||||||
];
|
|
||||||
|
|
||||||
BTreeSet::from_iter(classes.into_iter().map(|ec| ec.into()))
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Entries with these classes may not be modified under any circumstance.
|
|
||||||
pub static LOCKED_ENTRY_CLASSES: LazyLock<BTreeSet<String>> = LazyLock::new(|| {
|
|
||||||
let classes = vec![
|
|
||||||
EntryClass::Tombstone,
|
|
||||||
// EntryClass::Recycled,
|
|
||||||
];
|
|
||||||
|
|
||||||
BTreeSet::from_iter(classes.into_iter().map(|ec| ec.into()))
|
|
||||||
});
|
|
|
@ -4,11 +4,11 @@ use std::collections::BTreeSet;
|
||||||
use super::profiles::{
|
use super::profiles::{
|
||||||
AccessControlReceiverCondition, AccessControlSearchResolved, AccessControlTargetCondition,
|
AccessControlReceiverCondition, AccessControlSearchResolved, AccessControlTargetCondition,
|
||||||
};
|
};
|
||||||
use super::AccessSrchResult;
|
use super::AccessResult;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub(super) enum SearchResult {
|
pub(super) enum SearchResult {
|
||||||
Deny,
|
Denied,
|
||||||
Grant,
|
Grant,
|
||||||
Allow(BTreeSet<Attribute>),
|
Allow(BTreeSet<Attribute>),
|
||||||
}
|
}
|
||||||
|
@ -23,32 +23,32 @@ pub(super) fn apply_search_access(
|
||||||
// that.
|
// that.
|
||||||
let mut denied = false;
|
let mut denied = false;
|
||||||
let mut grant = false;
|
let mut grant = false;
|
||||||
let constrain = BTreeSet::default();
|
let mut constrain = BTreeSet::default();
|
||||||
let mut allow = BTreeSet::default();
|
let mut allow = BTreeSet::default();
|
||||||
|
|
||||||
// The access control profile
|
// The access control profile
|
||||||
match search_filter_entry(ident, related_acp, entry) {
|
match search_filter_entry(ident, related_acp, entry) {
|
||||||
AccessSrchResult::Deny => denied = true,
|
AccessResult::Denied => denied = true,
|
||||||
AccessSrchResult::Grant => grant = true,
|
AccessResult::Grant => grant = true,
|
||||||
AccessSrchResult::Ignore => {}
|
AccessResult::Ignore => {}
|
||||||
// AccessSrchResult::Constrain { mut attr } => constrain.append(&mut attr),
|
AccessResult::Constrain(mut set) => constrain.append(&mut set),
|
||||||
AccessSrchResult::Allow { mut attr } => allow.append(&mut attr),
|
AccessResult::Allow(mut set) => allow.append(&mut set),
|
||||||
};
|
};
|
||||||
|
|
||||||
match search_oauth2_filter_entry(ident, entry) {
|
match search_oauth2_filter_entry(ident, entry) {
|
||||||
AccessSrchResult::Deny => denied = true,
|
AccessResult::Denied => denied = true,
|
||||||
AccessSrchResult::Grant => grant = true,
|
AccessResult::Grant => grant = true,
|
||||||
AccessSrchResult::Ignore => {}
|
AccessResult::Ignore => {}
|
||||||
// AccessSrchResult::Constrain { mut attr } => constrain.append(&mut attr),
|
AccessResult::Constrain(mut set) => constrain.append(&mut set),
|
||||||
AccessSrchResult::Allow { mut attr } => allow.append(&mut attr),
|
AccessResult::Allow(mut set) => allow.append(&mut set),
|
||||||
};
|
};
|
||||||
|
|
||||||
match search_sync_account_filter_entry(ident, entry) {
|
match search_sync_account_filter_entry(ident, entry) {
|
||||||
AccessSrchResult::Deny => denied = true,
|
AccessResult::Denied => denied = true,
|
||||||
AccessSrchResult::Grant => grant = true,
|
AccessResult::Grant => grant = true,
|
||||||
AccessSrchResult::Ignore => {}
|
AccessResult::Ignore => {}
|
||||||
// AccessSrchResult::Constrain{ mut attr } => constrain.append(&mut attr),
|
AccessResult::Constrain(mut set) => constrain.append(&mut set),
|
||||||
AccessSrchResult::Allow { mut attr } => allow.append(&mut attr),
|
AccessResult::Allow(mut set) => allow.append(&mut set),
|
||||||
};
|
};
|
||||||
|
|
||||||
// We'll add more modules later.
|
// We'll add more modules later.
|
||||||
|
@ -56,7 +56,7 @@ pub(super) fn apply_search_access(
|
||||||
// Now finalise the decision.
|
// Now finalise the decision.
|
||||||
|
|
||||||
if denied {
|
if denied {
|
||||||
SearchResult::Deny
|
SearchResult::Denied
|
||||||
} else if grant {
|
} else if grant {
|
||||||
SearchResult::Grant
|
SearchResult::Grant
|
||||||
} else {
|
} else {
|
||||||
|
@ -74,17 +74,17 @@ fn search_filter_entry(
|
||||||
ident: &Identity,
|
ident: &Identity,
|
||||||
related_acp: &[AccessControlSearchResolved],
|
related_acp: &[AccessControlSearchResolved],
|
||||||
entry: &Arc<EntrySealedCommitted>,
|
entry: &Arc<EntrySealedCommitted>,
|
||||||
) -> AccessSrchResult {
|
) -> AccessResult {
|
||||||
// If this is an internal search, return our working set.
|
// If this is an internal search, return our working set.
|
||||||
match &ident.origin {
|
match &ident.origin {
|
||||||
IdentType::Internal => {
|
IdentType::Internal => {
|
||||||
trace!(uuid = ?entry.get_display_id(), "Internal operation, bypassing access check");
|
trace!(uuid = ?entry.get_display_id(), "Internal operation, bypassing access check");
|
||||||
// No need to check ACS
|
// No need to check ACS
|
||||||
return AccessSrchResult::Grant;
|
return AccessResult::Grant;
|
||||||
}
|
}
|
||||||
IdentType::Synch(_) => {
|
IdentType::Synch(_) => {
|
||||||
security_debug!(uuid = ?entry.get_display_id(), "Blocking sync check");
|
security_debug!(uuid = ?entry.get_display_id(), "Blocking sync check");
|
||||||
return AccessSrchResult::Deny;
|
return AccessResult::Denied;
|
||||||
}
|
}
|
||||||
IdentType::User(_) => {}
|
IdentType::User(_) => {}
|
||||||
};
|
};
|
||||||
|
@ -95,7 +95,7 @@ fn search_filter_entry(
|
||||||
security_debug!(
|
security_debug!(
|
||||||
"denied ❌ - identity access scope 'Synchronise' is not permitted to search"
|
"denied ❌ - identity access scope 'Synchronise' is not permitted to search"
|
||||||
);
|
);
|
||||||
return AccessSrchResult::Deny;
|
return AccessResult::Denied;
|
||||||
}
|
}
|
||||||
AccessScope::ReadOnly | AccessScope::ReadWrite => {
|
AccessScope::ReadOnly | AccessScope::ReadWrite => {
|
||||||
// As you were
|
// As you were
|
||||||
|
@ -161,21 +161,16 @@ fn search_filter_entry(
|
||||||
.flatten()
|
.flatten()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
AccessSrchResult::Allow {
|
AccessResult::Allow(allowed_attrs)
|
||||||
attr: allowed_attrs,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_oauth2_filter_entry(
|
fn search_oauth2_filter_entry(ident: &Identity, entry: &Arc<EntrySealedCommitted>) -> AccessResult {
|
||||||
ident: &Identity,
|
|
||||||
entry: &Arc<EntrySealedCommitted>,
|
|
||||||
) -> AccessSrchResult {
|
|
||||||
match &ident.origin {
|
match &ident.origin {
|
||||||
IdentType::Internal | IdentType::Synch(_) => AccessSrchResult::Ignore,
|
IdentType::Internal | IdentType::Synch(_) => AccessResult::Ignore,
|
||||||
IdentType::User(iuser) => {
|
IdentType::User(iuser) => {
|
||||||
if iuser.entry.get_uuid() == UUID_ANONYMOUS {
|
if iuser.entry.get_uuid() == UUID_ANONYMOUS {
|
||||||
debug!("Anonymous can't access OAuth2 entries, ignoring");
|
debug!("Anonymous can't access OAuth2 entries, ignoring");
|
||||||
return AccessSrchResult::Ignore;
|
return AccessResult::Ignore;
|
||||||
}
|
}
|
||||||
|
|
||||||
let contains_o2_rs = entry
|
let contains_o2_rs = entry
|
||||||
|
@ -195,18 +190,16 @@ fn search_oauth2_filter_entry(
|
||||||
if contains_o2_rs && contains_o2_scope_member {
|
if contains_o2_rs && contains_o2_scope_member {
|
||||||
security_debug!(entry = ?entry.get_uuid(), ident = ?iuser.entry.get_uuid2rdn(), "ident is a memberof a group granted an oauth2 scope by this entry");
|
security_debug!(entry = ?entry.get_uuid(), ident = ?iuser.entry.get_uuid2rdn(), "ident is a memberof a group granted an oauth2 scope by this entry");
|
||||||
|
|
||||||
return AccessSrchResult::Allow {
|
return AccessResult::Allow(btreeset!(
|
||||||
attr: btreeset!(
|
Attribute::Class,
|
||||||
Attribute::Class,
|
Attribute::DisplayName,
|
||||||
Attribute::DisplayName,
|
Attribute::Uuid,
|
||||||
Attribute::Uuid,
|
Attribute::Name,
|
||||||
Attribute::Name,
|
Attribute::OAuth2RsOriginLanding,
|
||||||
Attribute::OAuth2RsOriginLanding,
|
Attribute::Image
|
||||||
Attribute::Image
|
));
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
AccessSrchResult::Ignore
|
AccessResult::Ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -214,9 +207,9 @@ fn search_oauth2_filter_entry(
|
||||||
fn search_sync_account_filter_entry(
|
fn search_sync_account_filter_entry(
|
||||||
ident: &Identity,
|
ident: &Identity,
|
||||||
entry: &Arc<EntrySealedCommitted>,
|
entry: &Arc<EntrySealedCommitted>,
|
||||||
) -> AccessSrchResult {
|
) -> AccessResult {
|
||||||
match &ident.origin {
|
match &ident.origin {
|
||||||
IdentType::Internal | IdentType::Synch(_) => AccessSrchResult::Ignore,
|
IdentType::Internal | IdentType::Synch(_) => AccessResult::Ignore,
|
||||||
IdentType::User(iuser) => {
|
IdentType::User(iuser) => {
|
||||||
// Is the user a synced object?
|
// Is the user a synced object?
|
||||||
let is_user_sync_account = iuser
|
let is_user_sync_account = iuser
|
||||||
|
@ -251,18 +244,16 @@ fn search_sync_account_filter_entry(
|
||||||
// We finally got here!
|
// We finally got here!
|
||||||
security_debug!(entry = ?entry.get_uuid(), ident = ?iuser.entry.get_uuid2rdn(), "ident is a synchronised account from this sync account");
|
security_debug!(entry = ?entry.get_uuid(), ident = ?iuser.entry.get_uuid2rdn(), "ident is a synchronised account from this sync account");
|
||||||
|
|
||||||
return AccessSrchResult::Allow {
|
return AccessResult::Allow(btreeset!(
|
||||||
attr: btreeset!(
|
Attribute::Class,
|
||||||
Attribute::Class,
|
Attribute::Uuid,
|
||||||
Attribute::Uuid,
|
Attribute::SyncCredentialPortal
|
||||||
Attribute::SyncCredentialPortal
|
));
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Fall through
|
// Fall through
|
||||||
AccessSrchResult::Ignore
|
AccessResult::Ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ use crate::value::{CredentialType, EXTRACT_VAL_DN};
|
||||||
use crate::valueset::uuid_to_proto_string;
|
use crate::valueset::uuid_to_proto_string;
|
||||||
use crate::valueset::ScimValueIntermediate;
|
use crate::valueset::ScimValueIntermediate;
|
||||||
use crate::valueset::*;
|
use crate::valueset::*;
|
||||||
use concread::arcache::{ARCacheBuilder, ARCacheReadTxn, ARCacheWriteTxn};
|
use concread::arcache::{ARCacheBuilder, ARCacheReadTxn};
|
||||||
use concread::cowcell::*;
|
use concread::cowcell::*;
|
||||||
use hashbrown::{HashMap, HashSet};
|
use hashbrown::{HashMap, HashSet};
|
||||||
use kanidm_proto::internal::{DomainInfo as ProtoDomainInfo, ImageValue, UiHint};
|
use kanidm_proto::internal::{DomainInfo as ProtoDomainInfo, ImageValue, UiHint};
|
||||||
|
@ -205,13 +205,6 @@ pub struct QueryServerWriteTransaction<'a> {
|
||||||
pub(super) changed_uuid: HashSet<Uuid>,
|
pub(super) changed_uuid: HashSet<Uuid>,
|
||||||
_db_ticket: SemaphorePermit<'a>,
|
_db_ticket: SemaphorePermit<'a>,
|
||||||
_write_ticket: SemaphorePermit<'a>,
|
_write_ticket: SemaphorePermit<'a>,
|
||||||
resolve_filter_cache_clear: bool,
|
|
||||||
resolve_filter_cache_write: ARCacheWriteTxn<
|
|
||||||
'a,
|
|
||||||
(IdentityId, Arc<Filter<FilterValid>>),
|
|
||||||
Arc<Filter<FilterValidResolved>>,
|
|
||||||
(),
|
|
||||||
>,
|
|
||||||
resolve_filter_cache: ARCacheReadTxn<
|
resolve_filter_cache: ARCacheReadTxn<
|
||||||
'a,
|
'a,
|
||||||
(IdentityId, Arc<Filter<FilterValid>>),
|
(IdentityId, Arc<Filter<FilterValid>>),
|
||||||
|
@ -267,7 +260,7 @@ pub trait QueryServerTransaction<'a> {
|
||||||
|
|
||||||
fn get_domain_image_value(&self) -> Option<ImageValue>;
|
fn get_domain_image_value(&self) -> Option<ImageValue>;
|
||||||
|
|
||||||
fn get_resolve_filter_cache(&mut self) -> Option<&mut ResolveFilterCacheReadTxn<'a>>;
|
fn get_resolve_filter_cache(&mut self) -> &mut ResolveFilterCacheReadTxn<'a>;
|
||||||
|
|
||||||
// Because of how borrowck in rust works, if we need to get two inner types we have to get them
|
// Because of how borrowck in rust works, if we need to get two inner types we have to get them
|
||||||
// in a single fn.
|
// in a single fn.
|
||||||
|
@ -276,7 +269,7 @@ pub trait QueryServerTransaction<'a> {
|
||||||
&mut self,
|
&mut self,
|
||||||
) -> (
|
) -> (
|
||||||
&mut Self::BackendTransactionType,
|
&mut Self::BackendTransactionType,
|
||||||
Option<&mut ResolveFilterCacheReadTxn<'a>>,
|
&mut ResolveFilterCacheReadTxn<'a>,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Conduct a search and apply access controls to yield a set of entries that
|
/// Conduct a search and apply access controls to yield a set of entries that
|
||||||
|
@ -333,15 +326,11 @@ pub trait QueryServerTransaction<'a> {
|
||||||
// NOTE: Filters are validated in event conversion.
|
// NOTE: Filters are validated in event conversion.
|
||||||
|
|
||||||
let (be_txn, resolve_filter_cache) = self.get_resolve_filter_cache_and_be_txn();
|
let (be_txn, resolve_filter_cache) = self.get_resolve_filter_cache_and_be_txn();
|
||||||
|
|
||||||
let idxmeta = be_txn.get_idxmeta_ref();
|
let idxmeta = be_txn.get_idxmeta_ref();
|
||||||
|
|
||||||
trace!(resolve_filter_cache = %resolve_filter_cache.is_some());
|
|
||||||
|
|
||||||
// Now resolve all references and indexes.
|
// Now resolve all references and indexes.
|
||||||
let vfr = se
|
let vfr = se
|
||||||
.filter
|
.filter
|
||||||
.resolve(&se.ident, Some(idxmeta), resolve_filter_cache)
|
.resolve(&se.ident, Some(idxmeta), Some(resolve_filter_cache))
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!(?e, "search filter resolve failure");
|
admin_error!(?e, "search filter resolve failure");
|
||||||
e
|
e
|
||||||
|
@ -377,7 +366,7 @@ pub trait QueryServerTransaction<'a> {
|
||||||
|
|
||||||
let vfr = ee
|
let vfr = ee
|
||||||
.filter
|
.filter
|
||||||
.resolve(&ee.ident, Some(idxmeta), resolve_filter_cache)
|
.resolve(&ee.ident, Some(idxmeta), Some(resolve_filter_cache))
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!(?e, "Failed to resolve filter");
|
admin_error!(?e, "Failed to resolve filter");
|
||||||
e
|
e
|
||||||
|
@ -1455,17 +1444,17 @@ impl<'a> QueryServerTransaction<'a> for QueryServerReadTransaction<'a> {
|
||||||
&self.key_providers
|
&self.key_providers
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_resolve_filter_cache(&mut self) -> Option<&mut ResolveFilterCacheReadTxn<'a>> {
|
fn get_resolve_filter_cache(&mut self) -> &mut ResolveFilterCacheReadTxn<'a> {
|
||||||
Some(&mut self.resolve_filter_cache)
|
&mut self.resolve_filter_cache
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_resolve_filter_cache_and_be_txn(
|
fn get_resolve_filter_cache_and_be_txn(
|
||||||
&mut self,
|
&mut self,
|
||||||
) -> (
|
) -> (
|
||||||
&mut BackendReadTransaction<'a>,
|
&mut BackendReadTransaction<'a>,
|
||||||
Option<&mut ResolveFilterCacheReadTxn<'a>>,
|
&mut ResolveFilterCacheReadTxn<'a>,
|
||||||
) {
|
) {
|
||||||
(&mut self.be_txn, Some(&mut self.resolve_filter_cache))
|
(&mut self.be_txn, &mut self.resolve_filter_cache)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pw_badlist(&self) -> &HashSet<String> {
|
fn pw_badlist(&self) -> &HashSet<String> {
|
||||||
|
@ -1689,25 +1678,17 @@ impl<'a> QueryServerTransaction<'a> for QueryServerWriteTransaction<'a> {
|
||||||
&self.key_providers
|
&self.key_providers
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_resolve_filter_cache(&mut self) -> Option<&mut ResolveFilterCacheReadTxn<'a>> {
|
fn get_resolve_filter_cache(&mut self) -> &mut ResolveFilterCacheReadTxn<'a> {
|
||||||
if self.resolve_filter_cache_clear || *self.phase < ServerPhase::SchemaReady {
|
&mut self.resolve_filter_cache
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(&mut self.resolve_filter_cache)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_resolve_filter_cache_and_be_txn(
|
fn get_resolve_filter_cache_and_be_txn(
|
||||||
&mut self,
|
&mut self,
|
||||||
) -> (
|
) -> (
|
||||||
&mut BackendWriteTransaction<'a>,
|
&mut BackendWriteTransaction<'a>,
|
||||||
Option<&mut ResolveFilterCacheReadTxn<'a>>,
|
&mut ResolveFilterCacheReadTxn<'a>,
|
||||||
) {
|
) {
|
||||||
if self.resolve_filter_cache_clear || *self.phase < ServerPhase::SchemaReady {
|
(&mut self.be_txn, &mut self.resolve_filter_cache)
|
||||||
(&mut self.be_txn, None)
|
|
||||||
} else {
|
|
||||||
(&mut self.be_txn, Some(&mut self.resolve_filter_cache))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pw_badlist(&self) -> &HashSet<String> {
|
fn pw_badlist(&self) -> &HashSet<String> {
|
||||||
|
@ -2022,8 +2003,6 @@ impl QueryServer {
|
||||||
_db_ticket: db_ticket,
|
_db_ticket: db_ticket,
|
||||||
_write_ticket: write_ticket,
|
_write_ticket: write_ticket,
|
||||||
resolve_filter_cache: self.resolve_filter_cache.read(),
|
resolve_filter_cache: self.resolve_filter_cache.read(),
|
||||||
resolve_filter_cache_clear: false,
|
|
||||||
resolve_filter_cache_write: self.resolve_filter_cache.write(),
|
|
||||||
dyngroup_cache: self.dyngroup_cache.write(),
|
dyngroup_cache: self.dyngroup_cache.write(),
|
||||||
key_providers: self.key_providers.write(),
|
key_providers: self.key_providers.write(),
|
||||||
})
|
})
|
||||||
|
@ -2173,13 +2152,16 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
||||||
))
|
))
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
// Since we reloaded the schema, we need to reload the filter cache since it
|
// TODO: Clear the filter resolve cache.
|
||||||
// may have incorrect or outdated information about indexes now.
|
// currently we can't do this because of the limits of types with arccache txns. The only
|
||||||
self.resolve_filter_cache_clear = true;
|
// thing this impacts is if something in indexed though, and the backend does handle
|
||||||
|
// incorrectly indexed items correctly.
|
||||||
|
|
||||||
// Trigger reloads on services that require post-schema reloads.
|
// Trigger reloads on services that require post-schema reloads.
|
||||||
// Mainly this is plugins.
|
// Mainly this is plugins.
|
||||||
DynGroup::reload(self)?;
|
if *self.phase >= ServerPhase::SchemaReady {
|
||||||
|
DynGroup::reload(self)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -2602,14 +2584,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
||||||
self.changed_flags.remove(ChangeFlag::OAUTH2)
|
self.changed_flags.remove(ChangeFlag::OAUTH2)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Indicate that we are about to re-bootstrap this server. You should ONLY
|
fn set_phase(&mut self, phase: ServerPhase) {
|
||||||
/// call this during a replication refresh!!!
|
|
||||||
pub(crate) fn set_phase_bootstrap(&mut self) {
|
|
||||||
*self.phase = ServerPhase::Bootstrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Raise the currently running server phase.
|
|
||||||
pub(crate) fn set_phase(&mut self, phase: ServerPhase) {
|
|
||||||
// Phase changes are one way
|
// Phase changes are one way
|
||||||
if phase > *self.phase {
|
if phase > *self.phase {
|
||||||
*self.phase = phase
|
*self.phase = phase
|
||||||
|
@ -2723,8 +2698,6 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
||||||
changed_flags,
|
changed_flags,
|
||||||
changed_uuid: _,
|
changed_uuid: _,
|
||||||
resolve_filter_cache: _,
|
resolve_filter_cache: _,
|
||||||
resolve_filter_cache_clear,
|
|
||||||
mut resolve_filter_cache_write,
|
|
||||||
} = self;
|
} = self;
|
||||||
debug_assert!(!committed);
|
debug_assert!(!committed);
|
||||||
|
|
||||||
|
@ -2738,12 +2711,6 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
||||||
be_txn.set_db_ts_max(cid.ts)?;
|
be_txn.set_db_ts_max(cid.ts)?;
|
||||||
cid.commit();
|
cid.commit();
|
||||||
|
|
||||||
// We don't care if this passes/fails, committing this is fine.
|
|
||||||
if resolve_filter_cache_clear {
|
|
||||||
resolve_filter_cache_write.clear();
|
|
||||||
}
|
|
||||||
resolve_filter_cache_write.commit();
|
|
||||||
|
|
||||||
// Point of no return - everything has been validated and reloaded.
|
// Point of no return - everything has been validated and reloaded.
|
||||||
//
|
//
|
||||||
// = Lets commit =
|
// = Lets commit =
|
||||||
|
|
|
@ -388,65 +388,6 @@ impl fmt::Display for SyntaxType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SyntaxType {
|
|
||||||
pub fn index_types(&self) -> &[IndexType] {
|
|
||||||
match self {
|
|
||||||
SyntaxType::Utf8String => &[IndexType::Equality, IndexType::Presence],
|
|
||||||
// Used by classes, needs to change ...
|
|
||||||
// Probably need an attrname syntax too
|
|
||||||
SyntaxType::Utf8StringInsensitive => &[IndexType::Equality, IndexType::Presence],
|
|
||||||
SyntaxType::Utf8StringIname => &[
|
|
||||||
IndexType::Equality,
|
|
||||||
IndexType::Presence,
|
|
||||||
IndexType::SubString,
|
|
||||||
],
|
|
||||||
SyntaxType::Uuid => &[IndexType::Equality, IndexType::Presence],
|
|
||||||
SyntaxType::Boolean => &[IndexType::Equality],
|
|
||||||
SyntaxType::SyntaxId => &[],
|
|
||||||
SyntaxType::IndexId => &[],
|
|
||||||
SyntaxType::ReferenceUuid => &[IndexType::Equality, IndexType::Presence],
|
|
||||||
SyntaxType::JsonFilter => &[],
|
|
||||||
SyntaxType::Credential => &[IndexType::Equality],
|
|
||||||
SyntaxType::SecretUtf8String => &[],
|
|
||||||
SyntaxType::SshKey => &[IndexType::Equality, IndexType::Presence],
|
|
||||||
SyntaxType::SecurityPrincipalName => &[
|
|
||||||
IndexType::Equality,
|
|
||||||
IndexType::Presence,
|
|
||||||
IndexType::SubString,
|
|
||||||
],
|
|
||||||
SyntaxType::Uint32 => &[IndexType::Equality, IndexType::Presence],
|
|
||||||
SyntaxType::Cid => &[],
|
|
||||||
SyntaxType::NsUniqueId => &[IndexType::Equality, IndexType::Presence],
|
|
||||||
SyntaxType::DateTime => &[],
|
|
||||||
SyntaxType::EmailAddress => &[IndexType::Equality, IndexType::SubString],
|
|
||||||
SyntaxType::Url => &[],
|
|
||||||
SyntaxType::OauthScope => &[],
|
|
||||||
SyntaxType::OauthScopeMap => &[IndexType::Equality],
|
|
||||||
SyntaxType::PrivateBinary => &[],
|
|
||||||
SyntaxType::IntentToken => &[IndexType::Equality],
|
|
||||||
SyntaxType::Passkey => &[IndexType::Equality],
|
|
||||||
SyntaxType::AttestedPasskey => &[IndexType::Equality],
|
|
||||||
SyntaxType::Session => &[IndexType::Equality],
|
|
||||||
SyntaxType::JwsKeyEs256 => &[],
|
|
||||||
SyntaxType::JwsKeyRs256 => &[],
|
|
||||||
SyntaxType::Oauth2Session => &[IndexType::Equality],
|
|
||||||
SyntaxType::UiHint => &[],
|
|
||||||
SyntaxType::TotpSecret => &[],
|
|
||||||
SyntaxType::ApiToken => &[IndexType::Equality],
|
|
||||||
SyntaxType::AuditLogString => &[],
|
|
||||||
SyntaxType::EcKeyPrivate => &[],
|
|
||||||
SyntaxType::Image => &[],
|
|
||||||
SyntaxType::CredentialType => &[],
|
|
||||||
SyntaxType::WebauthnAttestationCaList => &[],
|
|
||||||
SyntaxType::OauthClaimMap => &[IndexType::Equality],
|
|
||||||
SyntaxType::KeyInternal => &[],
|
|
||||||
SyntaxType::HexString => &[],
|
|
||||||
SyntaxType::Certificate => &[],
|
|
||||||
SyntaxType::ApplicationPassword => &[IndexType::Equality],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Hash,
|
Hash,
|
||||||
Debug,
|
Debug,
|
||||||
|
|
|
@ -14,73 +14,87 @@ const ALLOWED_ATTRIBUTES: &[&str] = &[
|
||||||
"role",
|
"role",
|
||||||
"output_mode",
|
"output_mode",
|
||||||
"log_level",
|
"log_level",
|
||||||
"ldap",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
#[derive(Default)]
|
fn parse_knobs(
|
||||||
struct Flags {
|
|
||||||
ldap: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_attributes(
|
|
||||||
args: &TokenStream,
|
|
||||||
input: &syn::ItemFn,
|
input: &syn::ItemFn,
|
||||||
) -> Result<(proc_macro2::TokenStream, Flags), syn::Error> {
|
server_config: &Punctuated<ExprAssign, syn::token::Comma>,
|
||||||
let args: Punctuated<ExprAssign, syn::token::Comma> =
|
) -> TokenStream {
|
||||||
Punctuated::<ExprAssign, Token![,]>::parse_terminated.parse(args.clone())?;
|
// If type mismatch occurs, the current rustc points to the last statement.
|
||||||
|
let (last_stmt_start_span, _last_stmt_end_span) = {
|
||||||
let args_are_allowed = args.pairs().all(|p| {
|
let mut last_stmt = input
|
||||||
ALLOWED_ATTRIBUTES.to_vec().contains(
|
.block
|
||||||
&p.value()
|
.stmts
|
||||||
.left
|
.last()
|
||||||
.span()
|
.map(ToTokens::into_token_stream)
|
||||||
.source_text()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.as_str(),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
if !args_are_allowed {
|
|
||||||
let msg = "Invalid test config attribute. The following are allowed";
|
|
||||||
return Err(syn::Error::new_spanned(
|
|
||||||
input.sig.fn_token,
|
|
||||||
format!("{}: {}", msg, ALLOWED_ATTRIBUTES.join(", ")),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut flags = Flags::default();
|
|
||||||
let mut field_modifications = quote! {};
|
|
||||||
|
|
||||||
args.pairs().for_each(|p| {
|
|
||||||
match p
|
|
||||||
.value()
|
|
||||||
.left
|
|
||||||
.span()
|
|
||||||
.source_text()
|
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.as_str()
|
.into_iter();
|
||||||
{
|
// `Span` on stable Rust has a limitation that only points to the first
|
||||||
"ldap" => {
|
// token, not the whole tokens. We can work around this limitation by
|
||||||
flags.ldap = true;
|
// using the first/last span of the tokens like
|
||||||
field_modifications.extend(quote! {
|
// `syn::Error::new_spanned` does.
|
||||||
ldapbindaddress: Some("on".to_string()),})
|
let start = last_stmt.next().map_or_else(Span::call_site, |t| t.span());
|
||||||
}
|
let end = last_stmt.last().map_or(start, |t| t.span());
|
||||||
_ => {
|
(start, end)
|
||||||
let field_name = p.value().left.to_token_stream(); // here we can use to_token_stream as we know we're iterating over ExprAssigns
|
};
|
||||||
let field_value = p.value().right.to_token_stream();
|
|
||||||
// This is printing out struct members.
|
// here we gather all the provided configuration in a struct like declaration
|
||||||
field_modifications.extend(quote! {
|
// By now we have already checked that the configurations provided belong to the allowed subset
|
||||||
#field_name: #field_value,})
|
let mut field_modifications = quote! {};
|
||||||
}
|
server_config.pairs().for_each(|p| {
|
||||||
}
|
let field_name = p.value().left.to_token_stream(); // here we can use to_token_stream as we know we're iterating over ExprAssigns
|
||||||
|
let field_value = p.value().right.to_token_stream();
|
||||||
|
field_modifications.extend(quote! {
|
||||||
|
#field_name: #field_value,})
|
||||||
});
|
});
|
||||||
|
|
||||||
let ts = quote!(kanidmd_core::config::Configuration {
|
// Setup the config filling the remaining fields with the default values
|
||||||
|
let default_config_struct = quote!(kanidmd_core::config::Configuration {
|
||||||
#field_modifications
|
#field_modifications
|
||||||
..kanidmd_core::config::Configuration::new_for_test()
|
..kanidmd_core::config::Configuration::new_for_test()
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok((ts, flags))
|
let rt = quote_spanned! {last_stmt_start_span=>
|
||||||
|
tokio::runtime::Builder::new_current_thread()
|
||||||
|
};
|
||||||
|
|
||||||
|
let header = quote! {
|
||||||
|
#[::core::prelude::v1::test]
|
||||||
|
};
|
||||||
|
|
||||||
|
let fn_name = &input.sig.ident;
|
||||||
|
let test_driver = Ident::new(&format!("tk_{}", fn_name), input.sig.span());
|
||||||
|
|
||||||
|
// Effectively we are just injecting a real test function around this which we will
|
||||||
|
// call.
|
||||||
|
|
||||||
|
let result = quote! {
|
||||||
|
#input
|
||||||
|
|
||||||
|
#header
|
||||||
|
fn #test_driver() {
|
||||||
|
let body = async {
|
||||||
|
let (rsclient, mut core_handle) = kanidmd_testkit::setup_async_test(#default_config_struct).await;
|
||||||
|
#fn_name(rsclient).await;
|
||||||
|
core_handle.shutdown().await;
|
||||||
|
};
|
||||||
|
#[allow(clippy::expect_used, clippy::diverging_sub_expression)]
|
||||||
|
{
|
||||||
|
return #rt
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.expect("Failed building the Runtime")
|
||||||
|
.block_on(body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
result.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream {
|
||||||
|
tokens.extend(TokenStream::from(error.into_compile_error()));
|
||||||
|
tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
pub(crate) fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
|
@ -101,80 +115,31 @@ pub(crate) fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
let msg = "the `async` keyword is missing from the function declaration";
|
let msg = "the `async` keyword is missing from the function declaration";
|
||||||
return token_stream_with_error(item, syn::Error::new_spanned(input.sig.fn_token, msg));
|
return token_stream_with_error(item, syn::Error::new_spanned(input.sig.fn_token, msg));
|
||||||
}
|
}
|
||||||
|
let args: Punctuated<ExprAssign, syn::token::Comma> =
|
||||||
// If type mismatch occurs, the current rustc points to the last statement.
|
match Punctuated::<ExprAssign, Token![,]>::parse_terminated.parse(args.clone()) {
|
||||||
let (last_stmt_start_span, _last_stmt_end_span) = {
|
Ok(it) => it,
|
||||||
let mut last_stmt = input
|
Err(e) => return token_stream_with_error(args, e),
|
||||||
.block
|
};
|
||||||
.stmts
|
let args_are_allowed = args.pairs().all(|p| {
|
||||||
.last()
|
ALLOWED_ATTRIBUTES.to_vec().contains(
|
||||||
.map(ToTokens::into_token_stream)
|
&p.value()
|
||||||
.unwrap_or_default()
|
.left
|
||||||
.into_iter();
|
.span()
|
||||||
// `Span` on stable Rust has a limitation that only points to the first
|
.source_text()
|
||||||
// token, not the whole tokens. We can work around this limitation by
|
.unwrap_or_default()
|
||||||
// using the first/last span of the tokens like
|
.as_str(),
|
||||||
// `syn::Error::new_spanned` does.
|
)
|
||||||
let start = last_stmt.next().map_or_else(Span::call_site, |t| t.span());
|
});
|
||||||
let end = last_stmt.last().map_or(start, |t| t.span());
|
if !args_are_allowed {
|
||||||
(start, end)
|
let msg =
|
||||||
};
|
"Currently only a subset of all the server configs can be set. Here is the full list";
|
||||||
|
return token_stream_with_error(
|
||||||
// Setup the config filling the remaining fields with the default values
|
item,
|
||||||
let (default_config_struct, flags) = match parse_attributes(&args, &input) {
|
syn::Error::new_spanned(
|
||||||
Ok(dc) => dc,
|
input.sig.fn_token,
|
||||||
Err(e) => return token_stream_with_error(args, e),
|
format!("{}: {}", msg, ALLOWED_ATTRIBUTES.join(", ")),
|
||||||
};
|
),
|
||||||
|
);
|
||||||
let rt = quote_spanned! {last_stmt_start_span=>
|
}
|
||||||
tokio::runtime::Builder::new_current_thread()
|
parse_knobs(&input, &args)
|
||||||
};
|
|
||||||
|
|
||||||
let header = quote! {
|
|
||||||
#[::core::prelude::v1::test]
|
|
||||||
};
|
|
||||||
|
|
||||||
let test_fn_args = if flags.ldap {
|
|
||||||
quote! {
|
|
||||||
&test_env
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
quote! {
|
|
||||||
&test_env.rsclient
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let test_fn = &input.sig.ident;
|
|
||||||
let test_driver = Ident::new(&format!("tk_{}", test_fn), input.sig.span());
|
|
||||||
|
|
||||||
// Effectively we are just injecting a real test function around this which we will
|
|
||||||
// call.
|
|
||||||
let result = quote! {
|
|
||||||
#input
|
|
||||||
|
|
||||||
#header
|
|
||||||
fn #test_driver() {
|
|
||||||
let body = async {
|
|
||||||
let mut test_env = kanidmd_testkit::setup_async_test(#default_config_struct).await;
|
|
||||||
|
|
||||||
#test_fn(#test_fn_args).await;
|
|
||||||
test_env.core_handle.shutdown().await;
|
|
||||||
};
|
|
||||||
#[allow(clippy::expect_used, clippy::diverging_sub_expression)]
|
|
||||||
{
|
|
||||||
return #rt
|
|
||||||
.enable_all()
|
|
||||||
.build()
|
|
||||||
.expect("Failed building the Runtime")
|
|
||||||
.block_on(body);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
result.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream {
|
|
||||||
tokens.extend(TokenStream::from(error.into_compile_error()));
|
|
||||||
tokens
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,9 +51,8 @@ kanidm_build_profiles = { workspace = true }
|
||||||
compact_jwt = { workspace = true }
|
compact_jwt = { workspace = true }
|
||||||
escargot = "0.5.13"
|
escargot = "0.5.13"
|
||||||
# used for webdriver testing
|
# used for webdriver testing
|
||||||
fantoccini = { version = "0.21.5" }
|
fantoccini = { version = "0.21.4" }
|
||||||
futures = { workspace = true }
|
futures = { workspace = true }
|
||||||
ldap3_client = { workspace = true }
|
|
||||||
oauth2_ext = { workspace = true, default-features = false, features = [
|
oauth2_ext = { workspace = true, default-features = false, features = [
|
||||||
"reqwest",
|
"reqwest",
|
||||||
] }
|
] }
|
||||||
|
@ -64,7 +63,7 @@ tokio-openssl = { workspace = true }
|
||||||
kanidm_lib_crypto = { workspace = true }
|
kanidm_lib_crypto = { workspace = true }
|
||||||
uuid = { workspace = true }
|
uuid = { workspace = true }
|
||||||
webauthn-authenticator-rs = { workspace = true }
|
webauthn-authenticator-rs = { workspace = true }
|
||||||
jsonschema = "0.29.1"
|
jsonschema = "0.29.0"
|
||||||
|
|
||||||
[package.metadata.cargo-machete]
|
[package.metadata.cargo-machete]
|
||||||
ignored = ["escargot", "futures", "kanidm_build_profiles"]
|
ignored = ["escargot", "futures", "kanidm_build_profiles"]
|
||||||
|
|
|
@ -10,16 +10,16 @@
|
||||||
#![deny(clippy::needless_pass_by_value)]
|
#![deny(clippy::needless_pass_by_value)]
|
||||||
#![deny(clippy::trivially_copy_pass_by_ref)]
|
#![deny(clippy::trivially_copy_pass_by_ref)]
|
||||||
|
|
||||||
|
use std::net::TcpStream;
|
||||||
|
use std::sync::atomic::{AtomicU16, Ordering};
|
||||||
|
|
||||||
use kanidm_client::{KanidmClient, KanidmClientBuilder};
|
use kanidm_client::{KanidmClient, KanidmClientBuilder};
|
||||||
|
|
||||||
use kanidm_proto::internal::{Filter, Modify, ModifyList};
|
use kanidm_proto::internal::{Filter, Modify, ModifyList};
|
||||||
use kanidmd_core::config::{Configuration, IntegrationTestConfig};
|
use kanidmd_core::config::{Configuration, IntegrationTestConfig};
|
||||||
use kanidmd_core::{create_server_core, CoreHandle};
|
use kanidmd_core::{create_server_core, CoreHandle};
|
||||||
use kanidmd_lib::prelude::{Attribute, NAME_SYSTEM_ADMINS};
|
use kanidmd_lib::prelude::{Attribute, NAME_SYSTEM_ADMINS};
|
||||||
use std::net::TcpStream;
|
|
||||||
use std::sync::atomic::{AtomicU16, Ordering};
|
|
||||||
use tokio::task;
|
use tokio::task;
|
||||||
use tracing::error;
|
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
pub const ADMIN_TEST_USER: &str = "admin";
|
pub const ADMIN_TEST_USER: &str = "admin";
|
||||||
pub const ADMIN_TEST_PASSWORD: &str = "integration test admin password";
|
pub const ADMIN_TEST_PASSWORD: &str = "integration test admin password";
|
||||||
|
@ -46,9 +46,14 @@ pub fn is_free_port(port: u16) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test external behaviours of the service.
|
// Test external behaviours of the service.
|
||||||
fn port_loop() -> u16 {
|
|
||||||
|
// allowed because the use of this function is behind a test gate
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn setup_async_test(mut config: Configuration) -> (KanidmClient, CoreHandle) {
|
||||||
|
sketching::test_init();
|
||||||
|
|
||||||
let mut counter = 0;
|
let mut counter = 0;
|
||||||
loop {
|
let port = loop {
|
||||||
let possible_port = PORT_ALLOC.fetch_add(1, Ordering::SeqCst);
|
let possible_port = PORT_ALLOC.fetch_add(1, Ordering::SeqCst);
|
||||||
if is_free_port(possible_port) {
|
if is_free_port(possible_port) {
|
||||||
break possible_port;
|
break possible_port;
|
||||||
|
@ -59,21 +64,7 @@ fn port_loop() -> u16 {
|
||||||
tracing::error!("Unable to allocate port!");
|
tracing::error!("Unable to allocate port!");
|
||||||
panic!();
|
panic!();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
pub struct AsyncTestEnvironment {
|
|
||||||
pub rsclient: KanidmClient,
|
|
||||||
pub core_handle: CoreHandle,
|
|
||||||
pub ldap_url: Option<Url>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// allowed because the use of this function is behind a test gate
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub async fn setup_async_test(mut config: Configuration) -> AsyncTestEnvironment {
|
|
||||||
sketching::test_init();
|
|
||||||
|
|
||||||
let port = port_loop();
|
|
||||||
|
|
||||||
let int_config = Box::new(IntegrationTestConfig {
|
let int_config = Box::new(IntegrationTestConfig {
|
||||||
admin_user: ADMIN_TEST_USER.to_string(),
|
admin_user: ADMIN_TEST_USER.to_string(),
|
||||||
|
@ -84,16 +75,6 @@ pub async fn setup_async_test(mut config: Configuration) -> AsyncTestEnvironment
|
||||||
|
|
||||||
let addr = format!("http://localhost:{}", port);
|
let addr = format!("http://localhost:{}", port);
|
||||||
|
|
||||||
let ldap_url = if config.ldapbindaddress.is_some() {
|
|
||||||
let ldapport = port_loop();
|
|
||||||
config.ldapbindaddress = Some(format!("127.0.0.1:{}", ldapport));
|
|
||||||
Url::parse(&format!("ldap://127.0.0.1:{}", ldapport))
|
|
||||||
.inspect_err(|err| error!(?err, "ldap address setup"))
|
|
||||||
.ok()
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
// Setup the address and origin..
|
// Setup the address and origin..
|
||||||
config.address = format!("127.0.0.1:{}", port);
|
config.address = format!("127.0.0.1:{}", port);
|
||||||
config.integration_test_config = Some(int_config);
|
config.integration_test_config = Some(int_config);
|
||||||
|
@ -121,11 +102,7 @@ pub async fn setup_async_test(mut config: Configuration) -> AsyncTestEnvironment
|
||||||
|
|
||||||
tracing::info!("Testkit server setup complete - {}", addr);
|
tracing::info!("Testkit server setup complete - {}", addr);
|
||||||
|
|
||||||
AsyncTestEnvironment {
|
(rsclient, core_handle)
|
||||||
rsclient,
|
|
||||||
core_handle,
|
|
||||||
ldap_url,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// creates a user (username: `id`) and puts them into a group, creating it if need be.
|
/// creates a user (username: `id`) and puts them into a group, creating it if need be.
|
||||||
|
|
|
@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn check_that_the_swagger_api_loads(rsclient: &kanidm_client::KanidmClient) {
|
async fn check_that_the_swagger_api_loads(rsclient: kanidm_client::KanidmClient) {
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
struct OpenAPIResponse {
|
struct OpenAPIResponse {
|
||||||
pub openapi: String,
|
pub openapi: String,
|
||||||
|
|
|
@ -3,7 +3,7 @@ use kanidm_proto::constants::ATTR_DOMAIN_DISPLAY_NAME;
|
||||||
use kanidmd_testkit::{ADMIN_TEST_PASSWORD, ADMIN_TEST_USER};
|
use kanidmd_testkit::{ADMIN_TEST_PASSWORD, ADMIN_TEST_USER};
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_idm_set_ldap_allow_unix_password_bind(rsclient: &KanidmClient) {
|
async fn test_idm_set_ldap_allow_unix_password_bind(rsclient: KanidmClient) {
|
||||||
rsclient
|
rsclient
|
||||||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||||
.await
|
.await
|
||||||
|
@ -13,9 +13,8 @@ async fn test_idm_set_ldap_allow_unix_password_bind(rsclient: &KanidmClient) {
|
||||||
.await
|
.await
|
||||||
.expect("Failed to set LDAP allow unix password bind to true");
|
.expect("Failed to set LDAP allow unix password bind to true");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_idm_domain_set_ldap_basedn(rsclient: &KanidmClient) {
|
async fn test_idm_domain_set_ldap_basedn(rsclient: KanidmClient) {
|
||||||
rsclient
|
rsclient
|
||||||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||||
.await
|
.await
|
||||||
|
@ -28,7 +27,7 @@ async fn test_idm_domain_set_ldap_basedn(rsclient: &KanidmClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_idm_domain_set_ldap_max_queryable_attrs(rsclient: &KanidmClient) {
|
async fn test_idm_domain_set_ldap_max_queryable_attrs(rsclient: KanidmClient) {
|
||||||
rsclient
|
rsclient
|
||||||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||||
.await
|
.await
|
||||||
|
@ -41,7 +40,7 @@ async fn test_idm_domain_set_ldap_max_queryable_attrs(rsclient: &KanidmClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_idm_domain_set_display_name(rsclient: &KanidmClient) {
|
async fn test_idm_domain_set_display_name(rsclient: KanidmClient) {
|
||||||
rsclient
|
rsclient
|
||||||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -4,7 +4,7 @@ use kanidmd_testkit::{create_user, ADMIN_TEST_PASSWORD, ADMIN_TEST_USER};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_v1_group_id_patch(rsclient: &KanidmClient) {
|
async fn test_v1_group_id_patch(rsclient: KanidmClient) {
|
||||||
let res = rsclient
|
let res = rsclient
|
||||||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||||
.await;
|
.await;
|
||||||
|
@ -25,7 +25,7 @@ async fn test_v1_group_id_patch(rsclient: &KanidmClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_v1_group_id_attr_post(rsclient: &KanidmClient) {
|
async fn test_v1_group_id_attr_post(rsclient: KanidmClient) {
|
||||||
let res = rsclient
|
let res = rsclient
|
||||||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||||
.await;
|
.await;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use kanidm_client::{http::header, KanidmClient};
|
use kanidm_client::{http::header, KanidmClient};
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_https_manifest(rsclient: &KanidmClient) {
|
async fn test_https_manifest(rsclient: KanidmClient) {
|
||||||
// We need to do manual reqwests here.
|
// We need to do manual reqwests here.
|
||||||
let client = rsclient.client();
|
let client = rsclient.client();
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ const DEFAULT_IP_ADDRESS: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
|
||||||
// *test where we don't trust the x-forwarded-for header
|
// *test where we don't trust the x-forwarded-for header
|
||||||
|
|
||||||
#[kanidmd_testkit::test(trust_x_forward_for = false)]
|
#[kanidmd_testkit::test(trust_x_forward_for = false)]
|
||||||
async fn dont_trust_xff_send_header(rsclient: &KanidmClient) {
|
async fn dont_trust_xff_send_header(rsclient: KanidmClient) {
|
||||||
let client = rsclient.client();
|
let client = rsclient.client();
|
||||||
|
|
||||||
let res = client
|
let res = client
|
||||||
|
@ -32,7 +32,7 @@ async fn dont_trust_xff_send_header(rsclient: &KanidmClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test(trust_x_forward_for = false)]
|
#[kanidmd_testkit::test(trust_x_forward_for = false)]
|
||||||
async fn dont_trust_xff_dont_send_header(rsclient: &KanidmClient) {
|
async fn dont_trust_xff_dont_send_header(rsclient: KanidmClient) {
|
||||||
let client = rsclient.client();
|
let client = rsclient.client();
|
||||||
|
|
||||||
let res = client
|
let res = client
|
||||||
|
@ -58,7 +58,7 @@ async fn dont_trust_xff_dont_send_header(rsclient: &KanidmClient) {
|
||||||
// *test where we trust the x-forwarded-for header
|
// *test where we trust the x-forwarded-for header
|
||||||
|
|
||||||
#[kanidmd_testkit::test(trust_x_forward_for = true)]
|
#[kanidmd_testkit::test(trust_x_forward_for = true)]
|
||||||
async fn trust_xff_send_invalid_header_single_value(rsclient: &KanidmClient) {
|
async fn trust_xff_send_invalid_header_single_value(rsclient: KanidmClient) {
|
||||||
let client = rsclient.client();
|
let client = rsclient.client();
|
||||||
|
|
||||||
let res = client
|
let res = client
|
||||||
|
@ -78,7 +78,7 @@ async fn trust_xff_send_invalid_header_single_value(rsclient: &KanidmClient) {
|
||||||
// with a valid leftmost address and an invalid address later in the list. Right now it wouldn't work.
|
// with a valid leftmost address and an invalid address later in the list. Right now it wouldn't work.
|
||||||
//
|
//
|
||||||
#[kanidmd_testkit::test(trust_x_forward_for = true)]
|
#[kanidmd_testkit::test(trust_x_forward_for = true)]
|
||||||
async fn trust_xff_send_invalid_header_multiple_values(rsclient: &KanidmClient) {
|
async fn trust_xff_send_invalid_header_multiple_values(rsclient: KanidmClient) {
|
||||||
let client = rsclient.client();
|
let client = rsclient.client();
|
||||||
|
|
||||||
let res = client
|
let res = client
|
||||||
|
@ -95,7 +95,7 @@ async fn trust_xff_send_invalid_header_multiple_values(rsclient: &KanidmClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test(trust_x_forward_for = true)]
|
#[kanidmd_testkit::test(trust_x_forward_for = true)]
|
||||||
async fn trust_xff_send_valid_header_single_ipv4_address(rsclient: &KanidmClient) {
|
async fn trust_xff_send_valid_header_single_ipv4_address(rsclient: KanidmClient) {
|
||||||
let ip_addr = "2001:db8:85a3:8d3:1319:8a2e:370:7348";
|
let ip_addr = "2001:db8:85a3:8d3:1319:8a2e:370:7348";
|
||||||
|
|
||||||
let client = rsclient.client();
|
let client = rsclient.client();
|
||||||
|
@ -115,7 +115,7 @@ async fn trust_xff_send_valid_header_single_ipv4_address(rsclient: &KanidmClient
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test(trust_x_forward_for = true)]
|
#[kanidmd_testkit::test(trust_x_forward_for = true)]
|
||||||
async fn trust_xff_send_valid_header_single_ipv6_address(rsclient: &KanidmClient) {
|
async fn trust_xff_send_valid_header_single_ipv6_address(rsclient: KanidmClient) {
|
||||||
let ip_addr = "203.0.113.195";
|
let ip_addr = "203.0.113.195";
|
||||||
|
|
||||||
let client = rsclient.client();
|
let client = rsclient.client();
|
||||||
|
@ -135,7 +135,7 @@ async fn trust_xff_send_valid_header_single_ipv6_address(rsclient: &KanidmClient
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test(trust_x_forward_for = true)]
|
#[kanidmd_testkit::test(trust_x_forward_for = true)]
|
||||||
async fn trust_xff_send_valid_header_multiple_address(rsclient: &KanidmClient) {
|
async fn trust_xff_send_valid_header_multiple_address(rsclient: KanidmClient) {
|
||||||
let first_ip_addr = "203.0.113.195, 2001:db8:85a3:8d3:1319:8a2e:370:7348";
|
let first_ip_addr = "203.0.113.195, 2001:db8:85a3:8d3:1319:8a2e:370:7348";
|
||||||
|
|
||||||
let client = rsclient.client();
|
let client = rsclient.client();
|
||||||
|
@ -176,7 +176,7 @@ async fn trust_xff_send_valid_header_multiple_address(rsclient: &KanidmClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test(trust_x_forward_for = true)]
|
#[kanidmd_testkit::test(trust_x_forward_for = true)]
|
||||||
async fn trust_xff_dont_send_header(rsclient: &KanidmClient) {
|
async fn trust_xff_dont_send_header(rsclient: KanidmClient) {
|
||||||
let client = rsclient.client();
|
let client = rsclient.client();
|
||||||
|
|
||||||
let res = client
|
let res = client
|
||||||
|
|
|
@ -2,7 +2,7 @@ use kanidm_client::http::header;
|
||||||
use kanidm_client::KanidmClient;
|
use kanidm_client::KanidmClient;
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_https_middleware_headers(rsclient: &KanidmClient) {
|
async fn test_https_middleware_headers(rsclient: KanidmClient) {
|
||||||
// We need to do manual reqwests here.
|
// We need to do manual reqwests here.
|
||||||
let client = rsclient.client();
|
let client = rsclient.client();
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ static USER_B_NAME: &str = "valid_user_b";
|
||||||
// These tests check that invalid requests return the expected error
|
// These tests check that invalid requests return the expected error
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_not_authenticated(rsclient: &KanidmClient) {
|
async fn test_not_authenticated(rsclient: KanidmClient) {
|
||||||
// basically here we try a bit of all the possible combinations while unauthenticated to check it's not working
|
// basically here we try a bit of all the possible combinations while unauthenticated to check it's not working
|
||||||
setup_server(&rsclient).await;
|
setup_server(&rsclient).await;
|
||||||
create_user(&rsclient, USER_A_NAME).await;
|
create_user(&rsclient, USER_A_NAME).await;
|
||||||
|
@ -46,7 +46,7 @@ async fn test_not_authenticated(rsclient: &KanidmClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_non_existing_user_id(rsclient: &KanidmClient) {
|
async fn test_non_existing_user_id(rsclient: KanidmClient) {
|
||||||
setup_server(&rsclient).await;
|
setup_server(&rsclient).await;
|
||||||
create_user(&rsclient, USER_A_NAME).await;
|
create_user(&rsclient, USER_A_NAME).await;
|
||||||
create_user(&rsclient, USER_B_NAME).await;
|
create_user(&rsclient, USER_B_NAME).await;
|
||||||
|
@ -86,7 +86,7 @@ async fn test_non_existing_user_id(rsclient: &KanidmClient) {
|
||||||
// error cases have already been tested in the previous section!
|
// error cases have already been tested in the previous section!
|
||||||
// Each tests is named like `test_{api input}_response_{expected api output}_or_{expected api output}`
|
// Each tests is named like `test_{api input}_response_{expected api output}_or_{expected api output}`
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_start_response_identity_verification_available(rsclient: &KanidmClient) {
|
async fn test_start_response_identity_verification_available(rsclient: KanidmClient) {
|
||||||
setup_server(&rsclient).await;
|
setup_server(&rsclient).await;
|
||||||
create_user(&rsclient, USER_A_NAME).await;
|
create_user(&rsclient, USER_A_NAME).await;
|
||||||
login_with_user(&rsclient, USER_A_NAME).await;
|
login_with_user(&rsclient, USER_A_NAME).await;
|
||||||
|
@ -105,7 +105,7 @@ async fn test_start_response_identity_verification_available(rsclient: &KanidmCl
|
||||||
// this function tests both possible POSITIVE outcomes if we start from
|
// this function tests both possible POSITIVE outcomes if we start from
|
||||||
// `Start`, that is WaitForCode or ProvideCode
|
// `Start`, that is WaitForCode or ProvideCode
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_start_response_wait_for_code_or_provide_code(rsclient: &KanidmClient) {
|
async fn test_start_response_wait_for_code_or_provide_code(rsclient: KanidmClient) {
|
||||||
setup_server(&rsclient).await;
|
setup_server(&rsclient).await;
|
||||||
let user_a_uuid = create_user(&rsclient, USER_A_NAME).await;
|
let user_a_uuid = create_user(&rsclient, USER_A_NAME).await;
|
||||||
let user_b_uuid = create_user(&rsclient, USER_B_NAME).await;
|
let user_b_uuid = create_user(&rsclient, USER_B_NAME).await;
|
||||||
|
@ -129,7 +129,7 @@ async fn test_start_response_wait_for_code_or_provide_code(rsclient: &KanidmClie
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_provide_code_response_code_failure_or_provide_code(rsclient: &KanidmClient) {
|
async fn test_provide_code_response_code_failure_or_provide_code(rsclient: KanidmClient) {
|
||||||
setup_server(&rsclient).await;
|
setup_server(&rsclient).await;
|
||||||
let user_a_uuid = create_user(&rsclient, USER_A_NAME).await;
|
let user_a_uuid = create_user(&rsclient, USER_A_NAME).await;
|
||||||
let user_b_uuid = create_user(&rsclient, USER_B_NAME).await;
|
let user_b_uuid = create_user(&rsclient, USER_B_NAME).await;
|
||||||
|
@ -157,7 +157,7 @@ async fn test_provide_code_response_code_failure_or_provide_code(rsclient: &Kani
|
||||||
|
|
||||||
// here we actually test the full idm flow by duplicating the server
|
// here we actually test the full idm flow by duplicating the server
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_full_identification_flow(rsclient: &KanidmClient) {
|
async fn test_full_identification_flow(rsclient: KanidmClient) {
|
||||||
setup_server(&rsclient).await;
|
setup_server(&rsclient).await;
|
||||||
let user_a_uuid = create_user(&rsclient, USER_A_NAME).await;
|
let user_a_uuid = create_user(&rsclient, USER_A_NAME).await;
|
||||||
let user_b_uuid = create_user(&rsclient, USER_B_NAME).await;
|
let user_b_uuid = create_user(&rsclient, USER_B_NAME).await;
|
||||||
|
@ -175,12 +175,12 @@ async fn test_full_identification_flow(rsclient: &KanidmClient) {
|
||||||
(
|
(
|
||||||
valid_user_a_client,
|
valid_user_a_client,
|
||||||
USER_A_NAME,
|
USER_A_NAME,
|
||||||
&valid_user_b_client,
|
valid_user_b_client,
|
||||||
USER_B_NAME,
|
USER_B_NAME,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
&valid_user_b_client,
|
valid_user_b_client,
|
||||||
USER_B_NAME,
|
USER_B_NAME,
|
||||||
valid_user_a_client,
|
valid_user_a_client,
|
||||||
USER_A_NAME,
|
USER_A_NAME,
|
||||||
|
|
|
@ -66,7 +66,7 @@ async fn get_webdriver_client() -> fantoccini::Client {
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
#[cfg(feature = "webdriver")]
|
#[cfg(feature = "webdriver")]
|
||||||
async fn test_webdriver_user_login(rsclient: &KanidmClient) {
|
async fn test_webdriver_user_login(rsclient: kanidm_client::KanidmClient) {
|
||||||
if !cfg!(feature = "webdriver") {
|
if !cfg!(feature = "webdriver") {
|
||||||
println!("Skipping test as webdriver feature is not enabled!");
|
println!("Skipping test as webdriver feature is not enabled!");
|
||||||
return;
|
return;
|
||||||
|
@ -206,7 +206,7 @@ async fn test_webdriver_user_login(rsclient: &KanidmClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_domain_reset_token_key(rsclient: &KanidmClient) {
|
async fn test_domain_reset_token_key(rsclient: KanidmClient) {
|
||||||
login_put_admin_idm_admins(&rsclient).await;
|
login_put_admin_idm_admins(&rsclient).await;
|
||||||
|
|
||||||
let token = rsclient.get_token().await.expect("No bearer token present");
|
let token = rsclient.get_token().await.expect("No bearer token present");
|
||||||
|
@ -219,7 +219,7 @@ async fn test_domain_reset_token_key(rsclient: &KanidmClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_idm_domain_set_ldap_basedn(rsclient: &KanidmClient) {
|
async fn test_idm_domain_set_ldap_basedn(rsclient: KanidmClient) {
|
||||||
login_put_admin_idm_admins(&rsclient).await;
|
login_put_admin_idm_admins(&rsclient).await;
|
||||||
assert!(rsclient
|
assert!(rsclient
|
||||||
.idm_domain_set_ldap_basedn("dc=krabsarekool,dc=example,dc=com")
|
.idm_domain_set_ldap_basedn("dc=krabsarekool,dc=example,dc=com")
|
||||||
|
@ -232,7 +232,7 @@ async fn test_idm_domain_set_ldap_basedn(rsclient: &KanidmClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_idm_domain_set_ldap_max_queryable_attrs(rsclient: &KanidmClient) {
|
async fn test_idm_domain_set_ldap_max_queryable_attrs(rsclient: KanidmClient) {
|
||||||
login_put_admin_idm_admins(&rsclient).await;
|
login_put_admin_idm_admins(&rsclient).await;
|
||||||
assert!(rsclient
|
assert!(rsclient
|
||||||
.idm_domain_set_ldap_max_queryable_attrs(20)
|
.idm_domain_set_ldap_max_queryable_attrs(20)
|
||||||
|
@ -246,7 +246,7 @@ async fn test_idm_domain_set_ldap_max_queryable_attrs(rsclient: &KanidmClient) {
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
/// Checks that a built-in group idm_all_persons has the "builtin" class as expected.
|
/// Checks that a built-in group idm_all_persons has the "builtin" class as expected.
|
||||||
async fn test_all_persons_has_builtin_class(rsclient: &KanidmClient) {
|
async fn test_all_persons_has_builtin_class(rsclient: KanidmClient) {
|
||||||
login_put_admin_idm_admins(&rsclient).await;
|
login_put_admin_idm_admins(&rsclient).await;
|
||||||
let res = rsclient
|
let res = rsclient
|
||||||
.idm_group_get("idm_all_persons")
|
.idm_group_get("idm_all_persons")
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
use kanidmd_testkit::AsyncTestEnvironment;
|
|
||||||
use ldap3_client::LdapClientBuilder;
|
|
||||||
|
|
||||||
#[kanidmd_testkit::test(ldap = true)]
|
|
||||||
async fn test_ldap_basic_unix_bind(test_env: &AsyncTestEnvironment) {
|
|
||||||
let ldap_url = test_env.ldap_url.as_ref().unwrap();
|
|
||||||
|
|
||||||
let mut ldap_client = LdapClientBuilder::new(ldap_url).build().await.unwrap();
|
|
||||||
|
|
||||||
// Bind as anonymous
|
|
||||||
ldap_client.bind("".into(), "".into()).await.unwrap();
|
|
||||||
|
|
||||||
let whoami = ldap_client.whoami().await.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(whoami, Some("u: anonymous@localhost".to_string()));
|
|
||||||
}
|
|
|
@ -6,7 +6,6 @@ mod https_extractors;
|
||||||
mod https_middleware;
|
mod https_middleware;
|
||||||
mod identity_verification_tests;
|
mod identity_verification_tests;
|
||||||
mod integration;
|
mod integration;
|
||||||
mod ldap_basic;
|
|
||||||
mod mtls_test;
|
mod mtls_test;
|
||||||
mod oauth2_test;
|
mod oauth2_test;
|
||||||
mod person;
|
mod person;
|
||||||
|
|
|
@ -40,7 +40,7 @@ use kanidmd_testkit::{
|
||||||
/// If `true`, use the `code` passed in the callback URI's fragment, and
|
/// If `true`, use the `code` passed in the callback URI's fragment, and
|
||||||
/// require the query parameter to be empty.
|
/// require the query parameter to be empty.
|
||||||
async fn test_oauth2_openid_basic_flow_impl(
|
async fn test_oauth2_openid_basic_flow_impl(
|
||||||
rsclient: &KanidmClient,
|
rsclient: KanidmClient,
|
||||||
response_mode: Option<&str>,
|
response_mode: Option<&str>,
|
||||||
response_in_fragment: bool,
|
response_in_fragment: bool,
|
||||||
) {
|
) {
|
||||||
|
@ -535,7 +535,7 @@ async fn test_oauth2_openid_basic_flow_impl(
|
||||||
///
|
///
|
||||||
/// The response should be returned as a query parameter.
|
/// The response should be returned as a query parameter.
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_oauth2_openid_basic_flow_mode_unset(rsclient: &KanidmClient) {
|
async fn test_oauth2_openid_basic_flow_mode_unset(rsclient: KanidmClient) {
|
||||||
test_oauth2_openid_basic_flow_impl(rsclient, None, false).await;
|
test_oauth2_openid_basic_flow_impl(rsclient, None, false).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -544,7 +544,7 @@ async fn test_oauth2_openid_basic_flow_mode_unset(rsclient: &KanidmClient) {
|
||||||
///
|
///
|
||||||
/// The response should be returned as a query parameter.
|
/// The response should be returned as a query parameter.
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_oauth2_openid_basic_flow_mode_query(rsclient: &KanidmClient) {
|
async fn test_oauth2_openid_basic_flow_mode_query(rsclient: KanidmClient) {
|
||||||
test_oauth2_openid_basic_flow_impl(rsclient, Some("query"), false).await;
|
test_oauth2_openid_basic_flow_impl(rsclient, Some("query"), false).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -553,7 +553,7 @@ async fn test_oauth2_openid_basic_flow_mode_query(rsclient: &KanidmClient) {
|
||||||
///
|
///
|
||||||
/// The response should be returned in the URI's fragment.
|
/// The response should be returned in the URI's fragment.
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_oauth2_openid_basic_flow_mode_fragment(rsclient: &KanidmClient) {
|
async fn test_oauth2_openid_basic_flow_mode_fragment(rsclient: KanidmClient) {
|
||||||
test_oauth2_openid_basic_flow_impl(rsclient, Some("fragment"), true).await;
|
test_oauth2_openid_basic_flow_impl(rsclient, Some("fragment"), true).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -570,7 +570,7 @@ async fn test_oauth2_openid_basic_flow_mode_fragment(rsclient: &KanidmClient) {
|
||||||
/// If `true`, use the `code` passed in the callback URI's fragment, and
|
/// If `true`, use the `code` passed in the callback URI's fragment, and
|
||||||
/// require the query parameter to be empty.
|
/// require the query parameter to be empty.
|
||||||
async fn test_oauth2_openid_public_flow_impl(
|
async fn test_oauth2_openid_public_flow_impl(
|
||||||
rsclient: &KanidmClient,
|
rsclient: KanidmClient,
|
||||||
response_mode: Option<&str>,
|
response_mode: Option<&str>,
|
||||||
response_in_fragment: bool,
|
response_in_fragment: bool,
|
||||||
) {
|
) {
|
||||||
|
@ -901,7 +901,7 @@ async fn test_oauth2_openid_public_flow_impl(
|
||||||
///
|
///
|
||||||
/// The response should be returned as a query parameter.
|
/// The response should be returned as a query parameter.
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_oauth2_openid_public_flow_mode_unset(rsclient: &KanidmClient) {
|
async fn test_oauth2_openid_public_flow_mode_unset(rsclient: KanidmClient) {
|
||||||
test_oauth2_openid_public_flow_impl(rsclient, None, false).await;
|
test_oauth2_openid_public_flow_impl(rsclient, None, false).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -910,7 +910,7 @@ async fn test_oauth2_openid_public_flow_mode_unset(rsclient: &KanidmClient) {
|
||||||
///
|
///
|
||||||
/// The response should be returned as a query parameter.
|
/// The response should be returned as a query parameter.
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_oauth2_openid_public_flow_mode_query(rsclient: &KanidmClient) {
|
async fn test_oauth2_openid_public_flow_mode_query(rsclient: KanidmClient) {
|
||||||
test_oauth2_openid_public_flow_impl(rsclient, Some("query"), false).await;
|
test_oauth2_openid_public_flow_impl(rsclient, Some("query"), false).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -919,12 +919,12 @@ async fn test_oauth2_openid_public_flow_mode_query(rsclient: &KanidmClient) {
|
||||||
///
|
///
|
||||||
/// The response should be returned in the URI's fragment.
|
/// The response should be returned in the URI's fragment.
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_oauth2_openid_public_flow_mode_fragment(rsclient: &KanidmClient) {
|
async fn test_oauth2_openid_public_flow_mode_fragment(rsclient: KanidmClient) {
|
||||||
test_oauth2_openid_public_flow_impl(rsclient, Some("fragment"), true).await;
|
test_oauth2_openid_public_flow_impl(rsclient, Some("fragment"), true).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_oauth2_token_post_bad_bodies(rsclient: &KanidmClient) {
|
async fn test_oauth2_token_post_bad_bodies(rsclient: KanidmClient) {
|
||||||
let res = rsclient
|
let res = rsclient
|
||||||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||||
.await;
|
.await;
|
||||||
|
@ -960,7 +960,7 @@ async fn test_oauth2_token_post_bad_bodies(rsclient: &KanidmClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_oauth2_token_revoke_post(rsclient: &KanidmClient) {
|
async fn test_oauth2_token_revoke_post(rsclient: KanidmClient) {
|
||||||
let res = rsclient
|
let res = rsclient
|
||||||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||||
.await;
|
.await;
|
||||||
|
|
|
@ -4,7 +4,7 @@ use kanidmd_testkit::{create_user, ADMIN_TEST_PASSWORD, ADMIN_TEST_USER};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_v1_person_id_patch(rsclient: &KanidmClient) {
|
async fn test_v1_person_id_patch(rsclient: KanidmClient) {
|
||||||
let res = rsclient
|
let res = rsclient
|
||||||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||||
.await;
|
.await;
|
||||||
|
@ -25,7 +25,7 @@ async fn test_v1_person_id_patch(rsclient: &KanidmClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_v1_person_id_ssh_pubkeys_post(rsclient: &KanidmClient) {
|
async fn test_v1_person_id_ssh_pubkeys_post(rsclient: KanidmClient) {
|
||||||
let res = rsclient
|
let res = rsclient
|
||||||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||||
.await;
|
.await;
|
||||||
|
|
|
@ -29,7 +29,7 @@ use kanidmd_testkit::{ADMIN_TEST_PASSWORD, ADMIN_TEST_USER};
|
||||||
const UNIX_TEST_PASSWORD: &str = "unix test user password";
|
const UNIX_TEST_PASSWORD: &str = "unix test user password";
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_server_create(rsclient: &KanidmClient) {
|
async fn test_server_create(rsclient: KanidmClient) {
|
||||||
let e: Entry = serde_json::from_str(
|
let e: Entry = serde_json::from_str(
|
||||||
r#"{
|
r#"{
|
||||||
"attrs": {
|
"attrs": {
|
||||||
|
@ -55,7 +55,7 @@ async fn test_server_create(rsclient: &KanidmClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_server_whoami_anonymous(rsclient: &KanidmClient) {
|
async fn test_server_whoami_anonymous(rsclient: KanidmClient) {
|
||||||
// First show we are un-authenticated.
|
// First show we are un-authenticated.
|
||||||
let pre_res = rsclient.whoami().await;
|
let pre_res = rsclient.whoami().await;
|
||||||
// This means it was okay whoami, but no uat attached.
|
// This means it was okay whoami, but no uat attached.
|
||||||
|
@ -84,7 +84,7 @@ async fn test_server_whoami_anonymous(rsclient: &KanidmClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_server_whoami_admin_simple_password(rsclient: &KanidmClient) {
|
async fn test_server_whoami_admin_simple_password(rsclient: KanidmClient) {
|
||||||
// First show we are un-authenticated.
|
// First show we are un-authenticated.
|
||||||
let pre_res = rsclient.whoami().await;
|
let pre_res = rsclient.whoami().await;
|
||||||
// This means it was okay whoami, but no uat attached.
|
// This means it was okay whoami, but no uat attached.
|
||||||
|
@ -109,7 +109,7 @@ async fn test_server_whoami_admin_simple_password(rsclient: &KanidmClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_server_search(rsclient: &KanidmClient) {
|
async fn test_server_search(rsclient: KanidmClient) {
|
||||||
// First show we are un-authenticated.
|
// First show we are un-authenticated.
|
||||||
let pre_res = rsclient.whoami().await;
|
let pre_res = rsclient.whoami().await;
|
||||||
// This means it was okay whoami, but no uat attached.
|
// This means it was okay whoami, but no uat attached.
|
||||||
|
@ -135,7 +135,7 @@ async fn test_server_search(rsclient: &KanidmClient) {
|
||||||
|
|
||||||
// test the rest group endpoint.
|
// test the rest group endpoint.
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_server_rest_group_read(rsclient: &KanidmClient) {
|
async fn test_server_rest_group_read(rsclient: KanidmClient) {
|
||||||
let res = rsclient
|
let res = rsclient
|
||||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||||
.await;
|
.await;
|
||||||
|
@ -151,7 +151,7 @@ async fn test_server_rest_group_read(rsclient: &KanidmClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_server_rest_group_lifecycle(rsclient: &KanidmClient) {
|
async fn test_server_rest_group_lifecycle(rsclient: KanidmClient) {
|
||||||
let res = rsclient
|
let res = rsclient
|
||||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||||
.await;
|
.await;
|
||||||
|
@ -263,7 +263,7 @@ async fn test_server_rest_group_lifecycle(rsclient: &KanidmClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_server_rest_account_read(rsclient: &KanidmClient) {
|
async fn test_server_rest_account_read(rsclient: KanidmClient) {
|
||||||
let res = rsclient
|
let res = rsclient
|
||||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||||
.await;
|
.await;
|
||||||
|
@ -279,7 +279,7 @@ async fn test_server_rest_account_read(rsclient: &KanidmClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_server_rest_schema_read(rsclient: &KanidmClient) {
|
async fn test_server_rest_schema_read(rsclient: KanidmClient) {
|
||||||
let res = rsclient
|
let res = rsclient
|
||||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||||
.await;
|
.await;
|
||||||
|
@ -313,7 +313,7 @@ async fn test_server_rest_schema_read(rsclient: &KanidmClient) {
|
||||||
|
|
||||||
// Test resetting a radius cred, and then checking/viewing it.
|
// Test resetting a radius cred, and then checking/viewing it.
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_server_radius_credential_lifecycle(rsclient: &KanidmClient) {
|
async fn test_server_radius_credential_lifecycle(rsclient: KanidmClient) {
|
||||||
let res = rsclient
|
let res = rsclient
|
||||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||||
.await;
|
.await;
|
||||||
|
@ -384,7 +384,7 @@ async fn test_server_radius_credential_lifecycle(rsclient: &KanidmClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_server_rest_person_account_lifecycle(rsclient: &KanidmClient) {
|
async fn test_server_rest_person_account_lifecycle(rsclient: KanidmClient) {
|
||||||
let res = rsclient
|
let res = rsclient
|
||||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||||
.await;
|
.await;
|
||||||
|
@ -439,7 +439,7 @@ async fn test_server_rest_person_account_lifecycle(rsclient: &KanidmClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_server_rest_sshkey_lifecycle(rsclient: &KanidmClient) {
|
async fn test_server_rest_sshkey_lifecycle(rsclient: KanidmClient) {
|
||||||
let res = rsclient
|
let res = rsclient
|
||||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||||
.await;
|
.await;
|
||||||
|
@ -509,7 +509,7 @@ async fn test_server_rest_sshkey_lifecycle(rsclient: &KanidmClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_server_rest_domain_lifecycle(rsclient: &KanidmClient) {
|
async fn test_server_rest_domain_lifecycle(rsclient: KanidmClient) {
|
||||||
let res = rsclient
|
let res = rsclient
|
||||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||||
.await;
|
.await;
|
||||||
|
@ -539,7 +539,7 @@ async fn test_server_rest_domain_lifecycle(rsclient: &KanidmClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_server_rest_posix_lifecycle(rsclient: &KanidmClient) {
|
async fn test_server_rest_posix_lifecycle(rsclient: KanidmClient) {
|
||||||
let res = rsclient
|
let res = rsclient
|
||||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||||
.await;
|
.await;
|
||||||
|
@ -660,7 +660,7 @@ async fn test_server_rest_posix_lifecycle(rsclient: &KanidmClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_server_rest_posix_auth_lifecycle(rsclient: &KanidmClient) {
|
async fn test_server_rest_posix_auth_lifecycle(rsclient: KanidmClient) {
|
||||||
let res = rsclient
|
let res = rsclient
|
||||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||||
.await;
|
.await;
|
||||||
|
@ -760,7 +760,7 @@ async fn test_server_rest_posix_auth_lifecycle(rsclient: &KanidmClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_server_rest_recycle_lifecycle(rsclient: &KanidmClient) {
|
async fn test_server_rest_recycle_lifecycle(rsclient: KanidmClient) {
|
||||||
let res = rsclient
|
let res = rsclient
|
||||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||||
.await;
|
.await;
|
||||||
|
@ -814,7 +814,7 @@ async fn test_server_rest_recycle_lifecycle(rsclient: &KanidmClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_server_rest_oauth2_basic_lifecycle(rsclient: &KanidmClient) {
|
async fn test_server_rest_oauth2_basic_lifecycle(rsclient: KanidmClient) {
|
||||||
let res = rsclient
|
let res = rsclient
|
||||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||||
.await;
|
.await;
|
||||||
|
@ -1027,7 +1027,7 @@ async fn test_server_rest_oauth2_basic_lifecycle(rsclient: &KanidmClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_server_credential_update_session_pw(rsclient: &KanidmClient) {
|
async fn test_server_credential_update_session_pw(rsclient: KanidmClient) {
|
||||||
let res = rsclient
|
let res = rsclient
|
||||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||||
.await;
|
.await;
|
||||||
|
@ -1102,7 +1102,7 @@ async fn test_server_credential_update_session_pw(rsclient: &KanidmClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_server_credential_update_session_totp_pw(rsclient: &KanidmClient) {
|
async fn test_server_credential_update_session_totp_pw(rsclient: KanidmClient) {
|
||||||
let res = rsclient
|
let res = rsclient
|
||||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||||
.await;
|
.await;
|
||||||
|
@ -1365,7 +1365,7 @@ async fn setup_demo_account_password(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_server_credential_update_session_passkey(rsclient: &KanidmClient) {
|
async fn test_server_credential_update_session_passkey(rsclient: KanidmClient) {
|
||||||
let mut wa = setup_demo_account_passkey(&rsclient).await;
|
let mut wa = setup_demo_account_passkey(&rsclient).await;
|
||||||
|
|
||||||
let res = rsclient
|
let res = rsclient
|
||||||
|
@ -1383,7 +1383,7 @@ async fn test_server_credential_update_session_passkey(rsclient: &KanidmClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_server_api_token_lifecycle(rsclient: &KanidmClient) {
|
async fn test_server_api_token_lifecycle(rsclient: KanidmClient) {
|
||||||
let res = rsclient
|
let res = rsclient
|
||||||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||||
.await;
|
.await;
|
||||||
|
@ -1566,7 +1566,7 @@ async fn test_server_api_token_lifecycle(rsclient: &KanidmClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_server_user_auth_token_lifecycle(rsclient: &KanidmClient) {
|
async fn test_server_user_auth_token_lifecycle(rsclient: KanidmClient) {
|
||||||
let res = rsclient
|
let res = rsclient
|
||||||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||||
.await;
|
.await;
|
||||||
|
@ -1689,7 +1689,7 @@ async fn test_server_user_auth_token_lifecycle(rsclient: &KanidmClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_server_user_auth_reauthentication(rsclient: &KanidmClient) {
|
async fn test_server_user_auth_reauthentication(rsclient: KanidmClient) {
|
||||||
let mut wa = setup_demo_account_passkey(&rsclient).await;
|
let mut wa = setup_demo_account_passkey(&rsclient).await;
|
||||||
|
|
||||||
let res = rsclient
|
let res = rsclient
|
||||||
|
@ -1868,7 +1868,7 @@ async fn start_password_session(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_server_user_auth_unprivileged(rsclient: &KanidmClient) {
|
async fn test_server_user_auth_unprivileged(rsclient: KanidmClient) {
|
||||||
let (account_name, account_pass) = setup_demo_account_password(&rsclient)
|
let (account_name, account_pass) = setup_demo_account_password(&rsclient)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to setup demo_account");
|
.expect("Failed to setup demo_account");
|
||||||
|
@ -1891,7 +1891,7 @@ async fn test_server_user_auth_unprivileged(rsclient: &KanidmClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_server_user_auth_privileged_shortcut(rsclient: &KanidmClient) {
|
async fn test_server_user_auth_privileged_shortcut(rsclient: KanidmClient) {
|
||||||
let (account_name, account_pass) = setup_demo_account_password(&rsclient)
|
let (account_name, account_pass) = setup_demo_account_password(&rsclient)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to setup demo_account");
|
.expect("Failed to setup demo_account");
|
||||||
|
|
|
@ -9,7 +9,7 @@ use std::str::FromStr;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_sync_account_lifecycle(rsclient: &KanidmClient) {
|
async fn test_sync_account_lifecycle(rsclient: KanidmClient) {
|
||||||
let a_res = rsclient
|
let a_res = rsclient
|
||||||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||||
.await;
|
.await;
|
||||||
|
@ -104,7 +104,7 @@ async fn test_sync_account_lifecycle(rsclient: &KanidmClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_scim_sync_entry_get(rsclient: &KanidmClient) {
|
async fn test_scim_sync_entry_get(rsclient: KanidmClient) {
|
||||||
let res = rsclient
|
let res = rsclient
|
||||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||||
.await;
|
.await;
|
||||||
|
|
|
@ -2,7 +2,7 @@ use kanidm_client::KanidmClient;
|
||||||
|
|
||||||
/// This literally tests that the thing exists and responds in a way we expect, probably worth testing it better...
|
/// This literally tests that the thing exists and responds in a way we expect, probably worth testing it better...
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_v1_service_account_id_attr_attr_delete(rsclient: &KanidmClient) {
|
async fn test_v1_service_account_id_attr_attr_delete(rsclient: KanidmClient) {
|
||||||
// We need to do manual reqwests here.
|
// We need to do manual reqwests here.
|
||||||
let client = rsclient.client();
|
let client = rsclient.client();
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ use kanidm_client::KanidmClient;
|
||||||
|
|
||||||
/// This literally tests that the thing exists and responds in a way we expect, probably worth testing it better...
|
/// This literally tests that the thing exists and responds in a way we expect, probably worth testing it better...
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_v1_system_post_attr(rsclient: &KanidmClient) {
|
async fn test_v1_system_post_attr(rsclient: KanidmClient) {
|
||||||
let client = rsclient.client();
|
let client = rsclient.client();
|
||||||
|
|
||||||
let response = match client
|
let response = match client
|
||||||
|
|
|
@ -3,8 +3,8 @@ use kanidmd_lib::constants::NAME_IDM_ADMINS;
|
||||||
use kanidmd_testkit::*;
|
use kanidmd_testkit::*;
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn account_id_unix_token(rsclient: &KanidmClient) {
|
async fn account_id_unix_token(rsclient: KanidmClient) {
|
||||||
login_put_admin_idm_admins(rsclient).await;
|
login_put_admin_idm_admins(&rsclient).await;
|
||||||
|
|
||||||
create_user(&rsclient, "group_manager", "idm_group_manage_priv").await;
|
create_user(&rsclient, "group_manager", "idm_group_manage_priv").await;
|
||||||
// create test user without creating new groups
|
// create test user without creating new groups
|
||||||
|
|
14
shell.nix
14
shell.nix
|
@ -1,13 +1,14 @@
|
||||||
let
|
{ pkgs ? import <nixpkgs> {} }:
|
||||||
rust-overlay = (import (builtins.fetchTarball "https://github.com/oxalica/rust-overlay/archive/master.tar.gz"));
|
let
|
||||||
|
overrides = (builtins.fromTOML (builtins.readFile ./rust-toolchain.toml));
|
||||||
in
|
in
|
||||||
{ pkgs ? import <nixpkgs> { overlays = [ rust-overlay ]; } }:
|
pkgs.mkShellNoCC rec {
|
||||||
pkgs.mkShellNoCC {
|
|
||||||
# Kanidm dependencies
|
# Kanidm dependencies
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
pkg-config
|
pkg-config
|
||||||
|
|
||||||
(rust-bin.fromRustupToolchainFile ./rust-toolchain.toml)
|
cargo
|
||||||
|
rustc
|
||||||
|
|
||||||
clang
|
clang
|
||||||
llvmPackages.bintools
|
llvmPackages.bintools
|
||||||
|
@ -18,6 +19,7 @@ pkgs.mkShellNoCC {
|
||||||
linux-pam
|
linux-pam
|
||||||
];
|
];
|
||||||
|
|
||||||
|
RUSTC_VERSION = overrides.toolchain.channel;
|
||||||
# https://github.com/rust-lang/rust-bindgen#environment-variables
|
# https://github.com/rust-lang/rust-bindgen#environment-variables
|
||||||
LIBCLANG_PATH = pkgs.lib.makeLibraryPath [ pkgs.llvmPackages_latest.libclang.lib ];
|
LIBCLANG_PATH = pkgs.lib.makeLibraryPath [ pkgs.llvmPackages_latest.libclang.lib ];
|
||||||
}
|
}
|
|
@ -16,9 +16,7 @@ impl GroupOpt {
|
||||||
GroupOpt::RemoveMembers(gcopt) => gcopt.copt.debug,
|
GroupOpt::RemoveMembers(gcopt) => gcopt.copt.debug,
|
||||||
GroupOpt::SetMembers(gcopt) => gcopt.copt.debug,
|
GroupOpt::SetMembers(gcopt) => gcopt.copt.debug,
|
||||||
GroupOpt::PurgeMembers(gcopt) => gcopt.copt.debug,
|
GroupOpt::PurgeMembers(gcopt) => gcopt.copt.debug,
|
||||||
GroupOpt::SetDescription { copt, .. }
|
GroupOpt::Rename { copt, .. } | GroupOpt::SetMail { copt, .. } => copt.debug,
|
||||||
| GroupOpt::Rename { copt, .. }
|
|
||||||
| GroupOpt::SetMail { copt, .. } => copt.debug,
|
|
||||||
GroupOpt::Posix { commands } => match commands {
|
GroupOpt::Posix { commands } => match commands {
|
||||||
GroupPosix::Show(gcopt) => gcopt.copt.debug,
|
GroupPosix::Show(gcopt) => gcopt.copt.debug,
|
||||||
GroupPosix::Set(gcopt) => gcopt.copt.debug,
|
GroupPosix::Set(gcopt) => gcopt.copt.debug,
|
||||||
|
@ -180,26 +178,6 @@ impl GroupOpt {
|
||||||
Ok(_) => println!("Successfully set mail for group {}", name.as_str()),
|
Ok(_) => println!("Successfully set mail for group {}", name.as_str()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GroupOpt::SetDescription {
|
|
||||||
copt,
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
} => {
|
|
||||||
let client = copt.to_client(OpType::Write).await;
|
|
||||||
|
|
||||||
let result = if let Some(description) = description {
|
|
||||||
client
|
|
||||||
.idm_group_set_description(name.as_str(), description.as_str())
|
|
||||||
.await
|
|
||||||
} else {
|
|
||||||
client.idm_group_purge_description(name.as_str()).await
|
|
||||||
};
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Err(e) => handle_client_error(e, copt.output_mode),
|
|
||||||
Ok(_) => println!("Successfully set description for group {}", name.as_str()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
GroupOpt::Rename {
|
GroupOpt::Rename {
|
||||||
copt,
|
copt,
|
||||||
name,
|
name,
|
||||||
|
|
|
@ -348,14 +348,6 @@ pub enum GroupOpt {
|
||||||
name: String,
|
name: String,
|
||||||
mail: Vec<String>,
|
mail: Vec<String>,
|
||||||
},
|
},
|
||||||
/// Set the description of this group. If no description is provided, the value is cleared
|
|
||||||
#[clap(name = "set-description")]
|
|
||||||
SetDescription {
|
|
||||||
#[clap(flatten)]
|
|
||||||
copt: CommonOpt,
|
|
||||||
name: String,
|
|
||||||
description: Option<String>,
|
|
||||||
},
|
|
||||||
/// Set a new entry-managed-by for this group.
|
/// Set a new entry-managed-by for this group.
|
||||||
#[clap(name = "set-entry-manager")]
|
#[clap(name = "set-entry-manager")]
|
||||||
SetEntryManagedBy {
|
SetEntryManagedBy {
|
||||||
|
|
|
@ -16,9 +16,6 @@ pub struct Config {
|
||||||
|
|
||||||
pub sync_password_as_unix_password: Option<bool>,
|
pub sync_password_as_unix_password: Option<bool>,
|
||||||
|
|
||||||
/// Maximum LDAP message size (in kilobytes)
|
|
||||||
pub max_ber_size: Option<usize>,
|
|
||||||
|
|
||||||
// pub entry: Option<Vec<EntryConfig>>,
|
// pub entry: Option<Vec<EntryConfig>>,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub entry_map: BTreeMap<Uuid, EntryConfig>,
|
pub entry_map: BTreeMap<Uuid, EntryConfig>,
|
||||||
|
|
|
@ -306,7 +306,6 @@ async fn run_sync(
|
||||||
// Preflight check.
|
// Preflight check.
|
||||||
// * can we connect to ipa?
|
// * can we connect to ipa?
|
||||||
let mut ipa_client = match LdapClientBuilder::new(&sync_config.ipa_uri)
|
let mut ipa_client = match LdapClientBuilder::new(&sync_config.ipa_uri)
|
||||||
.max_ber_size(sync_config.max_ber_size)
|
|
||||||
.add_tls_ca(&sync_config.ipa_ca)
|
.add_tls_ca(&sync_config.ipa_ca)
|
||||||
.build()
|
.build()
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -59,14 +59,6 @@ fn group_attr_gidnumber() -> String {
|
||||||
Attribute::GidNumber.to_string()
|
Attribute::GidNumber.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Default)]
|
|
||||||
#[serde(rename_all = "lowercase")]
|
|
||||||
pub enum GroupAttrSchema {
|
|
||||||
Rfc2307,
|
|
||||||
#[default]
|
|
||||||
Rfc2307Bis,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub sync_token: String,
|
pub sync_token: String,
|
||||||
|
@ -110,14 +102,12 @@ pub struct Config {
|
||||||
pub group_attr_gidnumber: String,
|
pub group_attr_gidnumber: String,
|
||||||
#[serde(default = "group_attr_member")]
|
#[serde(default = "group_attr_member")]
|
||||||
pub group_attr_member: String,
|
pub group_attr_member: String,
|
||||||
#[serde(default)]
|
|
||||||
pub group_attr_schema: GroupAttrSchema,
|
|
||||||
|
|
||||||
/// Maximum LDAP message size (in kilobytes)
|
|
||||||
pub max_ber_size: Option<usize>,
|
|
||||||
|
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub entry_map: BTreeMap<Uuid, EntryConfig>,
|
pub entry_map: BTreeMap<Uuid, EntryConfig>,
|
||||||
|
|
||||||
|
/// Maximum LDAP message size (in kilobytes)
|
||||||
|
pub max_ber_size: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Default, Clone)]
|
#[derive(Debug, Deserialize, Default, Clone)]
|
||||||
|
|
|
@ -11,26 +11,16 @@
|
||||||
// We allow expect since it forces good error messages at the least.
|
// We allow expect since it forces good error messages at the least.
|
||||||
#![allow(clippy::expect_used)]
|
#![allow(clippy::expect_used)]
|
||||||
|
|
||||||
use crate::config::{Config, EntryConfig, GroupAttrSchema};
|
mod config;
|
||||||
|
mod error;
|
||||||
|
|
||||||
|
use crate::config::{Config, EntryConfig};
|
||||||
use crate::error::SyncError;
|
use crate::error::SyncError;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use cron::Schedule;
|
use cron::Schedule;
|
||||||
use kanidm_client::KanidmClientBuilder;
|
|
||||||
use kanidm_lib_file_permissions::readonly as file_permissions_readonly;
|
|
||||||
use kanidm_proto::constants::ATTR_OBJECTCLASS;
|
use kanidm_proto::constants::ATTR_OBJECTCLASS;
|
||||||
use kanidm_proto::scim_v1::{
|
|
||||||
MultiValueAttr, ScimEntry, ScimSshPubKey, ScimSyncGroup, ScimSyncPerson, ScimSyncRequest,
|
|
||||||
ScimSyncRetentionMode, ScimSyncState,
|
|
||||||
};
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
use kanidm_utils_users::{get_current_gid, get_current_uid, get_effective_gid, get_effective_uid};
|
|
||||||
use kanidmd_lib::prelude::Attribute;
|
use kanidmd_lib::prelude::Attribute;
|
||||||
use ldap3_client::{
|
|
||||||
proto::{self, LdapFilter},
|
|
||||||
LdapClient, LdapClientBuilder, LdapSyncRepl, LdapSyncReplEntry, LdapSyncStateValue,
|
|
||||||
};
|
|
||||||
use std::collections::{BTreeMap, BTreeSet};
|
|
||||||
use std::fs::metadata;
|
use std::fs::metadata;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
@ -48,12 +38,22 @@ use tokio::net::TcpListener;
|
||||||
use tokio::runtime;
|
use tokio::runtime;
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
use tokio::time::sleep;
|
use tokio::time::sleep;
|
||||||
|
|
||||||
use tracing::{debug, error, info, warn};
|
use tracing::{debug, error, info, warn};
|
||||||
use tracing_subscriber::prelude::*;
|
use tracing_subscriber::prelude::*;
|
||||||
use tracing_subscriber::{fmt, EnvFilter};
|
use tracing_subscriber::{fmt, EnvFilter};
|
||||||
|
|
||||||
mod config;
|
use kanidm_client::KanidmClientBuilder;
|
||||||
mod error;
|
use kanidm_lib_file_permissions::readonly as file_permissions_readonly;
|
||||||
|
use kanidm_proto::scim_v1::{
|
||||||
|
MultiValueAttr, ScimEntry, ScimSshPubKey, ScimSyncGroup, ScimSyncPerson, ScimSyncRequest,
|
||||||
|
ScimSyncRetentionMode, ScimSyncState,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
use kanidm_utils_users::{get_current_gid, get_current_uid, get_effective_gid, get_effective_uid};
|
||||||
|
|
||||||
|
use ldap3_client::{proto, LdapClientBuilder, LdapSyncRepl, LdapSyncReplEntry, LdapSyncStateValue};
|
||||||
|
|
||||||
include!("./opt.rs");
|
include!("./opt.rs");
|
||||||
|
|
||||||
|
@ -343,7 +343,7 @@ async fn run_sync(
|
||||||
LdapSyncRepl::Success {
|
LdapSyncRepl::Success {
|
||||||
cookie,
|
cookie,
|
||||||
refresh_deletes: _,
|
refresh_deletes: _,
|
||||||
mut entries,
|
entries,
|
||||||
delete_uuids,
|
delete_uuids,
|
||||||
present_uuids,
|
present_uuids,
|
||||||
} => {
|
} => {
|
||||||
|
@ -393,14 +393,6 @@ async fn run_sync(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if matches!(sync_config.group_attr_schema, GroupAttrSchema::Rfc2307) {
|
|
||||||
// Since the schema is rfc 2307, this means that the names of members
|
|
||||||
// in any group are uids, not dn's, so we need to resolve these now.
|
|
||||||
resolve_member_uid_to_dn(&mut ldap_client, &mut entries, sync_config)
|
|
||||||
.await
|
|
||||||
.map_err(|_| SyncError::Preprocess)?;
|
|
||||||
};
|
|
||||||
|
|
||||||
let entries = match process_ldap_sync_result(entries, sync_config).await {
|
let entries = match process_ldap_sync_result(entries, sync_config).await {
|
||||||
Ok(ssr) => ssr,
|
Ok(ssr) => ssr,
|
||||||
Err(()) => {
|
Err(()) => {
|
||||||
|
@ -452,99 +444,6 @@ async fn run_sync(
|
||||||
// done!
|
// done!
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn resolve_member_uid_to_dn(
|
|
||||||
ldap_client: &mut LdapClient,
|
|
||||||
ldap_entries: &mut [LdapSyncReplEntry],
|
|
||||||
sync_config: &Config,
|
|
||||||
) -> Result<(), ()> {
|
|
||||||
let mut lookup_cache: BTreeMap<String, String> = Default::default();
|
|
||||||
|
|
||||||
for sync_entry in ldap_entries.iter_mut() {
|
|
||||||
let oc = sync_entry
|
|
||||||
.entry
|
|
||||||
.attrs
|
|
||||||
.get(ATTR_OBJECTCLASS)
|
|
||||||
.ok_or_else(|| {
|
|
||||||
error!("Invalid entry - no object class {}", sync_entry.entry.dn);
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if !oc.contains(&sync_config.group_objectclass) {
|
|
||||||
// Not a group, skip.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// It's a group, does it have memberUid? We pop this out here
|
|
||||||
// because we plan to replace it.
|
|
||||||
let members = sync_entry
|
|
||||||
.entry
|
|
||||||
.remove_ava(&sync_config.group_attr_member)
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
// Now, search all the members to dns.
|
|
||||||
let mut resolved_members: BTreeSet<String> = Default::default();
|
|
||||||
|
|
||||||
for member_uid in members {
|
|
||||||
if let Some(member_dn) = lookup_cache.get(&member_uid) {
|
|
||||||
resolved_members.insert(member_dn.to_string());
|
|
||||||
} else {
|
|
||||||
// Not in cache, search it. We use a syncrepl request here as this
|
|
||||||
// can bypass some query limits. Note we set the sync cookie to None.
|
|
||||||
let filter = LdapFilter::And(vec![
|
|
||||||
// Always put uid first as openldap can't query optimise.
|
|
||||||
LdapFilter::Equality(
|
|
||||||
sync_config.person_attr_user_name.clone(),
|
|
||||||
member_uid.clone(),
|
|
||||||
),
|
|
||||||
LdapFilter::Equality(
|
|
||||||
ATTR_OBJECTCLASS.into(),
|
|
||||||
sync_config.person_objectclass.clone(),
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
let mode = proto::SyncRequestMode::RefreshOnly;
|
|
||||||
let sync_result = ldap_client
|
|
||||||
.syncrepl(sync_config.ldap_sync_base_dn.clone(), filter, None, mode)
|
|
||||||
.await
|
|
||||||
.map_err(|err| {
|
|
||||||
debug!(?member_uid, ?sync_entry.entry_uuid);
|
|
||||||
error!(
|
|
||||||
?err,
|
|
||||||
"Failed to perform syncrepl to resolve members from ldap"
|
|
||||||
);
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Get the memberDN out now.
|
|
||||||
let member_dn = match sync_result {
|
|
||||||
LdapSyncRepl::Success { mut entries, .. } => {
|
|
||||||
let Some(resolved_entry) = entries.pop() else {
|
|
||||||
warn!(?member_uid, "Unable to resolve member, no matching entries");
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
resolved_entry.entry.dn.clone()
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
error!("Invalid sync repl result state");
|
|
||||||
return Err(());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// cache it.
|
|
||||||
lookup_cache.insert(member_uid, member_dn.clone());
|
|
||||||
resolved_members.insert(member_dn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put the members back in resolved as DN's now.
|
|
||||||
sync_entry
|
|
||||||
.entry
|
|
||||||
.attrs
|
|
||||||
.insert(sync_config.group_attr_member.clone(), resolved_members);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn process_ldap_sync_result(
|
async fn process_ldap_sync_result(
|
||||||
ldap_entries: Vec<LdapSyncReplEntry>,
|
ldap_entries: Vec<LdapSyncReplEntry>,
|
||||||
sync_config: &Config,
|
sync_config: &Config,
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
|
|
||||||
current_dir = $(shell pwd)
|
|
||||||
|
|
||||||
dev_install:
|
|
||||||
@ echo "WARNING: THIS WILL BREAK EXISTING UNIXD INSTALLS"
|
|
||||||
@ echo "ctrl-c now if this is not what you want"
|
|
||||||
@ read
|
|
||||||
@ echo "LAST CHANCE"
|
|
||||||
@ sleep 5
|
|
||||||
ln -s -f $(current_dir)/../platform/opensuse/kanidm-unixd.service /etc/systemd/system/kanidm-unixd.service
|
|
||||||
ln -s -f $(current_dir)/../platform/opensuse/kanidm-unixd-tasks.service /etc/systemd/system/kanidm-unixd-tasks.service
|
|
||||||
ln -s -f $(current_dir)/../target/debug/kanidm-unix /usr/sbin/kanidm-unix
|
|
||||||
ln -s -f $(current_dir)/../target/debug/kanidm_ssh_authorizedkeys /usr/sbin/kanidm_ssh_authorizedkeys
|
|
||||||
ln -s -f $(current_dir)/../target/debug/kanidm_unixd_tasks /usr/sbin/kanidm_unixd_tasks
|
|
||||||
ln -s -f $(current_dir)/../target/debug/kanidm_unixd /usr/sbin/kanidm_unixd
|
|
||||||
ln -s -f $(current_dir)/../target/debug/libpam_kanidm.so /lib64/security/pam_kanidm.so
|
|
||||||
ln -s -f $(current_dir)/../target/debug/libnss_kanidm.so /usr/lib64/libnss_kanidm.so.2
|
|
||||||
|
|
||||||
|
|
|
@ -7,13 +7,6 @@ use std::io::Read;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct EtcDb {
|
|
||||||
pub users: Vec<EtcUser>,
|
|
||||||
pub shadow: Vec<EtcShadow>,
|
|
||||||
pub groups: Vec<EtcGroup>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||||
pub struct EtcUser {
|
pub struct EtcUser {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -46,7 +39,7 @@ pub fn read_etc_passwd_file<P: AsRef<Path>>(path: P) -> Result<Vec<EtcUser>, Uni
|
||||||
parse_etc_passwd(contents.as_slice()).map_err(|_| UnixIntegrationError)
|
parse_etc_passwd(contents.as_slice()).map_err(|_| UnixIntegrationError)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Default)]
|
#[derive(Debug, PartialEq, Default)]
|
||||||
pub enum CryptPw {
|
pub enum CryptPw {
|
||||||
Sha256(String),
|
Sha256(String),
|
||||||
Sha512(String),
|
Sha512(String),
|
||||||
|
@ -63,16 +56,6 @@ impl fmt::Display for CryptPw {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for CryptPw {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
CryptPw::Invalid => write!(f, "x"),
|
|
||||||
CryptPw::Sha256(_s) => write!(f, "crypt sha256"),
|
|
||||||
CryptPw::Sha512(_s) => write!(f, "crypt sha512"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for CryptPw {
|
impl FromStr for CryptPw {
|
||||||
type Err = &'static str;
|
type Err = &'static str;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::unix_passwd::{EtcDb, EtcGroup, EtcUser};
|
use crate::unix_passwd::{EtcGroup, EtcUser};
|
||||||
use kanidm_proto::internal::OperationError;
|
use kanidm_proto::internal::OperationError;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -200,12 +200,6 @@ pub struct HomeDirectoryInfo {
|
||||||
pub aliases: Vec<String>,
|
pub aliases: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
pub struct TaskRequestFrame {
|
|
||||||
pub id: u64,
|
|
||||||
pub req: TaskRequest,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub enum TaskRequest {
|
pub enum TaskRequest {
|
||||||
HomeDirectory(HomeDirectoryInfo),
|
HomeDirectory(HomeDirectoryInfo),
|
||||||
|
@ -213,9 +207,8 @@ pub enum TaskRequest {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub enum TaskResponse {
|
pub enum TaskResponse {
|
||||||
Success(u64),
|
Success,
|
||||||
Error(String),
|
Error(String),
|
||||||
NotifyShadowChange(EtcDb),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue