Compare commits

..

3 commits

Author SHA1 Message Date
ToxicMushroom 26cbb2d8c1
Add primary email selection, update email add button. Use scim. 2025-04-05 03:02:22 +02:00
ToxicMushroom e2c6190693
patch up rebase 2025-03-06 02:28:26 +01:00
ToxicMushroom a28c4e8cd4
- Form validation
- Editable emails
- Basic profile updating
2025-03-06 02:28:23 +01:00
109 changed files with 4170 additions and 4512 deletions
.github/workflows
Cargo.lockCargo.toml
book/src
examples
libs
client/src
profiles
platform
proto/src
pykanidm
scripts
server
shell.nix
tools
cli/src
cli/group
opt
iam_migrations
unix_integration

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,3 @@
version = "2"
bindaddress = "[::]:8443" bindaddress = "[::]:8443"
ldapbindaddress = "127.0.0.1:3636" ldapbindaddress = "127.0.0.1:3636"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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");

View file

@ -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");

View file

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

View file

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

View file

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

View file

@ -1,2 +0,0 @@
# This is a sysusers.d format config, please refer to man sysusers.d(5)
g kanidmd -

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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::*;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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()),
|_| {}
);
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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");

View file

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

View file

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

View file

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

View file

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

View file

@ -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 ];
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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