mirror of
https://github.com/kanidm/kanidm.git
synced 2025-04-25 03:35:38 +02:00
Compare commits
30 commits
26cbb2d8c1
...
bb0e759134
Author | SHA1 | Date | |
---|---|---|---|
|
bb0e759134 | ||
|
645f13b285 | ||
|
245e68c5ba | ||
|
ad012cd6fd | ||
|
82a883089f | ||
|
a2eae53328 | ||
|
ec3db91da0 | ||
|
efaef70abe | ||
|
5b48f1dfe3 | ||
|
567fe7b259 | ||
|
5edc6be51c | ||
|
c75c97893e | ||
|
638904f12c | ||
|
e1b9063b99 | ||
|
bf1e9b0989 | ||
|
11c7266ff3 | ||
|
ef638a62e9 | ||
|
f86bc03a93 | ||
|
46eda59cff | ||
|
b13951a79b | ||
|
1e91f244a2 | ||
|
23bb656c6b | ||
|
b88b6923eb | ||
|
e3243ce6b0 | ||
|
4b4e690642 | ||
|
d6549077fb | ||
|
2c5ce227ae | ||
|
919e0ba6fe | ||
|
23d35dc324 | ||
|
7d9661ef45 |
.github/workflows
Cargo.lockCargo.tomlbook/src
examples
libs
platform
proto/src
pykanidm
scripts
server
core
daemon
lib-macros/src
lib/src
testkit-macros/src
testkit
8
.github/workflows/clippy.yml
vendored
8
.github/workflows/clippy.yml
vendored
|
@ -19,9 +19,7 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.7
|
||||
with:
|
||||
version: "v0.4.2"
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update && \
|
||||
|
@ -41,8 +39,6 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.7
|
||||
with:
|
||||
version: "v0.4.2"
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- name: "Run cargo fmt"
|
||||
run: cargo fmt --check
|
||||
|
|
4
.github/workflows/kanidm_individual_book.yml
vendored
4
.github/workflows/kanidm_individual_book.yml
vendored
|
@ -24,9 +24,7 @@ jobs:
|
|||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.7
|
||||
with:
|
||||
version: "v0.4.2"
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- name: Install deps
|
||||
run: |
|
||||
sudo apt-get update
|
||||
|
|
15
.github/workflows/rust_build.yml
vendored
15
.github/workflows/rust_build.yml
vendored
|
@ -27,10 +27,7 @@ jobs:
|
|||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.7
|
||||
with:
|
||||
version: "v0.4.2"
|
||||
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update && \
|
||||
|
@ -75,10 +72,7 @@ jobs:
|
|||
with:
|
||||
toolchain: ${{ matrix.rust_version }}
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.7
|
||||
with:
|
||||
version: "v0.4.2"
|
||||
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update && \
|
||||
|
@ -118,10 +112,7 @@ jobs:
|
|||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.7
|
||||
with:
|
||||
version: "v0.4.2"
|
||||
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update && \
|
||||
|
|
4
.github/workflows/windows_build.yml
vendored
4
.github/workflows/windows_build.yml
vendored
|
@ -28,9 +28,7 @@ jobs:
|
|||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.7
|
||||
with:
|
||||
version: "v0.4.2"
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- run: cargo build --locked -p kanidm_client -p kanidm_tools --bin kanidm
|
||||
# yamllint disable-line rule:line-length
|
||||
- run: cargo test -p kanidm_client -p kanidm_tools
|
||||
|
|
923
Cargo.lock
generated
923
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
12
Cargo.toml
12
Cargo.toml
|
@ -159,12 +159,12 @@ base64 = "^0.22.1"
|
|||
base64urlsafedata = "0.5.1"
|
||||
bitflags = "^2.8.0"
|
||||
bytes = "^1.9.0"
|
||||
clap = { version = "^4.5.27", features = ["derive", "env"] }
|
||||
clap = { version = "^4.5.34", features = ["derive", "env"] }
|
||||
clap_complete = "^4.5.42"
|
||||
# Forced by saffron/cron
|
||||
chrono = "^0.4.39"
|
||||
compact_jwt = { version = "^0.4.2", default-features = false }
|
||||
concread = "^0.5.3"
|
||||
concread = "^0.5.5"
|
||||
cron = "0.15.0"
|
||||
crossbeam = "0.8.4"
|
||||
csv = "1.3.1"
|
||||
|
@ -190,7 +190,7 @@ image = { version = "0.24.9", default-features = false, features = [
|
|||
"jpeg",
|
||||
"webp",
|
||||
] }
|
||||
itertools = "0.13.0"
|
||||
itertools = "0.14.0"
|
||||
enum-iterator = "2.1.0"
|
||||
kanidmd_web_ui_shared = { path = "./server/web_ui/shared" }
|
||||
# REMOVE this
|
||||
|
@ -202,11 +202,11 @@ libc = "^0.2.168"
|
|||
libnss = "^0.8.0"
|
||||
libsqlite3-sys = "^0.25.2"
|
||||
lodepng = "3.11.0"
|
||||
lru = "^0.12.5"
|
||||
lru = "^0.13.0"
|
||||
mathru = "^0.13.0"
|
||||
md-5 = "0.10.6"
|
||||
mimalloc = "0.1.43"
|
||||
notify-debouncer-full = { version = "0.1" }
|
||||
notify-debouncer-full = { version = "0.5" }
|
||||
num_enum = "^0.5.11"
|
||||
oauth2_ext = { version = "^4.4.2", package = "oauth2", default-features = false }
|
||||
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-proto = "0.5.1"
|
||||
|
||||
whoami = "^1.5.2"
|
||||
whoami = "^1.6.0"
|
||||
walkdir = "2"
|
||||
|
||||
x509-cert = "0.2.5"
|
||||
|
|
|
@ -53,7 +53,6 @@
|
|||
|
||||
- [Service Integration Examples](examples/readme.md)
|
||||
- [Kubernetes Ingress](examples/kubernetes_ingress.md)
|
||||
- [OAuth2 Examples](integrations/oauth2/examples.md)
|
||||
- [Traefik](examples/traefik.md)
|
||||
|
||||
- [Replication](repl/readme.md)
|
||||
|
|
|
@ -58,6 +58,21 @@ can only use the UID range `1879048192` (`0x70000000`) to `2147483647` (`0x7ffff
|
|||
limitations of the Linux kernel and
|
||||
[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
|
||||
[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%
|
||||
|
@ -67,7 +82,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
|
||||
events.
|
||||
|
||||
We recommend the use of the range `65536` through `524287` for manual allocation. This leaves the
|
||||
We recommend the use of the range `65536` through `524287` (`unused C`) for manual allocation. This leaves the
|
||||
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.
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@ can take many forms such as.
|
|||
- firstname firstname lastname
|
||||
- firstname lastname lastname
|
||||
- firstname
|
||||
- middlename lastname
|
||||
- lastname firstname
|
||||
|
||||
And many many more that are not listed here. This is why our names are displayName as a freetext
|
||||
|
|
|
@ -54,7 +54,6 @@ services:
|
|||
- traefik.http.routers.kanidm.entrypoints=websecure
|
||||
- traefik.http.routers.kanidm.rule=Host(`idm.example.com`)
|
||||
- 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.scheme=https
|
||||
volumes:
|
||||
|
|
|
@ -566,7 +566,7 @@ Due to a [lack of public client support](https://github.com/oauth2-proxy/oauth2-
|
|||
|
||||
```bash
|
||||
kanidm system oauth2 create webapp 'webapp.example.com' 'https://webapp.example.com'
|
||||
kanidm system add-redirect-url webapp 'https://webapp.example.com/oauth2/callback'
|
||||
kanidm system oauth2 add-redirect-url webapp 'https://webapp.example.com/oauth2/callback'
|
||||
kanidm system oauth2 update-scope-map webapp email openid
|
||||
kanidm system oauth2 get webapp
|
||||
kanidm system oauth2 show-basic-secret webapp
|
||||
|
|
|
@ -5,57 +5,45 @@
|
|||
- Debian packaging is complex enough that it lives in a separate repository:
|
||||
[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
|
||||
through replicating the same process locally, using [cross](https://github.com/cross-rs/cross) & Docker to isolate the build process
|
||||
from your normal computer and allow building packages for multiple architectures.
|
||||
through replicating the same process locally, using Docker to isolate the build process from your normal computer.
|
||||
- Due to the complexity of crosscompilation, we no longer support it and recommend building natively,
|
||||
i.e. on the platform you're targeting.
|
||||
- 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.
|
||||
|
||||
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:
|
||||
```shell
|
||||
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:
|
||||
```shell
|
||||
docker run --rm -it -e CI=true \
|
||||
docker run --rm -it -e VERBOSE=true -e CI=true \
|
||||
--mount "type=bind,src=$PWD,target=/src" \
|
||||
--workdir /src \
|
||||
rust:bookworm
|
||||
```
|
||||
1. In the container install dependencies with:
|
||||
```shell
|
||||
# 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
|
||||
platform/debian/kanidm_ppa_automation/scripts/install_ci_build_dependencies.sh
|
||||
```
|
||||
1. In the container launch the deb build:
|
||||
1. Launch your desired target 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
|
||||
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
|
||||
persist.
|
||||
|
||||
## Adding or amending a deb package
|
||||
The rough overview of steps is:
|
||||
The rough overview of steps is as follows, see further down for details.
|
||||
1. Add cargo-deb specific metadata to the rust package and any static assets. Submit your changes as
|
||||
a PR.
|
||||
2. Add build instructions to the separate packaging repo. Submit your changes as a PR.
|
||||
2. Add build steps 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
|
||||
builds of the new package.
|
||||
|
||||
|
@ -72,8 +60,8 @@ an example, see `unix_integration/resolver/Cargo.toml`
|
|||
### Configuration in the kanidm_ppa_automation repo
|
||||
- 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.
|
||||
- Amend `scripts/crossbuild.sh` build rules to include new binaries or packages with shared
|
||||
libraries. Search for the lines starting with `cross build`.
|
||||
- Amend `scripts/build_native.sh` build rules to include new binaries or packages with shared
|
||||
libraries.
|
||||
- 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.
|
||||
- Add any new packages to `scripts/build_debs.sh`, search for the line starting with `for package in`.
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# The server configuration file version.
|
||||
version = "2"
|
||||
|
||||
# The webserver bind address. Requires TLS certificates.
|
||||
# If the port is set to 443 you may require the
|
||||
# NET_BIND_SERVICE capability.
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# The server configuration file version.
|
||||
version = "2"
|
||||
|
||||
# The webserver bind address. Requires TLS certificates.
|
||||
# If the port is set to 443 you may require the
|
||||
# NET_BIND_SERVICE capability.
|
||||
|
|
|
@ -195,4 +195,20 @@ impl KanidmClient {
|
|||
self.perform_get_request(&format!("/v1/group/{}/_attr/mail", id))
|
||||
.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
|
||||
}
|
||||
}
|
||||
|
|
14
libs/profiles/release_debian.toml
Normal file
14
libs/profiles/release_debian.toml
Normal file
|
@ -0,0 +1,14 @@
|
|||
# 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 942c7b69ca807cc38186b63ab02a391bac9eac7e
|
||||
Subproject commit 8d7579fb543632df74e609892c69ce9f368fdd02
|
|
@ -12,7 +12,7 @@ Conflicts=nscd.service
|
|||
|
||||
[Service]
|
||||
DynamicUser=yes
|
||||
SupplementaryGroups=tss shadow
|
||||
SupplementaryGroups=tss
|
||||
UMask=0027
|
||||
CacheDirectory=kanidm-unixd
|
||||
RuntimeDirectory=kanidm-unixd
|
||||
|
|
|
@ -22,6 +22,8 @@ pub enum Attribute {
|
|||
AcpCreateClass,
|
||||
AcpEnable,
|
||||
AcpModifyClass,
|
||||
AcpModifyPresentClass,
|
||||
AcpModifyRemoveClass,
|
||||
AcpModifyPresentAttr,
|
||||
AcpModifyRemovedAttr,
|
||||
AcpReceiver,
|
||||
|
@ -80,6 +82,7 @@ pub enum Attribute {
|
|||
IdVerificationEcKey,
|
||||
Image,
|
||||
Index,
|
||||
Indexed,
|
||||
IpaNtHash,
|
||||
IpaSshPubKey,
|
||||
JwsEs256PrivateKey,
|
||||
|
@ -254,6 +257,8 @@ impl Attribute {
|
|||
Attribute::AcpCreateClass => ATTR_ACP_CREATE_CLASS,
|
||||
Attribute::AcpEnable => ATTR_ACP_ENABLE,
|
||||
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::AcpModifyRemovedAttr => ATTR_ACP_MODIFY_REMOVEDATTR,
|
||||
Attribute::AcpReceiver => ATTR_ACP_RECEIVER,
|
||||
|
@ -311,6 +316,7 @@ impl Attribute {
|
|||
Attribute::IdVerificationEcKey => ATTR_ID_VERIFICATION_ECKEY,
|
||||
Attribute::Image => ATTR_IMAGE,
|
||||
Attribute::Index => ATTR_INDEX,
|
||||
Attribute::Indexed => ATTR_INDEXED,
|
||||
Attribute::IpaNtHash => ATTR_IPANTHASH,
|
||||
Attribute::IpaSshPubKey => ATTR_IPASSHPUBKEY,
|
||||
Attribute::JwsEs256PrivateKey => ATTR_JWS_ES256_PRIVATE_KEY,
|
||||
|
@ -438,6 +444,8 @@ impl Attribute {
|
|||
ATTR_ACP_CREATE_CLASS => Attribute::AcpCreateClass,
|
||||
ATTR_ACP_ENABLE => Attribute::AcpEnable,
|
||||
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_REMOVEDATTR => Attribute::AcpModifyRemovedAttr,
|
||||
ATTR_ACP_RECEIVER => Attribute::AcpReceiver,
|
||||
|
@ -495,6 +503,7 @@ impl Attribute {
|
|||
ATTR_ID_VERIFICATION_ECKEY => Attribute::IdVerificationEcKey,
|
||||
ATTR_IMAGE => Attribute::Image,
|
||||
ATTR_INDEX => Attribute::Index,
|
||||
ATTR_INDEXED => Attribute::Indexed,
|
||||
ATTR_IPANTHASH => Attribute::IpaNtHash,
|
||||
ATTR_IPASSHPUBKEY => Attribute::IpaSshPubKey,
|
||||
ATTR_JWS_ES256_PRIVATE_KEY => Attribute::JwsEs256PrivateKey,
|
||||
|
|
|
@ -62,6 +62,8 @@ pub const ATTR_ACP_CREATE_ATTR: &str = "acp_create_attr";
|
|||
pub const ATTR_ACP_CREATE_CLASS: &str = "acp_create_class";
|
||||
pub const ATTR_ACP_ENABLE: &str = "acp_enable";
|
||||
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_REMOVEDATTR: &str = "acp_modify_removedattr";
|
||||
pub const ATTR_ACP_RECEIVER_GROUP: &str = "acp_receiver_group";
|
||||
|
@ -124,6 +126,7 @@ pub const ATTR_GROUP: &str = "group";
|
|||
pub const ATTR_ID_VERIFICATION_ECKEY: &str = "id_verification_eckey";
|
||||
pub const ATTR_IMAGE: &str = "image";
|
||||
pub const ATTR_INDEX: &str = "index";
|
||||
pub const ATTR_INDEXED: &str = "indexed";
|
||||
pub const ATTR_IPANTHASH: &str = "ipanthash";
|
||||
pub const ATTR_IPASSHPUBKEY: &str = "ipasshpubkey";
|
||||
pub const ATTR_JWS_ES256_PRIVATE_KEY: &str = "jws_es256_private_key";
|
||||
|
|
|
@ -33,7 +33,7 @@ pub enum ScimAttributeEffectiveAccess {
|
|||
/// All attributes on the entry have this permission granted
|
||||
Grant,
|
||||
/// All attributes on the entry have this permission denied
|
||||
Denied,
|
||||
Deny,
|
||||
/// The following attributes on the entry have this permission granted
|
||||
Allow(BTreeSet<Attribute>),
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ impl ScimAttributeEffectiveAccess {
|
|||
pub fn check(&self, attr: &Attribute) -> bool {
|
||||
match self {
|
||||
Self::Grant => true,
|
||||
Self::Denied => false,
|
||||
Self::Deny => false,
|
||||
Self::Allow(set) => set.contains(attr),
|
||||
}
|
||||
}
|
||||
|
|
724
pykanidm/poetry.lock
generated
724
pykanidm/poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -29,7 +29,7 @@ Authlib = "^1.2.0"
|
|||
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
ruff = ">=0.5.1,<0.9.10"
|
||||
ruff = ">=0.5.1,<0.11.3"
|
||||
pytest = "^8.3.4"
|
||||
mypy = "^1.14.1"
|
||||
types-requests = "^2.32.0.20241016"
|
||||
|
@ -40,7 +40,7 @@ pylint-pydantic = "^0.3.5"
|
|||
coverage = "^7.6.10"
|
||||
mkdocs = "^1.6.1"
|
||||
mkdocs-material = "^9.6.1"
|
||||
mkdocstrings = ">=0.27,<0.29"
|
||||
mkdocstrings = ">=0.27,<0.30"
|
||||
mkdocstrings-python = "^1.13.0"
|
||||
pook = "^2.1.3"
|
||||
|
||||
|
|
|
@ -21,7 +21,8 @@ sudo apt-get install -y \
|
|||
libsystemd-dev \
|
||||
libudev-dev \
|
||||
pkg-config \
|
||||
ripgrep
|
||||
ripgrep \
|
||||
lld
|
||||
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
|
||||
|
@ -36,7 +37,7 @@ sudo chgrp vscode ~/ -R
|
|||
# shellcheck disable=SC1091
|
||||
source scripts/devcontainer_poststart.sh
|
||||
|
||||
cargo install
|
||||
cargo install \
|
||||
cargo-audit \
|
||||
mdbook-mermaid \
|
||||
mdbook
|
||||
|
|
|
@ -25,7 +25,7 @@ def recover_account(username: str) -> str:
|
|||
"recover-account",
|
||||
username,
|
||||
"--config",
|
||||
"../../examples/insecure_server.toml",
|
||||
"./insecure_server.toml",
|
||||
"--output",
|
||||
"json",
|
||||
]
|
||||
|
|
|
@ -44,7 +44,7 @@ fi
|
|||
|
||||
|
||||
# defaults
|
||||
KANIDM_CONFIG_FILE="../../examples/insecure_server.toml"
|
||||
KANIDM_CONFIG_FILE="./insecure_server.toml"
|
||||
KANIDM_URL="$(rg origin "${KANIDM_CONFIG_FILE}" | awk '{print $NF}' | tr -d '"')"
|
||||
KANIDM_CA_PATH="/tmp/kanidm/ca.pem"
|
||||
|
||||
|
@ -83,7 +83,7 @@ if [ "${REMOVE_TEST_DB}" -eq 1 ]; then
|
|||
rm /tmp/kanidm/kanidm.db || true
|
||||
fi
|
||||
|
||||
export KANIDM_CONFIG="../../examples/insecure_server.toml"
|
||||
export KANIDM_CONFIG="./insecure_server.toml"
|
||||
IDM_ADMIN_USER="idm_admin@localhost"
|
||||
|
||||
echo "Resetting the idm_admin user..."
|
||||
|
|
|
@ -25,7 +25,7 @@ if [ ! -f "run_insecure_dev_server.sh" ]; then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
export KANIDM_CONFIG="../../examples/insecure_server.toml"
|
||||
export KANIDM_CONFIG="./insecure_server.toml"
|
||||
|
||||
mkdir -p /tmp/kanidm/client_ca
|
||||
|
||||
|
@ -48,7 +48,7 @@ fi
|
|||
|
||||
ATTEMPT=0
|
||||
|
||||
KANIDM_CONFIG_FILE="../../examples/insecure_server.toml"
|
||||
KANIDM_CONFIG_FILE="./insecure_server.toml"
|
||||
KANIDM_URL="$(rg origin "${KANIDM_CONFIG_FILE}" | awk '{print $NF}' | tr -d '"')"
|
||||
KANIDM_CA_PATH="/tmp/kanidm/ca.pem"
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ askama = { workspace = true, features = ["with-axum"] }
|
|||
askama_axum = { workspace = true }
|
||||
axum = { workspace = true }
|
||||
axum-htmx = { workspace = true }
|
||||
axum-extra = { version = "0.9.6", features = ["cookie"] }
|
||||
axum-extra = { version = "0.9.6", features = ["cookie", "form"] }
|
||||
axum-macros = "0.4.2"
|
||||
axum-server = { version = "0.7.1", default-features = false }
|
||||
bytes = { workspace = true }
|
||||
|
|
|
@ -191,7 +191,7 @@ impl QueryServerReadV1 {
|
|||
pub async fn handle_online_backup(
|
||||
&self,
|
||||
msg: OnlineBackupEvent,
|
||||
outpath: &str,
|
||||
outpath: &Path,
|
||||
versions: usize,
|
||||
) -> Result<(), OperationError> {
|
||||
trace!(eventid = ?msg.eventid, "Begin online backup event");
|
||||
|
@ -200,12 +200,12 @@ impl QueryServerReadV1 {
|
|||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let timestamp = now.format(&Rfc3339).unwrap();
|
||||
let dest_file = format!("{}/backup-{}.json", outpath, timestamp);
|
||||
let dest_file = outpath.join(format!("backup-{}.json", timestamp));
|
||||
|
||||
if Path::new(&dest_file).exists() {
|
||||
if dest_file.exists() {
|
||||
error!(
|
||||
"Online backup file {} already exists, will not overwrite it.",
|
||||
dest_file
|
||||
dest_file.display()
|
||||
);
|
||||
return Err(OperationError::InvalidState);
|
||||
}
|
||||
|
@ -218,10 +218,14 @@ impl QueryServerReadV1 {
|
|||
.get_be_txn()
|
||||
.backup(&dest_file)
|
||||
.map(|()| {
|
||||
info!("Online backup created {} successfully", dest_file);
|
||||
info!("Online backup created {} successfully", dest_file.display());
|
||||
})
|
||||
.map_err(|e| {
|
||||
error!("Online backup failed to create {}: {:?}", dest_file, e);
|
||||
error!(
|
||||
"Online backup failed to create {}: {:?}",
|
||||
dest_file.display(),
|
||||
e
|
||||
);
|
||||
OperationError::InvalidState
|
||||
})?;
|
||||
}
|
||||
|
@ -267,7 +271,11 @@ impl QueryServerReadV1 {
|
|||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Online backup cleanup error read dir {}: {}", outpath, e);
|
||||
error!(
|
||||
"Online backup cleanup error read dir {}: {}",
|
||||
outpath.display(),
|
||||
e
|
||||
);
|
||||
return Err(OperationError::InvalidState);
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -3,7 +3,7 @@ use axum::routing::get;
|
|||
use axum::Router;
|
||||
use axum_htmx::HxRequestGuardLayer;
|
||||
|
||||
mod persons;
|
||||
pub(crate) mod persons;
|
||||
|
||||
pub fn admin_router() -> Router<ServerState> {
|
||||
let unguarded_router = Router::new()
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::https::errors::WebError;
|
||||
use crate::https::extractors::{DomainInfo, VerifiedClientInformation};
|
||||
use crate::https::middleware::KOpId;
|
||||
use crate::https::views::errors::HtmxError;
|
||||
|
@ -7,17 +8,15 @@ use crate::https::ServerState;
|
|||
use askama::Template;
|
||||
use axum::extract::{Path, State};
|
||||
use axum::http::Uri;
|
||||
use axum::response::{ErrorResponse, IntoResponse, Response};
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use axum::Extension;
|
||||
use axum_htmx::{HxPushUrl, HxRequest};
|
||||
use futures_util::TryFutureExt;
|
||||
use kanidm_proto::attribute::Attribute;
|
||||
use kanidm_proto::internal::OperationError;
|
||||
use kanidm_proto::scim_v1::client::ScimFilter;
|
||||
use kanidm_proto::scim_v1::server::{ScimEffectiveAccess, ScimEntryKanidm, ScimPerson};
|
||||
use kanidm_proto::scim_v1::ScimEntryGetQuery;
|
||||
use kanidmd_lib::constants::EntryClass;
|
||||
use kanidmd_lib::idm::server::DomainInfoRead;
|
||||
use kanidmd_lib::idm::ClientAuthInfo;
|
||||
use std::str::FromStr;
|
||||
use uuid::Uuid;
|
||||
|
@ -70,7 +69,7 @@ pub(crate) async fn view_person_view_get(
|
|||
DomainInfo(domain_info): DomainInfo,
|
||||
) -> axum::response::Result<Response> {
|
||||
let (person, scim_effective_access) =
|
||||
get_person_info(uuid, state, &kopid, client_auth_info, domain_info.clone()).await?;
|
||||
get_person_info(uuid, state, &kopid, client_auth_info).await?;
|
||||
let person_partial = PersonViewPartial {
|
||||
person,
|
||||
scim_effective_access,
|
||||
|
@ -101,7 +100,7 @@ pub(crate) async fn view_persons_get(
|
|||
DomainInfo(domain_info): DomainInfo,
|
||||
VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
|
||||
) -> axum::response::Result<Response> {
|
||||
let persons = get_persons_info(state, &kopid, client_auth_info, domain_info.clone()).await?;
|
||||
let persons = get_persons_info(state, &kopid, client_auth_info).await?;
|
||||
let persons_partial = PersonsPartialView { persons };
|
||||
|
||||
let push_url = HxPushUrl(Uri::from_static("/ui/admin/persons"));
|
||||
|
@ -119,13 +118,12 @@ pub(crate) async fn view_persons_get(
|
|||
})
|
||||
}
|
||||
|
||||
async fn get_person_info(
|
||||
pub async fn get_person_info(
|
||||
uuid: Uuid,
|
||||
state: ServerState,
|
||||
kopid: &KOpId,
|
||||
client_auth_info: ClientAuthInfo,
|
||||
domain_info: DomainInfoRead,
|
||||
) -> Result<(ScimPerson, ScimEffectiveAccess), ErrorResponse> {
|
||||
) -> Result<(ScimPerson, ScimEffectiveAccess), WebError> {
|
||||
let scim_entry: ScimEntryKanidm = state
|
||||
.qe_r_ref
|
||||
.scim_entry_id_get(
|
||||
|
@ -138,13 +136,12 @@ async fn get_person_info(
|
|||
ext_access_check: true,
|
||||
},
|
||||
)
|
||||
.map_err(|op_err| HtmxError::new(kopid, op_err, domain_info.clone()))
|
||||
.await?;
|
||||
|
||||
if let Some(personinfo_info) = scimentry_into_personinfo(scim_entry) {
|
||||
Ok(personinfo_info)
|
||||
} else {
|
||||
Err(HtmxError::new(kopid, OperationError::InvalidState, domain_info.clone()).into())
|
||||
Err(WebError::from(OperationError::InvalidState))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,8 +149,7 @@ async fn get_persons_info(
|
|||
state: ServerState,
|
||||
kopid: &KOpId,
|
||||
client_auth_info: ClientAuthInfo,
|
||||
domain_info: DomainInfoRead,
|
||||
) -> Result<Vec<(ScimPerson, ScimEffectiveAccess)>, ErrorResponse> {
|
||||
) -> Result<Vec<(ScimPerson, ScimEffectiveAccess)>, WebError> {
|
||||
let filter = ScimFilter::Equal(Attribute::Class.into(), EntryClass::Person.into());
|
||||
|
||||
let base: Vec<ScimEntryKanidm> = state
|
||||
|
@ -167,7 +163,6 @@ async fn get_persons_info(
|
|||
ext_access_check: true,
|
||||
},
|
||||
)
|
||||
.map_err(|op_err| HtmxError::new(kopid, op_err, domain_info.clone()))
|
||||
.await?;
|
||||
|
||||
// TODO: inefficient to sort here
|
||||
|
|
|
@ -9,17 +9,17 @@ pub(crate) enum ProfileMenuItems {
|
|||
UnixPassword,
|
||||
}
|
||||
|
||||
pub(crate) enum UiMessage {
|
||||
UnlockEdit,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for UiMessage {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
UiMessage::UnlockEdit => write!(f, "Unlock Edit 🔒"),
|
||||
}
|
||||
}
|
||||
}
|
||||
// pub(crate) enum UiMessage {
|
||||
// UnlockEdit,
|
||||
// }
|
||||
//
|
||||
// impl std::fmt::Display for UiMessage {
|
||||
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
// match self {
|
||||
// UiMessage::UnlockEdit => write!(f, "Unlock Edit 🔒"),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
pub(crate) enum Urls {
|
||||
Apps,
|
||||
|
|
|
@ -56,6 +56,7 @@ pub fn view_router() -> Router<ServerState> {
|
|||
.route("/reset", get(reset::view_reset_get))
|
||||
.route("/update_credentials", get(reset::view_self_reset_get))
|
||||
.route("/profile", get(profile::view_profile_get))
|
||||
.route("/profile/diff", get(profile::view_profile_get))
|
||||
.route("/profile/unlock", get(profile::view_profile_unlock_get))
|
||||
.route("/logout", get(login::view_logout_get))
|
||||
.route("/oauth2", get(oauth2::view_index_get));
|
||||
|
@ -129,6 +130,18 @@ pub fn view_router() -> Router<ServerState> {
|
|||
)
|
||||
.route("/api/cu_cancel", post(reset::cancel_cred_update))
|
||||
.route("/api/cu_commit", post(reset::commit))
|
||||
.route(
|
||||
"/api/user_settings/add_email",
|
||||
post(profile::view_new_email_entry_partial),
|
||||
)
|
||||
.route(
|
||||
"/api/user_settings/edit_profile",
|
||||
post(profile::view_profile_diff_start_save_post),
|
||||
)
|
||||
.route(
|
||||
"/api/user_settings/confirm_profile",
|
||||
post(profile::view_profile_diff_confirm_save_post),
|
||||
)
|
||||
.layer(HxRequestGuardLayer::new("/ui"));
|
||||
|
||||
let admin_router = admin_router();
|
||||
|
|
|
@ -1,18 +1,33 @@
|
|||
use kanidm_proto::attribute::Attribute;
|
||||
use super::constants::{ProfileMenuItems, Urls};
|
||||
use super::errors::HtmxError;
|
||||
use super::login::{LoginDisplayCtx, Reauth, ReauthPurpose};
|
||||
use super::navbar::NavbarCtx;
|
||||
use crate::https::errors::WebError;
|
||||
use crate::https::extractors::{DomainInfo, VerifiedClientInformation};
|
||||
use crate::https::middleware::KOpId;
|
||||
use crate::https::ServerState;
|
||||
use askama::Template;
|
||||
use askama_axum::IntoResponse;
|
||||
use axum::extract::State;
|
||||
use axum::http::Uri;
|
||||
use axum::response::Response;
|
||||
use axum::Extension;
|
||||
use axum_extra::extract::cookie::CookieJar;
|
||||
use axum_extra::extract::Form;
|
||||
use axum_htmx::{HxEvent, HxPushUrl, HxResponseTrigger};
|
||||
use futures_util::TryFutureExt;
|
||||
use kanidm_proto::constants::{ATTR_DISPLAYNAME, ATTR_MAIL};
|
||||
use kanidm_proto::internal::UserAuthToken;
|
||||
|
||||
use super::constants::{ProfileMenuItems, UiMessage, Urls};
|
||||
use super::errors::HtmxError;
|
||||
use super::login::{LoginDisplayCtx, Reauth, ReauthPurpose};
|
||||
use super::navbar::NavbarCtx;
|
||||
use kanidm_proto::scim_v1::server::{ScimEffectiveAccess, ScimPerson};
|
||||
use kanidmd_lib::filter::{f_id, Filter};
|
||||
use kanidmd_lib::prelude::f_and;
|
||||
use kanidmd_lib::prelude::FC;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::fmt;
|
||||
use std::fmt::Display;
|
||||
use std::fmt::Formatter;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "user_settings.html")]
|
||||
|
@ -26,9 +41,46 @@ pub(crate) struct ProfileView {
|
|||
struct ProfilePartialView {
|
||||
menu_active_item: ProfileMenuItems,
|
||||
can_rw: bool,
|
||||
person: ScimPerson,
|
||||
scim_effective_access: ScimEffectiveAccess,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub(crate) struct ProfileAttributes {
|
||||
#[serde(rename = "name")]
|
||||
account_name: String,
|
||||
#[serde(rename = "displayname")]
|
||||
display_name: String,
|
||||
email: Option<String>,
|
||||
#[serde(rename = "emails[]")]
|
||||
emails: Vec<String>,
|
||||
// radio buttons are used to pick a primary index
|
||||
primary_email_index: u16,
|
||||
}
|
||||
|
||||
#[derive(Template, Clone)]
|
||||
#[template(path = "user_settings/profile_changes_partial.html")]
|
||||
struct ProfileChangesPartialView {
|
||||
menu_active_item: ProfileMenuItems,
|
||||
can_rw: bool,
|
||||
person: ScimPerson,
|
||||
new_attrs: ProfileAttributes,
|
||||
}
|
||||
|
||||
#[derive(Template, Clone)]
|
||||
#[template(path = "user_settings/form_modifiable_entry_modifiable_list_partial.html")]
|
||||
// Modifiable entry in a modifiable list partial
|
||||
pub(crate) struct FormModEntryModListPartial {
|
||||
can_rw: bool,
|
||||
r#type: String,
|
||||
name: String,
|
||||
value: String,
|
||||
invalid_feedback: String,
|
||||
}
|
||||
|
||||
impl Display for ProfileAttributes {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn view_profile_get(
|
||||
|
@ -39,9 +91,16 @@ pub(crate) async fn view_profile_get(
|
|||
) -> Result<ProfileView, WebError> {
|
||||
let uat: UserAuthToken = state
|
||||
.qe_r_ref
|
||||
.handle_whoami_uat(client_auth_info, kopid.eventid)
|
||||
.handle_whoami_uat(client_auth_info.clone(), kopid.eventid)
|
||||
.await?;
|
||||
|
||||
let (scim_person, scim_effective_access) = crate::https::views::admin::persons::get_person_info(
|
||||
uat.uuid,
|
||||
state,
|
||||
&kopid,
|
||||
client_auth_info.clone()).await?;
|
||||
|
||||
|
||||
let time = time::OffsetDateTime::now_utc() + time::Duration::new(60, 0);
|
||||
|
||||
let can_rw = uat.purpose_readwrite_active(time);
|
||||
|
@ -52,13 +111,152 @@ pub(crate) async fn view_profile_get(
|
|||
profile_partial: ProfilePartialView {
|
||||
menu_active_item: ProfileMenuItems::UserProfile,
|
||||
can_rw,
|
||||
account_name: uat.name().to_string(),
|
||||
display_name: uat.displayname.clone(),
|
||||
email: uat.mail_primary.clone(),
|
||||
person: scim_person,
|
||||
scim_effective_access
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn view_profile_diff_start_save_post(
|
||||
State(state): State<ServerState>,
|
||||
Extension(kopid): Extension<KOpId>,
|
||||
VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
|
||||
DomainInfo(domain_info): DomainInfo,
|
||||
// Form must be the last parameter because it consumes the request body
|
||||
Form(new_attrs): Form<ProfileAttributes>,
|
||||
) -> axum::response::Result<Response> {
|
||||
let uat: UserAuthToken = state
|
||||
.qe_r_ref
|
||||
.handle_whoami_uat(client_auth_info.clone(), kopid.eventid)
|
||||
.map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))
|
||||
.await?;
|
||||
|
||||
let time = time::OffsetDateTime::now_utc() + time::Duration::new(60, 0);
|
||||
let can_rw = uat.purpose_readwrite_active(time);
|
||||
// TODO: A bit overkill to request scimEffectiveAccess here.
|
||||
let (scim_person, _) = crate::https::views::admin::persons::get_person_info(
|
||||
uat.uuid,
|
||||
state,
|
||||
&kopid,
|
||||
client_auth_info.clone()).await?;
|
||||
|
||||
let profile_view = ProfileChangesPartialView {
|
||||
menu_active_item: ProfileMenuItems::UserProfile,
|
||||
can_rw,
|
||||
person: scim_person,
|
||||
new_attrs
|
||||
};
|
||||
|
||||
Ok((
|
||||
HxPushUrl(Uri::from_static("/ui/profile/diff")),
|
||||
profile_view,
|
||||
)
|
||||
.into_response())
|
||||
}
|
||||
|
||||
pub(crate) async fn view_profile_diff_confirm_save_post(
|
||||
State(state): State<ServerState>,
|
||||
Extension(kopid): Extension<KOpId>,
|
||||
VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
|
||||
DomainInfo(domain_info): DomainInfo,
|
||||
// Form must be the last parameter because it consumes the request body
|
||||
Form(new_attrs): Form<ProfileAttributes>,
|
||||
) -> axum::response::Result<Response> {
|
||||
let uat: UserAuthToken = state
|
||||
.qe_r_ref
|
||||
.handle_whoami_uat(client_auth_info.clone(), kopid.eventid)
|
||||
.map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))
|
||||
.await?;
|
||||
dbg!(&new_attrs);
|
||||
|
||||
let filter = filter_all!(f_and!([f_id(uat.uuid.to_string().as_str())]));
|
||||
|
||||
state
|
||||
.qe_w_ref
|
||||
.handle_setattribute(
|
||||
client_auth_info.clone(),
|
||||
uat.uuid.to_string(),
|
||||
ATTR_DISPLAYNAME.to_string(),
|
||||
vec![new_attrs.display_name],
|
||||
filter.clone(),
|
||||
kopid.eventid,
|
||||
)
|
||||
.map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))
|
||||
.await?;
|
||||
|
||||
state
|
||||
.qe_w_ref
|
||||
.handle_setattribute(
|
||||
client_auth_info.clone(),
|
||||
uat.uuid.to_string(),
|
||||
ATTR_MAIL.to_string(),
|
||||
new_attrs.emails,
|
||||
filter.clone(),
|
||||
kopid.eventid,
|
||||
)
|
||||
.map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))
|
||||
.await?;
|
||||
|
||||
// TODO: These are normally not permitted, user should be prevented from changing non modifiable fields in the UI though
|
||||
// state
|
||||
// .qe_w_ref
|
||||
// .handle_setattribute(
|
||||
// client_auth_info.clone(),
|
||||
// uat.uuid.to_string(),
|
||||
// ATTR_EMAIL.to_string(),
|
||||
// vec![new_attrs.email.unwrap_or("".to_string())],
|
||||
// filter.clone(),
|
||||
// kopid.eventid,
|
||||
// )
|
||||
// .map_err(|op_err| HtmxError::new(&kopid, op_err))
|
||||
// .await?;
|
||||
//
|
||||
// state
|
||||
// .qe_w_ref
|
||||
// .handle_setattribute(
|
||||
// client_auth_info.clone(),
|
||||
// uat.uuid.to_string(),
|
||||
// ATTR_NAME.to_string(),
|
||||
// vec![new_attrs.account_name],
|
||||
// filter.clone(),
|
||||
// kopid.eventid,
|
||||
// )
|
||||
// .map_err(|op_err| HtmxError::new(&kopid, op_err))
|
||||
// .await?;
|
||||
|
||||
// TODO: Calling this here returns the old attributes
|
||||
match view_profile_get(
|
||||
State(state),
|
||||
Extension(kopid),
|
||||
VerifiedClientInformation(client_auth_info),
|
||||
DomainInfo(domain_info)
|
||||
).await {
|
||||
Ok(pv) => Ok(pv.into_response()),
|
||||
Err(e) => Ok(e.into_response()),
|
||||
}
|
||||
}
|
||||
|
||||
// Sends the user a new email input to fill in :)
|
||||
pub(crate) async fn view_new_email_entry_partial(
|
||||
State(_state): State<ServerState>,
|
||||
VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
|
||||
Extension(_kopid): Extension<KOpId>,
|
||||
) -> axum::response::Result<Response> {
|
||||
let add_email_trigger =
|
||||
HxResponseTrigger::after_swap([HxEvent::new("addEmailSwapped".to_string())]);
|
||||
Ok((
|
||||
add_email_trigger,
|
||||
FormModEntryModListPartial {
|
||||
can_rw: true,
|
||||
r#type: "email".to_string(),
|
||||
name: "emails[]".to_string(),
|
||||
value: "".to_string(),
|
||||
invalid_feedback: "Please enter a valid email address.".to_string(),
|
||||
},
|
||||
)
|
||||
.into_response())
|
||||
}
|
||||
|
||||
pub(crate) async fn view_profile_unlock_get(
|
||||
State(state): State<ServerState>,
|
||||
VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
|
||||
|
|
|
@ -112,19 +112,19 @@ impl IntervalActor {
|
|||
if !op.exists() {
|
||||
info!(
|
||||
"Online backup output folder '{}' does not exist, trying to create it.",
|
||||
outpath
|
||||
outpath.display()
|
||||
);
|
||||
fs::create_dir_all(&outpath).map_err(|e| {
|
||||
error!(
|
||||
"Online backup failed to create output directory '{}': {}",
|
||||
outpath.clone(),
|
||||
outpath.display(),
|
||||
e
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
if !op.is_dir() {
|
||||
error!("Online backup output '{}' is not a directory or we are missing permissions to access it.", outpath);
|
||||
error!("Online backup output '{}' is not a directory or we are missing permissions to access it.", outpath.display());
|
||||
return Err(());
|
||||
}
|
||||
|
||||
|
@ -148,7 +148,7 @@ impl IntervalActor {
|
|||
if let Err(e) = server
|
||||
.handle_online_backup(
|
||||
OnlineBackupEvent::new(),
|
||||
outpath.clone().as_str(),
|
||||
&outpath,
|
||||
versions,
|
||||
)
|
||||
.await
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
use std::net;
|
||||
use std::pin::Pin;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::actors::QueryServerReadV1;
|
||||
use crate::CoreAction;
|
||||
use futures_util::sink::SinkExt;
|
||||
use futures_util::stream::StreamExt;
|
||||
use kanidmd_lib::idm::ldap::{LdapBoundToken, LdapResponseState};
|
||||
|
@ -10,13 +7,15 @@ use kanidmd_lib::prelude::*;
|
|||
use ldap3_proto::proto::LdapMsg;
|
||||
use ldap3_proto::LdapCodec;
|
||||
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_openssl::SslStream;
|
||||
use tokio_util::codec::{FramedRead, FramedWrite};
|
||||
|
||||
use crate::CoreAction;
|
||||
use tokio::sync::broadcast;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_openssl::SslStream;
|
||||
use tokio_util::codec::{FramedRead, FramedWrite};
|
||||
|
||||
struct LdapSession {
|
||||
uat: Option<LdapBoundToken>,
|
||||
|
@ -49,28 +48,14 @@ async fn client_process_msg(
|
|||
.await
|
||||
}
|
||||
|
||||
async fn client_process(
|
||||
tcpstream: TcpStream,
|
||||
tls_acceptor: SslAcceptor,
|
||||
async fn client_process<STREAM>(
|
||||
stream: STREAM,
|
||||
client_address: 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))
|
||||
) where
|
||||
STREAM: AsyncRead + AsyncWrite,
|
||||
{
|
||||
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 (r, w) = tokio::io::split(stream);
|
||||
let mut r = FramedRead::new(r, LdapCodec::default());
|
||||
let mut w = FramedWrite::new(w, LdapCodec::default());
|
||||
|
||||
|
@ -126,7 +111,32 @@ async fn client_process(
|
|||
}
|
||||
}
|
||||
|
||||
/// TLS LDAP Listener, hands off to [client_process]
|
||||
async fn client_tls_accept(
|
||||
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(
|
||||
listener: TcpListener,
|
||||
mut tls_acceptor: SslAcceptor,
|
||||
|
@ -145,10 +155,10 @@ async fn ldap_tls_acceptor(
|
|||
match accept_result {
|
||||
Ok((tcpstream, client_socket_addr)) => {
|
||||
let clone_tls_acceptor = tls_acceptor.clone();
|
||||
tokio::spawn(client_process(tcpstream, clone_tls_acceptor, client_socket_addr, qe_r_ref));
|
||||
tokio::spawn(client_tls_accept(tcpstream, clone_tls_acceptor, client_socket_addr, qe_r_ref));
|
||||
}
|
||||
Err(e) => {
|
||||
error!("LDAP acceptor error, continuing -> {:?}", e);
|
||||
Err(err) => {
|
||||
warn!(?err, "LDAP acceptor error, continuing");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -161,6 +171,34 @@ async fn ldap_tls_acceptor(
|
|||
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(
|
||||
address: &str,
|
||||
opt_ssl_acceptor: Option<SslAcceptor>,
|
||||
|
@ -197,10 +235,7 @@ pub(crate) async fn create_ldap_server(
|
|||
tls_acceptor_reload_rx,
|
||||
))
|
||||
}
|
||||
None => {
|
||||
error!("The server won't run without TLS!");
|
||||
return Err(());
|
||||
}
|
||||
None => tokio::spawn(ldap_plaintext_acceptor(listener, qe_r_ref, rx)),
|
||||
};
|
||||
|
||||
info!("Created LDAP interface");
|
||||
|
|
|
@ -36,9 +36,10 @@ mod ldaps;
|
|||
mod repl;
|
||||
mod utils;
|
||||
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::actors::{QueryServerReadV1, QueryServerWriteV1};
|
||||
use crate::admin::AdminActor;
|
||||
use crate::config::{Configuration, ServerRole};
|
||||
use crate::interval::IntervalActor;
|
||||
use crate::utils::touch_file_or_quit;
|
||||
use compact_jwt::{JwsHs256Signer, JwsSigner};
|
||||
use kanidm_proto::internal::OperationError;
|
||||
|
@ -50,17 +51,14 @@ use kanidmd_lib::status::StatusActor;
|
|||
use kanidmd_lib::value::CredentialType;
|
||||
#[cfg(not(target_family = "windows"))]
|
||||
use libc::umask;
|
||||
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::broadcast;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::Notify;
|
||||
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
|
||||
|
||||
fn setup_backend(config: &Configuration, schema: &Schema) -> Result<Backend, OperationError> {
|
||||
|
@ -80,7 +78,7 @@ fn setup_backend_vacuum(
|
|||
let pool_size: u32 = config.threads as u32;
|
||||
|
||||
let cfg = BackendConfig::new(
|
||||
config.db_path.as_str(),
|
||||
config.db_path.as_deref(),
|
||||
pool_size,
|
||||
config.db_fs_type.unwrap_or_default(),
|
||||
config.db_arc_size,
|
||||
|
@ -335,7 +333,7 @@ pub fn dbscan_restore_quarantined_core(config: &Configuration, id: u64) {
|
|||
};
|
||||
}
|
||||
|
||||
pub fn backup_server_core(config: &Configuration, dst_path: &str) {
|
||||
pub fn backup_server_core(config: &Configuration, dst_path: &Path) {
|
||||
let schema = match Schema::new() {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
|
@ -371,8 +369,11 @@ pub fn backup_server_core(config: &Configuration, dst_path: &str) {
|
|||
// Let the txn abort, even on success.
|
||||
}
|
||||
|
||||
pub async fn restore_server_core(config: &Configuration, dst_path: &str) {
|
||||
touch_file_or_quit(config.db_path.as_str());
|
||||
pub async fn restore_server_core(config: &Configuration, dst_path: &Path) {
|
||||
// If it's an in memory database, we don't need to touch anything
|
||||
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.
|
||||
let schema = match Schema::new() {
|
||||
|
@ -1011,7 +1012,7 @@ pub async fn create_server_core(
|
|||
let tls_accepter_reload_task_notify = tls_acceptor_reload_notify.clone();
|
||||
let tls_config = config.tls_config.clone();
|
||||
|
||||
let ldap_configured = config.ldapaddress.is_some();
|
||||
let ldap_configured = config.ldapbindaddress.is_some();
|
||||
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);
|
||||
|
||||
|
@ -1076,12 +1077,10 @@ pub async fn create_server_core(
|
|||
};
|
||||
|
||||
// If we have been requested to init LDAP, configure it now.
|
||||
let maybe_ldap_acceptor_handle = match &config.ldapaddress {
|
||||
let maybe_ldap_acceptor_handle = match &config.ldapbindaddress {
|
||||
Some(la) => {
|
||||
let opt_ldap_ssl_acceptor = maybe_tls_acceptor.clone();
|
||||
|
||||
if !config_test {
|
||||
// ⚠️ only start the sockets and listeners in non-config-test modes.
|
||||
let h = ldaps::create_ldap_server(
|
||||
la.as_str(),
|
||||
opt_ldap_ssl_acceptor,
|
||||
|
@ -1091,9 +1090,6 @@ pub async fn create_server_core(
|
|||
)
|
||||
.await?;
|
||||
Some(h)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
None => {
|
||||
debug!("LDAP not requested, skipping");
|
||||
|
|
|
@ -1,32 +1,39 @@
|
|||
use filetime::FileTime;
|
||||
use std::fs::File;
|
||||
use std::io::ErrorKind;
|
||||
use std::path::PathBuf;
|
||||
use std::path::Path;
|
||||
use std::time::SystemTime;
|
||||
|
||||
pub fn touch_file_or_quit(file_path: &str) {
|
||||
pub fn touch_file_or_quit<P: AsRef<Path>>(file_path: P) {
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
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());
|
||||
match filetime::set_file_times(file_path, t, t) {
|
||||
Ok(_) => debug!(
|
||||
"Successfully touched existing file {}, can continue",
|
||||
file_path
|
||||
file_path.display()
|
||||
),
|
||||
Err(e) => {
|
||||
match e.kind() {
|
||||
ErrorKind::PermissionDenied => {
|
||||
// we bail here because you won't be able to write them back...
|
||||
error!("Permission denied writing to {}, quitting.", file_path)
|
||||
error!(
|
||||
"Permission denied writing to {}, quitting.",
|
||||
file_path.display()
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
error!(
|
||||
"Failed to write to {} due to error: {:?} ... quitting.",
|
||||
file_path, e
|
||||
file_path.display(),
|
||||
e
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -35,11 +42,12 @@ pub fn touch_file_or_quit(file_path: &str) {
|
|||
}
|
||||
} else {
|
||||
match File::create(file_path) {
|
||||
Ok(_) => debug!("Successfully touched new file {}", file_path),
|
||||
Ok(_) => debug!("Successfully touched new file {}", file_path.display()),
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Failed to write to {} due to error: {:?} ... quitting.",
|
||||
file_path, e
|
||||
file_path.display(),
|
||||
e
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
|
25
server/core/static/external/forms.js
vendored
Normal file
25
server/core/static/external/forms.js
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
// This file will contain js helpers to have some interactivity on forms that we can't achieve with pure html.
|
||||
function rehook_string_list_removers() {
|
||||
const buttons = document.getElementsByClassName("kanidm-remove-list-entry");
|
||||
for (let i = 0; i < buttons.length; i++) {
|
||||
const button = buttons.item(i)
|
||||
if (button.getAttribute("kanidm_hooked") !== null) continue
|
||||
|
||||
button.addEventListener("click", (e) => {
|
||||
// Expected html nesting: li > div.input-group > button.kanidm-remove-list-entry
|
||||
let li = button.parentElement?.parentElement;
|
||||
if (li && li.tagName === "LI") {
|
||||
li.remove();
|
||||
}
|
||||
})
|
||||
button.setAttribute("kanidm_hooked", "")
|
||||
}
|
||||
}
|
||||
|
||||
window.onload = function () {
|
||||
rehook_string_list_removers();
|
||||
document.body.addEventListener("addEmailSwapped", () => {
|
||||
rehook_string_list_removers();
|
||||
})
|
||||
};
|
||||
|
34
server/core/static/external/htmx_bs_validation.js
vendored
Normal file
34
server/core/static/external/htmx_bs_validation.js
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
htmx.defineExtension("bs-validation", {
|
||||
onEvent: function (name, evt) {
|
||||
let form = evt.detail.elt;
|
||||
// Htmx propagates attributes onto children like button, which would break those buttons, so we return if not a form.
|
||||
if (form.tagName !== "FORM") return;
|
||||
|
||||
// check if trigger attribute and submit event exists
|
||||
// for the form
|
||||
if (!form.hasAttribute("hx-trigger")) {
|
||||
// set trigger for custom event bs-send
|
||||
form.setAttribute("hx-trigger", "bs-send");
|
||||
|
||||
// and attach the event only once
|
||||
form.addEventListener("submit", function (event) {
|
||||
if (form.checkValidity()) {
|
||||
// trigger custom event hx-trigger="bs-send"
|
||||
htmx.trigger(form, "bsSend");
|
||||
}
|
||||
|
||||
// focus the first :invalid field
|
||||
let invalidField = form.querySelector(":invalid");
|
||||
if (invalidField) {
|
||||
invalidField.focus();
|
||||
}
|
||||
|
||||
console.log("prevented htmx send, form was invalid")
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
form.classList.add("was-validated")
|
||||
}, false)
|
||||
}
|
||||
}
|
||||
});
|
|
@ -29,6 +29,7 @@ body {
|
|||
display: block !important;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Sidebar
|
||||
*/
|
||||
|
@ -206,3 +207,7 @@ footer {
|
|||
height: var(--icon-size);
|
||||
transform: rotate(35deg);
|
||||
}
|
||||
|
||||
.cursor-pointer:hover {
|
||||
cursor: pointer;
|
||||
}
|
|
@ -3,6 +3,8 @@
|
|||
(% block title %)Profile(% endblock %)
|
||||
|
||||
(% block head %)
|
||||
<script src="/pkg/external/forms.js"></script>
|
||||
<script src="/pkg/external/htmx_bs_validation.js"></script>
|
||||
(% endblock %)
|
||||
|
||||
(% block main %)
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<li class="mb-2">
|
||||
<div class="input-group">
|
||||
<input type="(( type ))" class="form-control" name="(( name ))" value="(( value ))" hx-validate="true" required>
|
||||
(% if can_rw %)<button type="button" class="btn btn-secondary kanidm-remove-list-entry">Remove</button>(% endif %)
|
||||
</div>
|
||||
<div class="invalid-feedback">(( invalid_feedback ))</div>
|
||||
</li>
|
|
@ -0,0 +1,79 @@
|
|||
(% extends "user_settings_partial_base.html" %)
|
||||
|
||||
(% block selected_setting_group %)
|
||||
Profile Difference
|
||||
(% endblock %)
|
||||
|
||||
(% block settings_vertical_point %)lg(% endblock %)
|
||||
|
||||
(% block settings_window %)
|
||||
|
||||
<form>
|
||||
<input type="hidden" name="account_name" value="(( new_attrs.account_name ))"/>
|
||||
<input type="hidden" name="display_name" value="(( new_attrs.display_name ))"/>
|
||||
<!-- <input type="hidden" name="legal_name" value=" new_attrs.legal_name "/>-->
|
||||
(% for email in new_attrs.emails %)
|
||||
<input type="hidden" name="emails[]" value="(( email ))"/>
|
||||
(% endfor %)
|
||||
|
||||
<table class="table table-bordered table-responsive">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Attribute</th>
|
||||
<th scope="col">Old value</th>
|
||||
<th scope="col">New value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
(% if person.name != new_attrs.account_name %)
|
||||
<tr>
|
||||
<th scope="row">Username</th>
|
||||
<td class="text-break">(( person.name ))</td>
|
||||
<td class="text-break">(( new_attrs.account_name ))</td>
|
||||
</tr>
|
||||
(% endif %)
|
||||
|
||||
(% if person.displayname != new_attrs.display_name %)
|
||||
<tr>
|
||||
<th scope="row">Display name</th>
|
||||
<td class="text-break">(( person.displayname ))</td>
|
||||
<td class="text-break">(( new_attrs.display_name ))</td>
|
||||
</tr>
|
||||
(% endif %)
|
||||
|
||||
|
||||
<!-- TODO: List new items with +, same items with . -->
|
||||
<tr>
|
||||
<th scope="row">Emails</th>
|
||||
<td class="text-break">
|
||||
<ul>
|
||||
(% for email in person.mails %)
|
||||
<li>(( email.value ))</li>
|
||||
(% endfor %)
|
||||
</ul>
|
||||
</td>
|
||||
<td class="text-break">
|
||||
<ul>
|
||||
(% for email in new_attrs.emails %)
|
||||
<li>(( email ))</li>
|
||||
(% endfor %)
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- Edit button -->
|
||||
<div class="pt-4" hx-target="#user_settings_container" hx-swap="outerHTML">
|
||||
(% if can_rw %)
|
||||
<button class="btn btn-danger" type="button" hx-get="/ui/profile" hx-target="#user_settings_container" hx-swap="outerHTML">Discard Changes</button>
|
||||
<button class="btn btn-primary" type="button" hx-post="/ui/api/user_settings/confirm_profile" hx-target="#user_settings_container" hx-swap="outerHTML">Confirm Changes</button>
|
||||
(% else %)
|
||||
<a href="/ui/profile/unlock" hx-boost="false">
|
||||
<!-- TODO: at the moment, session expiring here means progress is lost. Do we just show an error screen ? We can't pass the update state through the reauth session, and we don't have profile update sessions like cred update. -->
|
||||
<button class="btn btn-primary" type="button">Unlock Confirm 🔓</button>
|
||||
</a>
|
||||
(% endif %)
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
(% endblock %)
|
||||
|
|
@ -4,40 +4,81 @@
|
|||
Profile
|
||||
(% endblock %)
|
||||
|
||||
(% macro string_attr(dispname, name, value, editable, attribute) %)
|
||||
(% if scim_effective_access.search.check(attribute|as_ref) %)
|
||||
<div class="row g-0 mt-3">
|
||||
<label for="person(( name ))" class="col-12 col-md-3 col-lg-2 col-form-label fw-bold py-0">(( dispname ))</label>
|
||||
<div class="col-12 col-md-8 col-lg-6">
|
||||
(% if scim_effective_access.modify_present.check(attribute|as_ref) %)
|
||||
<input class="form-control py-0" id="person(( name ))" name="(( name ))" value="(( value ))">
|
||||
(% else %)
|
||||
<input readonly class="form-control-plaintext py-0" id="person(( name ))" name="(( name ))" value="(( value ))">
|
||||
(% endif %)
|
||||
</div>
|
||||
</div>
|
||||
(% endif %)
|
||||
(% endmacro %)
|
||||
|
||||
(% block settings_window %)
|
||||
|
||||
<form>
|
||||
<div class="mb-2 row">
|
||||
<label for="profileUserName" class="col-12 col-md-3 col-xl-2 col-form-label">User name</label>
|
||||
<div class="col-12 col-md-6 col-lg-5">
|
||||
<input type="text" readonly class="form-control-plaintext" id="profileUserName" value="(( account_name ))">
|
||||
<form id="user_settings_container" class="needs-validation" hx-post="/ui/api/user_settings/edit_profile"
|
||||
hx-target="#user_settings_container" hx-swap="outerHTML" hx-validate="true" hx-ext="bs-validation" novalidate>
|
||||
(% call string_attr("Name", "name", person.name, true, Attribute::Name) %)
|
||||
|
||||
(% call string_attr("Displayname", "displayname", person.displayname, true, Attribute::DisplayName) %)
|
||||
|
||||
<div class="mb-2">
|
||||
<div class="mt-3 mb-2 col-12 col-md-11 col-lg-8">
|
||||
<label for="profileEmail" class="fw-bold">Email addresses (select primary)</label>
|
||||
(% if can_rw %)
|
||||
<a class="cursor-pointer float-end" hx-boost="true" hx-post="/ui/api/user_settings/add_email" hx-target="#emailAddresses"
|
||||
hx-swap="beforeend">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-plus-square"
|
||||
viewBox="0 0 16 16" width="20" height="20">
|
||||
<path d="M14 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2z"></path>
|
||||
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
(% endif %)
|
||||
<div>
|
||||
<div class="row g-0">
|
||||
<div class="col-12 col-md-11 col-lg-8" id="emailAddresses">
|
||||
(% for (i, email) in person.mails.iter().enumerate() %)
|
||||
(% let type = "email" %)
|
||||
(% let name = "emails[]" %)
|
||||
(% let value = email.value.clone() %)
|
||||
(% let invalid_feedback = "Please enter a valid email address." %)
|
||||
|
||||
<div class="input-group mb-1">
|
||||
<div class="input-group-text">
|
||||
<input class="form-check-input mt-0" name="primary_email_index" type="radio" value="((i))" aria-label="Primary email radio button for following text input">
|
||||
</div>
|
||||
|
||||
<div class="mb-2 row">
|
||||
<label for="profileDisplayName" class="col-12 col-md-3 col-xl-2 col-form-label">Display name</label>
|
||||
<div class="col-12 col-md-6 col-lg-5">
|
||||
<input type="text" class="form-control-plaintext" id="profileDisplayName" value="(( display_name ))" disabled>
|
||||
</div>
|
||||
</div>
|
||||
<input type="(( type ))" class="form-control" name="(( name ))" value="(( value ))" hx-validate="true" required>
|
||||
(% if can_rw %)
|
||||
<button type="button" class="btn btn-secondary kanidm-remove-list-entry">Remove</button>
|
||||
(% endif %)
|
||||
|
||||
<div class="mb-2 row">
|
||||
<label for="profileEmail" class="col-12 col-md-3 col-xl-2 col-form-label">Email</label>
|
||||
<div class="col-12 col-md-6 col-lg-5">
|
||||
<input type="email" disabled class="form-control-plaintext" id="profileEmail" value="(( email.clone().unwrap_or("None configured".to_string())))">
|
||||
</div>
|
||||
<div class="invalid-feedback">(( invalid_feedback ))</div>
|
||||
|
||||
(% endfor %)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit button -->
|
||||
<!-- <div class="pt-4">
|
||||
<div class="pt-4">
|
||||
(% if can_rw %)
|
||||
<button class="btn btn-primary" type="button" hx-post="/ui/api/user_settings/edit_profile" disabled>Edit (Currently Not Working!)</button>
|
||||
<button class="btn btn-primary" type="submit">Save</button>
|
||||
(% else %)
|
||||
<a href=(Urls::ProfileUnlock) hx-boost="false">
|
||||
<button class="btn btn-primary" type="button">((UiMessage::UnlockEdit))</button>
|
||||
<a href="/ui/profile/unlock" hx-boost="false">
|
||||
<button class="btn btn-primary" type="button">Unlock Edit 🔒</button>
|
||||
</a>
|
||||
(% endif %)
|
||||
</div> -->
|
||||
</div>
|
||||
</form>
|
||||
|
||||
(% endblock %)
|
||||
|
|
|
@ -57,6 +57,31 @@ clap = { workspace = true, features = ["derive"] }
|
|||
clap_complete = { 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]
|
||||
ignored = ["clap_complete", "kanidm_build_profiles"]
|
||||
|
|
2
server/daemon/debian/group.conf
Normal file
2
server/daemon/debian/group.conf
Normal file
|
@ -0,0 +1,2 @@
|
|||
# This is a sysusers.d format config, please refer to man sysusers.d(5)
|
||||
g kanidmd -
|
|
@ -10,13 +10,15 @@ Before=radiusd.service
|
|||
[Service]
|
||||
Type=notify
|
||||
DynamicUser=yes
|
||||
StateDirectory=kanidm
|
||||
User=kanidmd_dyn
|
||||
Group=kanidmd
|
||||
StateDirectory=kanidmd
|
||||
StateDirectoryMode=0750
|
||||
CacheDirectory=kanidmd
|
||||
CacheDirectoryMode=0750
|
||||
RuntimeDirectory=kanidmd
|
||||
RuntimeDirectoryMode=0755
|
||||
ExecStart=/usr/sbin/kanidmd server -c /etc/kanidm/server.toml
|
||||
ExecStart=/usr/bin/kanidmd server
|
||||
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
38
server/daemon/debian/postinst
Normal file
38
server/daemon/debian/postinst
Normal file
|
@ -0,0 +1,38 @@
|
|||
#!/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
|
51
server/daemon/debian/server.toml
Normal file
51
server/daemon/debian/server.toml
Normal file
|
@ -0,0 +1,51 @@
|
|||
# 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 * * *"
|
|
@ -1,3 +1,4 @@
|
|||
version = "2"
|
||||
bindaddress = "[::]:8443"
|
||||
ldapbindaddress = "127.0.0.1:3636"
|
||||
|
|
@ -22,14 +22,16 @@ fi
|
|||
|
||||
mkdir -p "${KANI_TMP}"/client_ca
|
||||
|
||||
CONFIG_FILE=${CONFIG_FILE:="${SCRIPT_DIR}/../../examples/insecure_server.toml"}
|
||||
CONFIG_FILE=${CONFIG_FILE:="${SCRIPT_DIR}/insecure_server.toml"}
|
||||
|
||||
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})."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
pushd "${SCRIPT_DIR}" > /dev/null 2>&1
|
||||
# Save current directory and change to script directory without pushd
|
||||
OLD_DIR=$(pwd)
|
||||
cd "${SCRIPT_DIR}" || exit 1
|
||||
if [ -n "${1}" ]; then
|
||||
COMMAND=$*
|
||||
#shellcheck disable=SC2086
|
||||
|
@ -40,4 +42,4 @@ else
|
|||
#shellcheck disable=SC2086
|
||||
cargo run ${KANI_CARGO_OPTS} --bin kanidmd -- server -c "${CONFIG_FILE}"
|
||||
fi
|
||||
popd > /dev/null 2>&1
|
||||
cd "${OLD_DIR}" || exit 1
|
||||
|
|
|
@ -37,7 +37,7 @@ use kanidmd_core::admin::{
|
|||
AdminTaskRequest, AdminTaskResponse, ClientCodec, ProtoDomainInfo,
|
||||
ProtoDomainUpgradeCheckReport, ProtoDomainUpgradeCheckStatus,
|
||||
};
|
||||
use kanidmd_core::config::{Configuration, ServerConfig};
|
||||
use kanidmd_core::config::{CliConfig, Configuration, EnvironmentConfig, ServerConfigUntagged};
|
||||
use kanidmd_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,
|
||||
|
@ -379,17 +379,13 @@ 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.
|
||||
async fn start_daemon(
|
||||
opt: KanidmdParser,
|
||||
mut config: Configuration,
|
||||
sconfig: ServerConfig,
|
||||
) -> ExitCode {
|
||||
async fn start_daemon(opt: KanidmdParser, config: Configuration) -> ExitCode {
|
||||
// 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
|
||||
let sub = match sketching::otel::start_logging_pipeline(
|
||||
&sconfig.otel_grpc_url,
|
||||
sconfig.log_level.unwrap_or_default(),
|
||||
&config.otel_grpc_url,
|
||||
config.log_level,
|
||||
"kanidmd",
|
||||
) {
|
||||
Err(err) => {
|
||||
|
@ -423,8 +419,8 @@ async fn start_daemon(
|
|||
return err;
|
||||
};
|
||||
|
||||
if let Some(db_path) = sconfig.db_path.as_ref() {
|
||||
let db_pathbuf = PathBuf::from(db_path.as_str());
|
||||
if let Some(db_path) = config.db_path.as_ref() {
|
||||
let db_pathbuf = db_path.to_path_buf();
|
||||
// We can't check the db_path permissions because it may not exist yet!
|
||||
if let Some(db_parent_path) = db_pathbuf.parent() {
|
||||
if !db_parent_path.exists() {
|
||||
|
@ -464,33 +460,11 @@ async fn start_daemon(
|
|||
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 {
|
||||
error!("No db_path set in configuration, server startup will FAIL!");
|
||||
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 {
|
||||
// we aren't going to touch the DB so we can carry on
|
||||
KanidmdOpt::ShowReplicationCertificate { .. }
|
||||
|
@ -501,19 +475,15 @@ async fn start_daemon(
|
|||
_ => {
|
||||
// Okay - Lets now create our lock and go.
|
||||
#[allow(clippy::expect_used)]
|
||||
let klock_path = match sconfig.db_path.clone() {
|
||||
Some(val) => format!("{}.klock", val),
|
||||
None => std::env::temp_dir()
|
||||
.join("kanidmd.klock")
|
||||
.to_str()
|
||||
.expect("Unable to create klock path, this is a critical error!")
|
||||
.to_string(),
|
||||
let klock_path = match config.db_path.clone() {
|
||||
Some(val) => val.with_extension("klock"),
|
||||
None => std::env::temp_dir().join("kanidmd.klock"),
|
||||
};
|
||||
|
||||
let flock = match File::create(&klock_path) {
|
||||
Ok(flock) => flock,
|
||||
Err(e) => {
|
||||
error!("ERROR: Refusing to start - unable to create kanidmd exclusive lock at {} - {:?}", klock_path, e);
|
||||
error!("ERROR: Refusing to start - unable to create kanidmd exclusive lock at {} - {:?}", klock_path.display(), e);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
|
@ -521,7 +491,7 @@ async fn start_daemon(
|
|||
match flock.try_lock_exclusive() {
|
||||
Ok(()) => debug!("Acquired kanidm exclusive lock"),
|
||||
Err(e) => {
|
||||
error!("ERROR: Refusing to start - unable to lock kanidmd exclusive lock at {} - {:?}", klock_path, e);
|
||||
error!("ERROR: Refusing to start - unable to lock kanidmd exclusive lock at {} - {:?}", klock_path.display(), e);
|
||||
error!("Is another kanidmd process running?");
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
|
@ -529,7 +499,7 @@ async fn start_daemon(
|
|||
}
|
||||
}
|
||||
|
||||
kanidm_main(sconfig, config, opt).await
|
||||
kanidm_main(config, opt).await
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
|
@ -556,10 +526,6 @@ fn main() -> ExitCode {
|
|||
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() {
|
||||
println!("CRITICAL: Kanidmd was not built correctly and is missing a valid KANIDM_SERVER_CONFIG_PATH value");
|
||||
return ExitCode::FAILURE;
|
||||
|
@ -581,49 +547,56 @@ fn main() -> ExitCode {
|
|||
}
|
||||
};
|
||||
|
||||
let sconfig = match ServerConfig::new(maybe_config_path) {
|
||||
let maybe_sconfig = if let Some(config_path) = maybe_config_path {
|
||||
match ServerConfigUntagged::new(config_path) {
|
||||
Ok(c) => Some(c),
|
||||
Err(e) => {
|
||||
config_error.push(format!("Config Parse failure {:?}", e));
|
||||
Err(err) => {
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
// Get information on the windows username
|
||||
#[cfg(target_family = "windows")]
|
||||
get_user_details_windows();
|
||||
let cli_config = CliConfig {
|
||||
output_mode: Some(opt.commands.commonopt().output_mode.to_owned().into()),
|
||||
};
|
||||
|
||||
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 is_server = matches!(&opt.commands, KanidmdOpt::Server(_));
|
||||
|
||||
let sconfig = match sconfig {
|
||||
Some(val) => val,
|
||||
None => {
|
||||
println!("Somehow you got an empty ServerConfig after error checking? Cannot start!");
|
||||
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
|
||||
|
||||
// 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);
|
||||
};
|
||||
// Get information on the windows username
|
||||
#[cfg(target_family = "windows")]
|
||||
get_user_details_windows();
|
||||
|
||||
// Start the runtime
|
||||
|
||||
let maybe_rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(config.threads)
|
||||
.enable_all()
|
||||
|
@ -643,16 +616,12 @@ fn main() -> ExitCode {
|
|||
}
|
||||
};
|
||||
|
||||
rt.block_on(start_daemon(opt, config, sconfig))
|
||||
rt.block_on(start_daemon(opt, config))
|
||||
}
|
||||
|
||||
/// Build and execute the main server. The ServerConfig are the configuration options
|
||||
/// that we are processing into the config for the main server.
|
||||
async fn kanidm_main(
|
||||
sconfig: ServerConfig,
|
||||
mut config: Configuration,
|
||||
opt: KanidmdParser,
|
||||
) -> ExitCode {
|
||||
async fn kanidm_main(config: Configuration, opt: KanidmdParser) -> ExitCode {
|
||||
match &opt.commands {
|
||||
KanidmdOpt::Server(_sopt) | KanidmdOpt::ConfigTest(_sopt) => {
|
||||
let config_test = matches!(&opt.commands, KanidmdOpt::ConfigTest(_));
|
||||
|
@ -662,61 +631,58 @@ async fn kanidm_main(
|
|||
info!("Running in server mode ...");
|
||||
};
|
||||
|
||||
// configuration options that only relate to server mode
|
||||
config.update_config_for_server_mode(&sconfig);
|
||||
|
||||
if let Some(i_str) = &(sconfig.tls_chain) {
|
||||
let i_path = PathBuf::from(i_str.as_str());
|
||||
let i_meta = match metadata(&i_path) {
|
||||
// Verify the TLs configs.
|
||||
if let Some(tls_config) = config.tls_config.as_ref() {
|
||||
{
|
||||
let i_meta = match metadata(&tls_config.chain) {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Unable to read metadata for TLS chain file '{}' - {:?}",
|
||||
&i_path.to_str().unwrap_or("invalid file path"),
|
||||
tls_config.chain.display(),
|
||||
e
|
||||
);
|
||||
let diag = kanidm_lib_file_permissions::diagnose_path(&i_path);
|
||||
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 ...", i_str);
|
||||
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_key) {
|
||||
let i_path = PathBuf::from(i_str.as_str());
|
||||
|
||||
let i_meta = match metadata(&i_path) {
|
||||
{
|
||||
let i_meta = match metadata(&tls_config.key) {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Unable to read metadata for TLS key file '{}' - {:?}",
|
||||
&i_path.to_str().unwrap_or("invalid file path"),
|
||||
tls_config.key.display(),
|
||||
e
|
||||
);
|
||||
let diag = kanidm_lib_file_permissions::diagnose_path(&i_path);
|
||||
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 ...", i_str);
|
||||
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 ...", i_str);
|
||||
warn!("WARNING: {} has 'everyone' permission bits in the mode. This could be a security risk ...", tls_config.key.display());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ca_dir) = &(sconfig.tls_client_ca) {
|
||||
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!(
|
||||
"TLS CA folder {} does not exist, server startup will FAIL!",
|
||||
ca_dir
|
||||
ca_dir.display()
|
||||
);
|
||||
let diag = kanidm_lib_file_permissions::diagnose_path(&ca_dir_path);
|
||||
info!(%diag);
|
||||
|
@ -725,7 +691,11 @@ async fn kanidm_main(
|
|||
let i_meta = match metadata(&ca_dir_path) {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
error!("Unable to read metadata for '{}' - {:?}", ca_dir, 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;
|
||||
|
@ -734,16 +704,17 @@ async fn kanidm_main(
|
|||
if !i_meta.is_dir() {
|
||||
error!(
|
||||
"ERROR: Refusing to run - TLS Client CA folder {} may not be a directory",
|
||||
ca_dir
|
||||
ca_dir.display()
|
||||
);
|
||||
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);
|
||||
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());
|
||||
}
|
||||
#[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);
|
||||
warn!("WARNING: TLS Client CA folder {} has 'everyone' permission bits in the mode. This could be a security risk ...", ca_dir.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -753,14 +724,6 @@ async fn kanidm_main(
|
|||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
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(
|
||||
true,
|
||||
&[sd_notify::NotifyState::Status("Started Kanidm 🦀")],
|
||||
|
@ -800,16 +763,12 @@ async fn kanidm_main(
|
|||
// systemd has a special reload handler for this.
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Reloading]);
|
||||
// CRITICAL - if you do not send a monotonic usec message after a reloading
|
||||
// message, your service WILL BE KILLED.
|
||||
if let Ok(monotonic_usec) = sd_notify::NotifyState::monotonic_usec_now() {
|
||||
let _ =
|
||||
sd_notify::notify(true, &[monotonic_usec]);
|
||||
let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Reloading, monotonic_usec]);
|
||||
let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Status("Reloading ...")]);
|
||||
} 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;
|
||||
|
@ -820,14 +779,12 @@ async fn kanidm_main(
|
|||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Ready]);
|
||||
if let Ok(monotonic_usec) = sd_notify::NotifyState::monotonic_usec_now() {
|
||||
let _ =
|
||||
sd_notify::notify(true, &[monotonic_usec]);
|
||||
let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Ready, monotonic_usec]);
|
||||
let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Status("Reload Success")]);
|
||||
} 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");
|
||||
|
@ -880,34 +837,19 @@ async fn kanidm_main(
|
|||
}
|
||||
KanidmdOpt::CertGenerate(_sopt) => {
|
||||
info!("Running in certificate generate mode ...");
|
||||
config.update_config_for_server_mode(&sconfig);
|
||||
cert_generate_core(&config);
|
||||
}
|
||||
KanidmdOpt::Database {
|
||||
commands: DbCommands::Backup(bopt),
|
||||
} => {
|
||||
info!("Running in backup mode ...");
|
||||
let p = match bopt.path.to_str() {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
error!("Invalid backup path");
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
backup_server_core(&config, p);
|
||||
backup_server_core(&config, &bopt.path);
|
||||
}
|
||||
KanidmdOpt::Database {
|
||||
commands: DbCommands::Restore(ropt),
|
||||
} => {
|
||||
info!("Running in restore mode ...");
|
||||
let p = match ropt.path.to_str() {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
error!("Invalid restore path");
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
restore_server_core(&config, p).await;
|
||||
restore_server_core(&config, &ropt.path).await;
|
||||
}
|
||||
KanidmdOpt::Database {
|
||||
commands: DbCommands::Verify(_vopt),
|
||||
|
@ -1088,8 +1030,6 @@ async fn kanidm_main(
|
|||
vacuum_server_core(&config);
|
||||
}
|
||||
KanidmdOpt::HealthCheck(sopt) => {
|
||||
config.update_config_for_server_mode(&sconfig);
|
||||
|
||||
debug!("{sopt:?}");
|
||||
|
||||
let healthcheck_url = match &sopt.check_origin {
|
||||
|
@ -1110,12 +1050,15 @@ async fn kanidm_main(
|
|||
.danger_accept_invalid_hostnames(!sopt.verify_tls)
|
||||
.https_only(true);
|
||||
|
||||
client = match &sconfig.tls_chain {
|
||||
client = match &config.tls_config {
|
||||
None => client,
|
||||
Some(ca_cert) => {
|
||||
debug!("Trying to load {} to build a CA cert path", ca_cert);
|
||||
Some(tls_config) => {
|
||||
debug!(
|
||||
"Trying to load {} to build a CA cert path",
|
||||
tls_config.chain.display()
|
||||
);
|
||||
// if the ca_cert file exists, then we'll use it
|
||||
let ca_cert_path = PathBuf::from(ca_cert);
|
||||
let ca_cert_path = tls_config.chain.clone();
|
||||
match ca_cert_path.exists() {
|
||||
true => {
|
||||
let mut cert_buf = Vec::new();
|
||||
|
@ -1148,7 +1091,10 @@ async fn kanidm_main(
|
|||
client
|
||||
}
|
||||
false => {
|
||||
warn!("Couldn't find ca cert {} but carrying on...", ca_cert);
|
||||
warn!(
|
||||
"Couldn't find ca cert {} but carrying on...",
|
||||
tls_config.chain.display()
|
||||
);
|
||||
client
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ fn parse_attributes(
|
|||
});
|
||||
|
||||
if !args_are_allowed {
|
||||
let msg = "Invalid test config attribute. The following are allow";
|
||||
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(", ")),
|
||||
|
|
|
@ -1,27 +1,21 @@
|
|||
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 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::dbvalue::DbCidV1;
|
||||
use crate::be::{BackendConfig, IdList, IdRawEntry, IdxKey, IdxSlope};
|
||||
use crate::entry::{Entry, EntryCommitted, EntrySealed};
|
||||
use crate::prelude::*;
|
||||
use crate::value::{IndexType, Value};
|
||||
|
||||
// use uuid::Uuid;
|
||||
use hashbrown::HashMap;
|
||||
use idlset::v2::IDLBitRange;
|
||||
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_INDEXV: &str = "indexv";
|
||||
|
@ -205,7 +199,7 @@ pub(crate) trait IdlSqliteTransaction {
|
|||
let mut stmt = self
|
||||
.get_conn()?
|
||||
.prepare(&format!(
|
||||
"SELECT rowid from {}.sqlite_master where name = :tname LIMIT 1",
|
||||
"SELECT rowid from {}.sqlite_master where type=\"table\" AND name = :tname LIMIT 1",
|
||||
self.get_db_name()
|
||||
))
|
||||
.map_err(sqlite_error)?;
|
||||
|
@ -1712,7 +1706,7 @@ impl IdlSqliteWriteTransaction {
|
|||
|
||||
impl IdlSqlite {
|
||||
pub fn new(cfg: &BackendConfig, vacuum: bool) -> Result<Self, OperationError> {
|
||||
if cfg.path.is_empty() {
|
||||
if cfg.path.as_os_str().is_empty() {
|
||||
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
|
||||
|
@ -1734,8 +1728,7 @@ impl IdlSqlite {
|
|||
|
||||
// Initial setup routines.
|
||||
{
|
||||
let vconn =
|
||||
Connection::open_with_flags(cfg.path.as_str(), flags).map_err(sqlite_error)?;
|
||||
let vconn = Connection::open_with_flags(&cfg.path, flags).map_err(sqlite_error)?;
|
||||
|
||||
vconn
|
||||
.execute_batch(
|
||||
|
@ -1764,8 +1757,7 @@ impl IdlSqlite {
|
|||
);
|
||||
*/
|
||||
|
||||
let vconn =
|
||||
Connection::open_with_flags(cfg.path.as_str(), flags).map_err(sqlite_error)?;
|
||||
let vconn = Connection::open_with_flags(&cfg.path, flags).map_err(sqlite_error)?;
|
||||
|
||||
vconn
|
||||
.execute_batch("PRAGMA wal_checkpoint(TRUNCATE);")
|
||||
|
@ -1786,8 +1778,7 @@ impl IdlSqlite {
|
|||
OperationError::SqliteError
|
||||
})?;
|
||||
|
||||
let vconn =
|
||||
Connection::open_with_flags(cfg.path.as_str(), flags).map_err(sqlite_error)?;
|
||||
let vconn = Connection::open_with_flags(&cfg.path, flags).map_err(sqlite_error)?;
|
||||
|
||||
vconn
|
||||
.pragma_update(None, "page_size", cfg.fstype as u32)
|
||||
|
@ -1821,7 +1812,7 @@ impl IdlSqlite {
|
|||
.map(|i| {
|
||||
trace!("Opening Connection {}", i);
|
||||
let conn =
|
||||
Connection::open_with_flags(cfg.path.as_str(), flags).map_err(sqlite_error);
|
||||
Connection::open_with_flags(&cfg.path, flags).map_err(sqlite_error);
|
||||
match conn {
|
||||
Ok(conn) => {
|
||||
// We need to set the cachesize at this point as well.
|
||||
|
|
|
@ -4,20 +4,6 @@
|
|||
//! is to persist content safely to disk, load that content, and execute queries
|
||||
//! 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::dbrepl::DbReplMeta;
|
||||
use crate::entry::Entry;
|
||||
|
@ -31,6 +17,19 @@ use crate::repl::ruv::{
|
|||
};
|
||||
use crate::utils::trigraph_iter;
|
||||
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 dbrepl;
|
||||
|
@ -132,7 +131,7 @@ impl IdxMeta {
|
|||
|
||||
#[derive(Clone)]
|
||||
pub struct BackendConfig {
|
||||
path: String,
|
||||
path: PathBuf,
|
||||
pool_size: u32,
|
||||
db_name: &'static str,
|
||||
fstype: FsType,
|
||||
|
@ -141,10 +140,16 @@ pub struct BackendConfig {
|
|||
}
|
||||
|
||||
impl BackendConfig {
|
||||
pub fn new(path: &str, pool_size: u32, fstype: FsType, arcsize: Option<usize>) -> Self {
|
||||
pub fn new(
|
||||
path: Option<&Path>,
|
||||
pool_size: u32,
|
||||
fstype: FsType,
|
||||
arcsize: Option<usize>,
|
||||
) -> Self {
|
||||
BackendConfig {
|
||||
pool_size,
|
||||
path: path.to_string(),
|
||||
// This means if path is None, that "" implies an sqlite in memory/ram only database.
|
||||
path: path.unwrap_or_else(|| Path::new("")).to_path_buf(),
|
||||
db_name: "main",
|
||||
fstype,
|
||||
arcsize,
|
||||
|
@ -154,7 +159,7 @@ impl BackendConfig {
|
|||
pub(crate) fn new_test(db_name: &'static str) -> Self {
|
||||
BackendConfig {
|
||||
pool_size: 1,
|
||||
path: "".to_string(),
|
||||
path: PathBuf::from(""),
|
||||
db_name,
|
||||
fstype: FsType::Generic,
|
||||
arcsize: Some(2048),
|
||||
|
@ -549,10 +554,11 @@ pub trait BackendTransaction {
|
|||
}
|
||||
(_, fp) => {
|
||||
plan.push(fp);
|
||||
filter_error!(
|
||||
let setplan = FilterPlan::InclusionInvalid(plan);
|
||||
error!(
|
||||
?setplan,
|
||||
"Inclusion is unable to proceed - all terms must be fully indexed!"
|
||||
);
|
||||
let setplan = FilterPlan::InclusionInvalid(plan);
|
||||
return Ok((IdList::Partial(IDLBitRange::new()), setplan));
|
||||
}
|
||||
}
|
||||
|
@ -935,7 +941,7 @@ pub trait BackendTransaction {
|
|||
self.get_ruv().verify(&entries, results);
|
||||
}
|
||||
|
||||
fn backup(&mut self, dst_path: &str) -> Result<(), OperationError> {
|
||||
fn backup(&mut self, dst_path: &Path) -> Result<(), OperationError> {
|
||||
let repl_meta = self.get_ruv().to_db_backup_ruv();
|
||||
|
||||
// load all entries into RAM, may need to change this later
|
||||
|
@ -1427,20 +1433,16 @@ impl<'a> BackendWriteTransaction<'a> {
|
|||
if self.is_idx_slopeyness_generated()? {
|
||||
trace!("Indexing slopes available");
|
||||
} else {
|
||||
admin_warn!(
|
||||
"No indexing slopes available. You should consider reindexing to generate these"
|
||||
);
|
||||
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
|
||||
// all indexes are "equal" but also worse case unless analysed. If they
|
||||
// have been analysed, we can set the slope factor into here.
|
||||
let idxkeys: Result<Map<_, _>, _> = idxkeys
|
||||
let mut idxkeys = idxkeys
|
||||
.into_iter()
|
||||
.map(|k| self.get_idx_slope(&k).map(|slope| (k, slope)))
|
||||
.collect();
|
||||
|
||||
let mut idxkeys = idxkeys?;
|
||||
.collect::<Result<Map<_, _>, _>>()?;
|
||||
|
||||
std::mem::swap(&mut self.idxmeta_wr.deref_mut().idxkeys, &mut idxkeys);
|
||||
Ok(())
|
||||
|
@ -1811,7 +1813,7 @@ impl<'a> BackendWriteTransaction<'a> {
|
|||
Ok(slope)
|
||||
}
|
||||
|
||||
pub fn restore(&mut self, src_path: &str) -> Result<(), OperationError> {
|
||||
pub fn restore(&mut self, src_path: &Path) -> Result<(), OperationError> {
|
||||
let serialized_string = fs::read_to_string(src_path).map_err(|e| {
|
||||
admin_error!("fs::read_to_string {:?}", e);
|
||||
OperationError::FsError
|
||||
|
@ -2124,7 +2126,7 @@ impl Backend {
|
|||
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 cfg.path.is_empty() {
|
||||
if cfg.path.as_os_str().is_empty() {
|
||||
cfg.pool_size = 1;
|
||||
}
|
||||
|
||||
|
@ -2210,13 +2212,6 @@ impl Backend {
|
|||
|
||||
#[cfg(test)]
|
||||
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::Limits;
|
||||
use super::{
|
||||
|
@ -2226,6 +2221,12 @@ mod tests {
|
|||
use crate::prelude::*;
|
||||
use crate::repl::cid::Cid;
|
||||
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! {
|
||||
static ref CID_ZERO: Cid = Cid::new_zero();
|
||||
|
@ -2600,11 +2601,9 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_be_backup_restore() {
|
||||
let db_backup_file_name = format!(
|
||||
"{}/.backup_test.json",
|
||||
option_env!("OUT_DIR").unwrap_or("/tmp")
|
||||
);
|
||||
eprintln!(" ⚠️ {db_backup_file_name}");
|
||||
let db_backup_file_name =
|
||||
Path::new(option_env!("OUT_DIR").unwrap_or("/tmp")).join(".backup_test.json");
|
||||
eprintln!(" ⚠️ {}", db_backup_file_name.display());
|
||||
run_test!(|be: &mut BackendWriteTransaction| {
|
||||
// Important! Need db metadata setup!
|
||||
be.reset_db_s_uuid().unwrap();
|
||||
|
@ -2659,11 +2658,9 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_be_backup_restore_tampered() {
|
||||
let db_backup_file_name = format!(
|
||||
"{}/.backup2_test.json",
|
||||
option_env!("OUT_DIR").unwrap_or("/tmp")
|
||||
);
|
||||
eprintln!(" ⚠️ {db_backup_file_name}");
|
||||
let db_backup_file_name =
|
||||
Path::new(option_env!("OUT_DIR").unwrap_or("/tmp")).join(".backup2_test.json");
|
||||
eprintln!(" ⚠️ {}", db_backup_file_name.display());
|
||||
run_test!(|be: &mut BackendWriteTransaction| {
|
||||
// Important! Need db metadata setup!
|
||||
be.reset_db_s_uuid().unwrap();
|
||||
|
|
|
@ -136,8 +136,6 @@ 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_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_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_CLASSNAME: Uuid = uuid!("00000000-0000-0000-0000-ffff00000049");
|
||||
pub const UUID_SCHEMA_ATTR_LEGALNAME: Uuid = uuid!("00000000-0000-0000-0000-ffff00000050");
|
||||
|
@ -329,6 +327,13 @@ pub const UUID_SCHEMA_ATTR_ALLOW_PRIMARY_CRED_FALLBACK: Uuid =
|
|||
uuid!("00000000-0000-0000-0000-ffff00000185");
|
||||
pub const UUID_SCHEMA_ATTR_DOMAIN_ALLOW_EASTER_EGGS: Uuid =
|
||||
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
|
||||
// I'd like to strongly criticise william of the past for making poor choices about these allocations.
|
||||
|
|
|
@ -492,6 +492,97 @@ 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> {
|
||||
pub fn from_repl_entry_v1(repl_entry: ReplEntryV1) -> Result<Self, OperationError> {
|
||||
// From the entry, we have to rebuild the ecstate and the attrs.
|
||||
|
@ -1949,7 +2040,7 @@ impl<STATE> Entry<EntryValid, STATE> {
|
|||
};
|
||||
|
||||
if !valid_supplements {
|
||||
admin_warn!(
|
||||
warn!(
|
||||
"Validation error, the following possible supplement classes are missing - {:?}",
|
||||
supplements_classes
|
||||
);
|
||||
|
@ -2633,21 +2724,6 @@ impl<VALID, STATE> Entry<VALID, STATE> {
|
|||
// These are special types to allow returning typed values from
|
||||
// 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
|
||||
/// there are multiple values present (ambiguous).
|
||||
pub fn get_ava_single<A: AsRef<Attribute>>(&self, attr: A) -> Option<Value> {
|
||||
|
@ -3260,97 +3336,6 @@ 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)]
|
||||
mod tests {
|
||||
use crate::prelude::*;
|
||||
|
|
|
@ -527,7 +527,8 @@ impl Filter<FilterValid> {
|
|||
// cases! The exception is *large* filters, especially from the memberof plugin. We
|
||||
// want to skip these because they can really jam up the server.
|
||||
|
||||
let cacheable = FilterResolved::resolve_cacheable(&self.state.inner);
|
||||
// Don't cache anything unless we have valid indexing metadata.
|
||||
let cacheable = idxmeta.is_some() && FilterResolved::resolve_cacheable(&self.state.inner);
|
||||
|
||||
let cache_key = if cacheable {
|
||||
// do we have a cache?
|
||||
|
@ -536,6 +537,7 @@ impl Filter<FilterValid> {
|
|||
let cache_key = (ev.get_event_origin_id(), Arc::new(self.clone()));
|
||||
if let Some(f) = rcache.get(&cache_key) {
|
||||
// Got it? Shortcut and return!
|
||||
trace!("shortcut: a resolved filter already exists.");
|
||||
return Ok(f.as_ref().clone());
|
||||
};
|
||||
// Not in cache? Set the cache_key.
|
||||
|
@ -574,6 +576,7 @@ impl Filter<FilterValid> {
|
|||
// if cacheable == false.
|
||||
if let Some(cache_key) = cache_key {
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -599,19 +599,19 @@ impl IdmServerProxyWriteTransaction<'_> {
|
|||
}
|
||||
|
||||
let eperm_search_primary_cred = match &eperm.search {
|
||||
Access::Denied => false,
|
||||
Access::Deny => false,
|
||||
Access::Grant => true,
|
||||
Access::Allow(attrs) => attrs.contains(&Attribute::PrimaryCredential),
|
||||
};
|
||||
|
||||
let eperm_mod_primary_cred = match &eperm.modify_pres {
|
||||
Access::Denied => false,
|
||||
Access::Deny => false,
|
||||
Access::Grant => true,
|
||||
Access::Allow(attrs) => attrs.contains(&Attribute::PrimaryCredential),
|
||||
};
|
||||
|
||||
let eperm_rem_primary_cred = match &eperm.modify_rem {
|
||||
Access::Denied => false,
|
||||
Access::Deny => false,
|
||||
Access::Grant => true,
|
||||
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;
|
||||
|
||||
let eperm_search_passkeys = match &eperm.search {
|
||||
Access::Denied => false,
|
||||
Access::Deny => false,
|
||||
Access::Grant => true,
|
||||
Access::Allow(attrs) => attrs.contains(&Attribute::PassKeys),
|
||||
};
|
||||
|
||||
let eperm_mod_passkeys = match &eperm.modify_pres {
|
||||
Access::Denied => false,
|
||||
Access::Deny => false,
|
||||
Access::Grant => true,
|
||||
Access::Allow(attrs) => attrs.contains(&Attribute::PassKeys),
|
||||
};
|
||||
|
||||
let eperm_rem_passkeys = match &eperm.modify_rem {
|
||||
Access::Denied => false,
|
||||
Access::Deny => false,
|
||||
Access::Grant => true,
|
||||
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 eperm_search_attested_passkeys = match &eperm.search {
|
||||
Access::Denied => false,
|
||||
Access::Deny => false,
|
||||
Access::Grant => true,
|
||||
Access::Allow(attrs) => attrs.contains(&Attribute::AttestedPasskeys),
|
||||
};
|
||||
|
||||
let eperm_mod_attested_passkeys = match &eperm.modify_pres {
|
||||
Access::Denied => false,
|
||||
Access::Deny => false,
|
||||
Access::Grant => true,
|
||||
Access::Allow(attrs) => attrs.contains(&Attribute::AttestedPasskeys),
|
||||
};
|
||||
|
||||
let eperm_rem_attested_passkeys = match &eperm.modify_rem {
|
||||
Access::Denied => false,
|
||||
Access::Deny => false,
|
||||
Access::Grant => true,
|
||||
Access::Allow(attrs) => attrs.contains(&Attribute::AttestedPasskeys),
|
||||
};
|
||||
|
@ -662,19 +662,19 @@ impl IdmServerProxyWriteTransaction<'_> {
|
|||
&& eperm_rem_attested_passkeys;
|
||||
|
||||
let eperm_search_unixcred = match &eperm.search {
|
||||
Access::Denied => false,
|
||||
Access::Deny => false,
|
||||
Access::Grant => true,
|
||||
Access::Allow(attrs) => attrs.contains(&Attribute::UnixPassword),
|
||||
};
|
||||
|
||||
let eperm_mod_unixcred = match &eperm.modify_pres {
|
||||
Access::Denied => false,
|
||||
Access::Deny => false,
|
||||
Access::Grant => true,
|
||||
Access::Allow(attrs) => attrs.contains(&Attribute::UnixPassword),
|
||||
};
|
||||
|
||||
let eperm_rem_unixcred = match &eperm.modify_rem {
|
||||
Access::Denied => false,
|
||||
Access::Deny => false,
|
||||
Access::Grant => true,
|
||||
Access::Allow(attrs) => attrs.contains(&Attribute::UnixPassword),
|
||||
};
|
||||
|
@ -685,19 +685,19 @@ impl IdmServerProxyWriteTransaction<'_> {
|
|||
&& eperm_rem_unixcred;
|
||||
|
||||
let eperm_search_sshpubkey = match &eperm.search {
|
||||
Access::Denied => false,
|
||||
Access::Deny => false,
|
||||
Access::Grant => true,
|
||||
Access::Allow(attrs) => attrs.contains(&Attribute::SshPublicKey),
|
||||
};
|
||||
|
||||
let eperm_mod_sshpubkey = match &eperm.modify_pres {
|
||||
Access::Denied => false,
|
||||
Access::Deny => false,
|
||||
Access::Grant => true,
|
||||
Access::Allow(attrs) => attrs.contains(&Attribute::SshPublicKey),
|
||||
};
|
||||
|
||||
let eperm_rem_sshpubkey = match &eperm.modify_rem {
|
||||
Access::Denied => false,
|
||||
Access::Deny => false,
|
||||
Access::Grant => true,
|
||||
Access::Allow(attrs) => attrs.contains(&Attribute::SshPublicKey),
|
||||
};
|
||||
|
@ -726,7 +726,7 @@ impl IdmServerProxyWriteTransaction<'_> {
|
|||
})?;
|
||||
|
||||
match &eperm.search {
|
||||
Access::Denied => false,
|
||||
Access::Deny => false,
|
||||
Access::Grant => true,
|
||||
Access::Allow(attrs) => attrs.contains(&Attribute::SyncCredentialPortal),
|
||||
}
|
||||
|
|
|
@ -124,8 +124,8 @@ pub mod prelude {
|
|||
pub use kanidmd_lib_macros::*;
|
||||
|
||||
pub(crate) use crate::valueset::{
|
||||
ValueSet, ValueSetBool, ValueSetCid, ValueSetIndex, ValueSetIutf8, ValueSetRefer,
|
||||
ValueSetSyntax, ValueSetT, ValueSetUtf8, ValueSetUuid,
|
||||
ValueSet, ValueSetBool, ValueSetCid, ValueSetIutf8, ValueSetRefer, ValueSetSyntax,
|
||||
ValueSetT, ValueSetUtf8, ValueSetUuid,
|
||||
};
|
||||
|
||||
pub(crate) use kanidm_proto::scim_v1::{
|
||||
|
|
|
@ -620,22 +620,6 @@ 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)]
|
||||
#[macro_export]
|
||||
macro_rules! vs_cid {
|
||||
|
|
|
@ -72,6 +72,8 @@ pub struct BuiltinAcp {
|
|||
modify_present_attrs: Vec<Attribute>,
|
||||
modify_removed_attrs: Vec<Attribute>,
|
||||
modify_classes: Vec<EntryClass>,
|
||||
modify_present_classes: Vec<EntryClass>,
|
||||
modify_remove_classes: Vec<EntryClass>,
|
||||
create_classes: Vec<EntryClass>,
|
||||
create_attrs: Vec<Attribute>,
|
||||
}
|
||||
|
@ -159,9 +161,19 @@ impl From<BuiltinAcp> for EntryInitNew {
|
|||
value.modify_removed_attrs.into_iter().for_each(|attr| {
|
||||
entry.add_ava(Attribute::AcpModifyRemovedAttr, Value::from(attr));
|
||||
});
|
||||
|
||||
value.modify_classes.into_iter().for_each(|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| {
|
||||
entry.add_ava(Attribute::AcpCreateClass, Value::from(class));
|
||||
});
|
||||
|
@ -214,7 +226,7 @@ lazy_static! {
|
|||
ATTR_RECYCLED.to_string()
|
||||
)),
|
||||
modify_removed_attrs: vec![Attribute::Class],
|
||||
modify_classes: vec![EntryClass::Recycled],
|
||||
modify_remove_classes: vec![EntryClass::Recycled],
|
||||
..Default::default()
|
||||
};
|
||||
}
|
||||
|
@ -425,6 +437,7 @@ lazy_static! {
|
|||
EntryClass::AccessControlCreate,
|
||||
EntryClass::AccessControlDelete,
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -69,7 +69,6 @@ pub fn phase_1_schema_attrs() -> Vec<EntryInitNew> {
|
|||
SCHEMA_ATTR_SYNC_TOKEN_SESSION.clone().into(),
|
||||
SCHEMA_ATTR_UNIX_PASSWORD.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_WEBAUTHN_ATTESTATION_CA_LIST.clone().into(),
|
||||
// DL4
|
||||
|
|
|
@ -2,52 +2,25 @@
|
|||
use crate::constants::entries::{Attribute, EntryClass};
|
||||
use crate::constants::uuids::*;
|
||||
use crate::schema::{SchemaAttribute, SchemaClass};
|
||||
use crate::value::IndexType;
|
||||
use crate::value::SyntaxType;
|
||||
|
||||
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 {
|
||||
uuid: UUID_SCHEMA_ATTR_DISPLAYNAME,
|
||||
name: Attribute::DisplayName,
|
||||
description: "The publicly visible display name of this person".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality, IndexType::SubString],
|
||||
indexed: true,
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::Utf8String,
|
||||
..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 {
|
||||
uuid: UUID_SCHEMA_ATTR_MAIL,
|
||||
name: Attribute::Mail,
|
||||
description: "Mail addresses of the object".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality, IndexType::SubString],
|
||||
indexed: true,
|
||||
unique: true,
|
||||
multivalue: true,
|
||||
sync_allowed: true,
|
||||
|
@ -59,8 +32,7 @@ pub static ref SCHEMA_ATTR_EC_KEY_PRIVATE: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_EC_KEY_PRIVATE,
|
||||
name: Attribute::IdVerificationEcKey,
|
||||
description: "Account verification private key".to_string(),
|
||||
|
||||
index: vec![IndexType::Presence],
|
||||
indexed: true,
|
||||
unique: false,
|
||||
sync_allowed: false,
|
||||
syntax: SyntaxType::EcKeyPrivate,
|
||||
|
@ -82,30 +54,17 @@ pub static ref SCHEMA_ATTR_PRIMARY_CREDENTIAL: SchemaAttribute = SchemaAttribute
|
|||
uuid: UUID_SCHEMA_ATTR_PRIMARY_CREDENTIAL,
|
||||
name: Attribute::PrimaryCredential,
|
||||
description: "Primary credential material of the account for authentication interactively".to_string(),
|
||||
|
||||
index: vec![IndexType::Presence],
|
||||
indexed: true,
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::Credential,
|
||||
..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 {
|
||||
uuid: UUID_SCHEMA_ATTR_LEGALNAME,
|
||||
name: Attribute::LegalName,
|
||||
description: "The private and sensitive legal name of this person".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality, IndexType::SubString],
|
||||
indexed: true,
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::Utf8String,
|
||||
..Default::default()
|
||||
|
@ -115,8 +74,7 @@ pub static ref SCHEMA_ATTR_NAME_HISTORY: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_NAME_HISTORY,
|
||||
name: Attribute::NameHistory,
|
||||
description: "The history of names that a person has had".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
indexed: true,
|
||||
multivalue: true,
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::AuditLogString,
|
||||
|
@ -127,7 +85,6 @@ pub static ref SCHEMA_ATTR_RADIUS_SECRET: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_RADIUS_SECRET,
|
||||
name: Attribute::RadiusSecret,
|
||||
description: "The accounts generated radius secret for device network authentication".to_string(),
|
||||
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::SecretUtf8String,
|
||||
..Default::default()
|
||||
|
@ -137,8 +94,7 @@ pub static ref SCHEMA_ATTR_DOMAIN_NAME: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_DOMAIN_NAME,
|
||||
name: Attribute::DomainName,
|
||||
description: "The domain's DNS name for webauthn and SPN generation purposes".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality, IndexType::Presence],
|
||||
indexed: true,
|
||||
unique: true,
|
||||
syntax: SyntaxType::Utf8StringIname,
|
||||
..Default::default()
|
||||
|
@ -148,7 +104,6 @@ pub static ref SCHEMA_ATTR_LDAP_ALLOW_UNIX_PW_BIND: SchemaAttribute = SchemaAttr
|
|||
uuid: UUID_SCHEMA_ATTR_LDAP_ALLOW_UNIX_PW_BIND,
|
||||
name: Attribute::LdapAllowUnixPwBind,
|
||||
description: "Configuration to enable binds to LDAP objects using their UNIX password".to_string(),
|
||||
|
||||
unique: false,
|
||||
syntax: SyntaxType::Boolean,
|
||||
..Default::default()
|
||||
|
@ -158,7 +113,6 @@ pub static ref SCHEMA_ATTR_DOMAIN_LDAP_BASEDN: SchemaAttribute = SchemaAttribute
|
|||
uuid: UUID_SCHEMA_ATTR_DOMAIN_LDAP_BASEDN,
|
||||
name: Attribute::DomainLdapBasedn,
|
||||
description: "The domain's optional ldap basedn. If unset defaults to domain components of domain name".to_string(),
|
||||
|
||||
unique: true,
|
||||
syntax: SyntaxType::Utf8StringInsensitive,
|
||||
..Default::default()
|
||||
|
@ -168,7 +122,6 @@ pub static ref SCHEMA_ATTR_LDAP_MAXIMUM_QUERYABLE_ATTRIBUTES: SchemaAttribute =
|
|||
uuid: UUID_SCHEMA_ATTR_LDAP_MAXIMUM_QUERYABLE_ATTRIBUTES,
|
||||
name: Attribute::LdapMaxQueryableAttrs,
|
||||
description: "The maximum number of LDAP attributes that can be queried in one operation".to_string(),
|
||||
|
||||
multivalue: false,
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::Uint32,
|
||||
|
@ -179,8 +132,7 @@ pub static ref SCHEMA_ATTR_DOMAIN_DISPLAY_NAME: SchemaAttribute = SchemaAttribut
|
|||
uuid: UUID_SCHEMA_ATTR_DOMAIN_DISPLAY_NAME,
|
||||
name: Attribute::DomainDisplayName,
|
||||
description: "The user-facing display name of the Kanidm domain".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
indexed: true,
|
||||
syntax: SyntaxType::Utf8String,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -189,8 +141,7 @@ pub static ref SCHEMA_ATTR_DOMAIN_UUID: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_DOMAIN_UUID,
|
||||
name: Attribute::DomainUuid,
|
||||
description: "The domain's uuid, used in CSN and trust relationships".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
indexed: true,
|
||||
unique: true,
|
||||
syntax: SyntaxType::Uuid,
|
||||
..Default::default()
|
||||
|
@ -200,27 +151,16 @@ pub static ref SCHEMA_ATTR_DOMAIN_SSID: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_DOMAIN_SSID,
|
||||
name: Attribute::DomainSsid,
|
||||
description: "The domains site-wide SSID for device autoconfiguration of wireless".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
indexed: true,
|
||||
unique: true,
|
||||
syntax: SyntaxType::Utf8String,
|
||||
..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 {
|
||||
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,
|
||||
multivalue: true,
|
||||
..Default::default()
|
||||
|
@ -230,7 +170,6 @@ pub static ref SCHEMA_ATTR_DOMAIN_TOKEN_KEY: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_DOMAIN_TOKEN_KEY,
|
||||
name: Attribute::DomainTokenKey,
|
||||
description: "The domain token encryption private key (NOT USED)".to_string(),
|
||||
|
||||
syntax: SyntaxType::SecretUtf8String,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -248,8 +187,7 @@ pub static ref SCHEMA_ATTR_GIDNUMBER: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_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(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
indexed: true,
|
||||
unique: true,
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::Uint32,
|
||||
|
@ -260,7 +198,6 @@ pub static ref SCHEMA_ATTR_BADLIST_PASSWORD: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_BADLIST_PASSWORD,
|
||||
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(),
|
||||
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::Utf8StringInsensitive,
|
||||
..Default::default()
|
||||
|
@ -270,7 +207,6 @@ pub static ref SCHEMA_ATTR_AUTH_SESSION_EXPIRY: SchemaAttribute = SchemaAttribut
|
|||
uuid: UUID_SCHEMA_ATTR_AUTH_SESSION_EXPIRY,
|
||||
name: Attribute::AuthSessionExpiry,
|
||||
description: "An expiration time for an authentication session".to_string(),
|
||||
|
||||
syntax: SyntaxType::Uint32,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -279,7 +215,6 @@ pub static ref SCHEMA_ATTR_AUTH_PRIVILEGE_EXPIRY: SchemaAttribute = SchemaAttrib
|
|||
uuid: UUID_SCHEMA_ATTR_AUTH_PRIVILEGE_EXPIRY,
|
||||
name: Attribute::PrivilegeExpiry,
|
||||
description: "An expiration time for a privileged authentication session".to_string(),
|
||||
|
||||
syntax: SyntaxType::Uint32,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -288,7 +223,6 @@ pub static ref SCHEMA_ATTR_AUTH_PASSWORD_MINIMUM_LENGTH: SchemaAttribute = Schem
|
|||
uuid: UUID_SCHEMA_ATTR_AUTH_PASSWORD_MINIMUM_LENGTH,
|
||||
name: Attribute::AuthPasswordMinimumLength,
|
||||
description: "Minimum length of passwords".to_string(),
|
||||
|
||||
syntax: SyntaxType::Uint32,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -297,7 +231,6 @@ pub static ref SCHEMA_ATTR_LOGINSHELL: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_LOGINSHELL,
|
||||
name: Attribute::LoginShell,
|
||||
description: "A POSIX user's UNIX login shell".to_string(),
|
||||
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::Utf8StringInsensitive,
|
||||
..Default::default()
|
||||
|
@ -307,8 +240,7 @@ pub static ref SCHEMA_ATTR_UNIX_PASSWORD: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_UNIX_PASSWORD,
|
||||
name: Attribute::UnixPassword,
|
||||
description: "A POSIX user's UNIX login password".to_string(),
|
||||
|
||||
index: vec![IndexType::Presence],
|
||||
indexed: true,
|
||||
syntax: SyntaxType::Credential,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -317,8 +249,7 @@ pub static ref SCHEMA_ATTR_NSUNIQUEID: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_NSUNIQUEID,
|
||||
name: Attribute::NsUniqueId,
|
||||
description: "A unique id compatibility for 389-ds/dsee".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
indexed: true,
|
||||
unique: true,
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::NsUniqueId,
|
||||
|
@ -329,7 +260,6 @@ pub static ref SCHEMA_ATTR_ACCOUNT_EXPIRE: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_ACCOUNT_EXPIRE,
|
||||
name: Attribute::AccountExpire,
|
||||
description: "The datetime after which this account no longer may authenticate".to_string(),
|
||||
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::DateTime,
|
||||
..Default::default()
|
||||
|
@ -339,7 +269,6 @@ pub static ref SCHEMA_ATTR_ACCOUNT_VALID_FROM: SchemaAttribute = SchemaAttribute
|
|||
uuid: UUID_SCHEMA_ATTR_ACCOUNT_VALID_FROM,
|
||||
name: Attribute::AccountValidFrom,
|
||||
description: "The datetime after which this account may commence authenticating".to_string(),
|
||||
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::DateTime,
|
||||
..Default::default()
|
||||
|
@ -349,7 +278,6 @@ pub static ref SCHEMA_ATTR_WEBAUTHN_ATTESTATION_CA_LIST: SchemaAttribute = Schem
|
|||
uuid: UUID_SCHEMA_ATTR_WEBAUTHN_ATTESTATION_CA_LIST,
|
||||
name: Attribute::WebauthnAttestationCaList,
|
||||
description: "A set of CA's that limit devices that can be used with webauthn".to_string(),
|
||||
|
||||
syntax: SyntaxType::WebauthnAttestationCaList,
|
||||
multivalue: true,
|
||||
..Default::default()
|
||||
|
@ -359,27 +287,16 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_NAME: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_NAME,
|
||||
name: Attribute::OAuth2RsName,
|
||||
description: "The unique name of an external Oauth2 resource".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
indexed: true,
|
||||
unique: true,
|
||||
syntax: SyntaxType::Utf8StringIname,
|
||||
..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 {
|
||||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_ORIGIN,
|
||||
name: Attribute::OAuth2RsOrigin,
|
||||
description: "The origin domain of an OAuth2 client".to_string(),
|
||||
|
||||
syntax: SyntaxType::Url,
|
||||
multivalue: true,
|
||||
..Default::default()
|
||||
|
@ -389,7 +306,6 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_ORIGIN_LANDING: SchemaAttribute = SchemaAtt
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_ORIGIN_LANDING,
|
||||
name: Attribute::OAuth2RsOriginLanding,
|
||||
description: "The landing page of an RS, that will automatically trigger the auth process".to_string(),
|
||||
|
||||
syntax: SyntaxType::Url,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -399,7 +315,6 @@ pub static ref SCHEMA_ATTR_OAUTH2_ALLOW_LOCALHOST_REDIRECT_DL4: SchemaAttribute
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_ALLOW_LOCALHOST_REDIRECT,
|
||||
name: Attribute::OAuth2AllowLocalhostRedirect,
|
||||
description: "Allow public clients associated to this RS to redirect to localhost".to_string(),
|
||||
|
||||
syntax: SyntaxType::Boolean,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -408,8 +323,7 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_CLAIM_MAP_DL4: SchemaAttribute = SchemaAttr
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_CLAIM_MAP,
|
||||
name: Attribute::OAuth2RsClaimMap,
|
||||
description: "A set of custom claims mapped to group memberships of accounts".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
indexed: true,
|
||||
multivalue: true,
|
||||
// CHANGE ME
|
||||
syntax: SyntaxType::OauthClaimMap,
|
||||
|
@ -420,8 +334,7 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_SCOPE_MAP: SchemaAttribute = SchemaAttribut
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_SCOPE_MAP,
|
||||
name: Attribute::OAuth2RsScopeMap,
|
||||
description: "A reference to a group mapped to scopes for the associated oauth2 resource server".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
indexed: true,
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::OauthScopeMap,
|
||||
..Default::default()
|
||||
|
@ -431,8 +344,7 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_SUP_SCOPE_MAP: SchemaAttribute = SchemaAttr
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_SUP_SCOPE_MAP,
|
||||
name: Attribute::OAuth2RsSupScopeMap,
|
||||
description: "A reference to a group mapped to scopes for the associated oauth2 resource server".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
indexed: true,
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::OauthScopeMap,
|
||||
..Default::default()
|
||||
|
@ -442,7 +354,6 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_BASIC_SECRET: SchemaAttribute = SchemaAttri
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_BASIC_SECRET,
|
||||
name: Attribute::OAuth2RsBasicSecret,
|
||||
description: "When using oauth2 basic authentication, the secret string of the resource server".to_string(),
|
||||
|
||||
syntax: SyntaxType::SecretUtf8String,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -451,7 +362,6 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_TOKEN_KEY: SchemaAttribute = SchemaAttribut
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_TOKEN_KEY,
|
||||
name: Attribute::OAuth2RsTokenKey,
|
||||
description: "An oauth2 resource servers unique token signing key".to_string(),
|
||||
|
||||
syntax: SyntaxType::SecretUtf8String,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -460,7 +370,6 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_IMPLICIT_SCOPES: SchemaAttribute = SchemaAt
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_IMPLICIT_SCOPES,
|
||||
name: Attribute::OAuth2RsImplicitScopes,
|
||||
description: "An oauth2 resource servers scopes that are implicitly granted to all users".to_string(),
|
||||
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::OauthScope,
|
||||
..Default::default()
|
||||
|
@ -470,8 +379,7 @@ pub static ref SCHEMA_ATTR_OAUTH2_CONSENT_SCOPE_MAP: SchemaAttribute = SchemaAtt
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_CONSENT_SCOPE_MAP,
|
||||
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(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
indexed: true,
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::OauthScopeMap,
|
||||
..Default::default()
|
||||
|
@ -481,7 +389,6 @@ pub static ref SCHEMA_ATTR_OAUTH2_STRICT_REDIRECT_URI_DL7: SchemaAttribute = Sch
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_STRICT_REDIRECT_URI,
|
||||
name: Attribute::OAuth2StrictRedirectUri,
|
||||
description: "Represents if strict redirect uri enforcement is enabled.".to_string(),
|
||||
|
||||
syntax: SyntaxType::Boolean,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -491,7 +398,6 @@ pub static ref SCHEMA_ATTR_OAUTH2_DEVICE_FLOW_ENABLE_DL9: SchemaAttribute = Sche
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_DEVICE_FLOW_ENABLE,
|
||||
name: Attribute::OAuth2DeviceFlowEnable,
|
||||
description: "Represents if OAuth2 Device Flow is permitted on this client.".to_string(),
|
||||
|
||||
syntax: SyntaxType::Boolean,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -500,7 +406,6 @@ pub static ref SCHEMA_ATTR_ES256_PRIVATE_KEY_DER: SchemaAttribute = SchemaAttrib
|
|||
uuid: UUID_SCHEMA_ATTR_ES256_PRIVATE_KEY_DER,
|
||||
name: Attribute::Es256PrivateKeyDer,
|
||||
description: "An es256 private key".to_string(),
|
||||
|
||||
syntax: SyntaxType::PrivateBinary,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -509,7 +414,6 @@ pub static ref SCHEMA_ATTR_RS256_PRIVATE_KEY_DER: SchemaAttribute = SchemaAttrib
|
|||
uuid: UUID_SCHEMA_ATTR_RS256_PRIVATE_KEY_DER,
|
||||
name: Attribute::Rs256PrivateKeyDer,
|
||||
description: "An rs256 private key".to_string(),
|
||||
|
||||
syntax: SyntaxType::PrivateBinary,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -518,8 +422,7 @@ pub static ref SCHEMA_ATTR_JWS_ES256_PRIVATE_KEY: SchemaAttribute = SchemaAttrib
|
|||
uuid: UUID_SCHEMA_ATTR_JWS_ES256_PRIVATE_KEY,
|
||||
name: Attribute::JwsEs256PrivateKey,
|
||||
description: "An es256 private key for jws".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
indexed: true,
|
||||
unique: true,
|
||||
syntax: SyntaxType::JwsKeyEs256,
|
||||
..Default::default()
|
||||
|
@ -530,7 +433,6 @@ pub static ref SCHEMA_ATTR_PRIVATE_COOKIE_KEY: SchemaAttribute = SchemaAttribute
|
|||
uuid: UUID_SCHEMA_ATTR_PRIVATE_COOKIE_KEY,
|
||||
name: Attribute::PrivateCookieKey,
|
||||
description: "An private cookie hmac key".to_string(),
|
||||
|
||||
syntax: SyntaxType::PrivateBinary,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -539,7 +441,6 @@ pub static ref SCHEMA_ATTR_OAUTH2_ALLOW_INSECURE_CLIENT_DISABLE_PKCE: SchemaAttr
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_ALLOW_INSECURE_CLIENT_DISABLE_PKCE,
|
||||
name: Attribute::OAuth2AllowInsecureClientDisablePkce,
|
||||
description: "Allows disabling of PKCE for insecure OAuth2 clients".to_string(),
|
||||
|
||||
syntax: SyntaxType::Boolean,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -548,7 +449,6 @@ pub static ref SCHEMA_ATTR_OAUTH2_JWT_LEGACY_CRYPTO_ENABLE: SchemaAttribute = Sc
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_JWT_LEGACY_CRYPTO_ENABLE,
|
||||
name: Attribute::OAuth2JwtLegacyCryptoEnable,
|
||||
description: "Allows enabling legacy JWT cryptograhpy for clients".to_string(),
|
||||
|
||||
syntax: SyntaxType::Boolean,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -557,8 +457,7 @@ pub static ref SCHEMA_ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN: SchemaAttribute = Sch
|
|||
uuid: UUID_SCHEMA_ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN,
|
||||
name: Attribute::CredentialUpdateIntentToken,
|
||||
description: "The status of a credential update intent token".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
indexed: true,
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::IntentToken,
|
||||
..Default::default()
|
||||
|
@ -568,8 +467,7 @@ pub static ref SCHEMA_ATTR_PASSKEYS: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_PASSKEYS,
|
||||
name: Attribute::PassKeys,
|
||||
description: "A set of registered passkeys".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
indexed: true,
|
||||
multivalue: true,
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::Passkey,
|
||||
|
@ -580,8 +478,7 @@ pub static ref SCHEMA_ATTR_ATTESTED_PASSKEYS: SchemaAttribute = SchemaAttribute
|
|||
uuid: UUID_SCHEMA_ATTR_ATTESTED_PASSKEYS,
|
||||
name: Attribute::AttestedPasskeys,
|
||||
description: "A set of registered device keys".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
indexed: true,
|
||||
multivalue: true,
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::AttestedPasskey,
|
||||
|
@ -592,7 +489,6 @@ pub static ref SCHEMA_ATTR_DYNGROUP_FILTER: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_DYNGROUP_FILTER,
|
||||
name: Attribute::DynGroupFilter,
|
||||
description: "A filter describing the set of entries to add to a dynamic group".to_string(),
|
||||
|
||||
syntax: SyntaxType::JsonFilter,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -601,7 +497,6 @@ pub static ref SCHEMA_ATTR_OAUTH2_PREFER_SHORT_USERNAME: SchemaAttribute = Schem
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_PREFER_SHORT_USERNAME,
|
||||
name: Attribute::OAuth2PreferShortUsername,
|
||||
description: "Use 'name' instead of 'spn' in the preferred_username claim".to_string(),
|
||||
|
||||
syntax: SyntaxType::Boolean,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -610,8 +505,7 @@ pub static ref SCHEMA_ATTR_API_TOKEN_SESSION: SchemaAttribute = SchemaAttribute
|
|||
uuid: UUID_SCHEMA_ATTR_API_TOKEN_SESSION,
|
||||
name: Attribute::ApiTokenSession,
|
||||
description: "A session entry related to an issued API token".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
indexed: true,
|
||||
unique: true,
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::ApiToken,
|
||||
|
@ -622,8 +516,7 @@ pub static ref SCHEMA_ATTR_USER_AUTH_TOKEN_SESSION: SchemaAttribute = SchemaAttr
|
|||
uuid: UUID_SCHEMA_ATTR_USER_AUTH_TOKEN_SESSION,
|
||||
name: Attribute::UserAuthTokenSession,
|
||||
description: "A session entry related to an issued user auth token".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
indexed: true,
|
||||
unique: true,
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::Session,
|
||||
|
@ -634,8 +527,7 @@ pub static ref SCHEMA_ATTR_OAUTH2_SESSION: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_SESSION,
|
||||
name: Attribute::OAuth2Session,
|
||||
description: "A session entry to an active oauth2 session, bound to a parent user auth token".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
indexed: true,
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::Oauth2Session,
|
||||
..Default::default()
|
||||
|
@ -645,8 +537,7 @@ pub static ref SCHEMA_ATTR_SYNC_TOKEN_SESSION: SchemaAttribute = SchemaAttribute
|
|||
uuid: UUID_SCHEMA_ATTR_SYNC_TOKEN_SESSION,
|
||||
name: Attribute::SyncTokenSession,
|
||||
description: "A session entry related to an issued sync token".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
indexed: true,
|
||||
unique: true,
|
||||
syntax: SyntaxType::ApiToken,
|
||||
..Default::default()
|
||||
|
@ -656,7 +547,6 @@ pub static ref SCHEMA_ATTR_SYNC_COOKIE: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_SYNC_COOKIE,
|
||||
name: Attribute::SyncCookie,
|
||||
description: "A private sync cookie for a remote IDM source".to_string(),
|
||||
|
||||
syntax: SyntaxType::PrivateBinary,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -665,8 +555,7 @@ pub static ref SCHEMA_ATTR_GRANT_UI_HINT: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_GRANT_UI_HINT,
|
||||
name: Attribute::GrantUiHint,
|
||||
description: "A UI hint that is granted via membership to a group".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
indexed: true,
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::UiHint,
|
||||
..Default::default()
|
||||
|
@ -676,7 +565,6 @@ pub static ref SCHEMA_ATTR_SYNC_CREDENTIAL_PORTAL: SchemaAttribute = SchemaAttri
|
|||
uuid: UUID_SCHEMA_ATTR_SYNC_CREDENTIAL_PORTAL,
|
||||
name: Attribute::SyncCredentialPortal,
|
||||
description: "The url of an external credential portal for synced accounts to visit to update their credentials".to_string(),
|
||||
|
||||
syntax: SyntaxType::Url,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -685,7 +573,6 @@ pub static ref SCHEMA_ATTR_SYNC_YIELD_AUTHORITY: SchemaAttribute = SchemaAttribu
|
|||
uuid: UUID_SCHEMA_ATTR_SYNC_YIELD_AUTHORITY,
|
||||
name: Attribute::SyncYieldAuthority,
|
||||
description: "A set of attributes that have their authority yielded to Kanidm in a sync agreement".to_string(),
|
||||
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::Utf8StringInsensitive,
|
||||
..Default::default()
|
||||
|
@ -695,7 +582,6 @@ pub static ref SCHEMA_ATTR_CREDENTIAL_TYPE_MINIMUM: SchemaAttribute = SchemaAttr
|
|||
uuid: UUID_SCHEMA_ATTR_CREDENTIAL_TYPE_MINIMUM,
|
||||
name: Attribute::CredentialTypeMinimum,
|
||||
description: "The minimum level of credential type that can satisfy this policy".to_string(),
|
||||
|
||||
multivalue: false,
|
||||
syntax: SyntaxType::CredentialType,
|
||||
..Default::default()
|
||||
|
@ -705,7 +591,6 @@ pub static ref SCHEMA_ATTR_LIMIT_SEARCH_MAX_RESULTS_DL6: SchemaAttribute = Schem
|
|||
uuid: UUID_SCHEMA_ATTR_LIMIT_SEARCH_MAX_RESULTS,
|
||||
name: Attribute::LimitSearchMaxResults,
|
||||
description: "The maximum number of query results that may be returned in a single operation".to_string(),
|
||||
|
||||
multivalue: false,
|
||||
syntax: SyntaxType::Uint32,
|
||||
..Default::default()
|
||||
|
@ -715,7 +600,6 @@ pub static ref SCHEMA_ATTR_LIMIT_SEARCH_MAX_FILTER_TEST_DL6: SchemaAttribute = S
|
|||
uuid: UUID_SCHEMA_ATTR_LIMIT_SEARCH_MAX_FILTER_TEST,
|
||||
name: Attribute::LimitSearchMaxFilterTest,
|
||||
description: "The maximum number of entries that may be examined in a partially indexed query".to_string(),
|
||||
|
||||
multivalue: false,
|
||||
syntax: SyntaxType::Uint32,
|
||||
..Default::default()
|
||||
|
@ -735,6 +619,7 @@ pub static ref SCHEMA_ATTR_KEY_PROVIDER_DL6: SchemaAttribute = SchemaAttribute {
|
|||
name: Attribute::KeyProvider,
|
||||
description: "".to_string(),
|
||||
multivalue: false,
|
||||
indexed: true,
|
||||
syntax: SyntaxType::ReferenceUuid,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -800,6 +685,7 @@ pub static ref SCHEMA_ATTR_REFERS_DL7: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_REFERS,
|
||||
name: Attribute::Refers,
|
||||
description: "A reference to linked object".to_string(),
|
||||
indexed: true,
|
||||
multivalue: false,
|
||||
syntax: SyntaxType::ReferenceUuid,
|
||||
..Default::default()
|
||||
|
@ -809,8 +695,8 @@ pub static ref SCHEMA_ATTR_LINKED_GROUP_DL8: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_LINKED_GROUP,
|
||||
name: Attribute::LinkedGroup,
|
||||
description: "A reference linking a group to an entry".to_string(),
|
||||
|
||||
multivalue: false,
|
||||
indexed: true,
|
||||
syntax: SyntaxType::ReferenceUuid,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -819,7 +705,6 @@ pub static ref SCHEMA_ATTR_ALLOW_PRIMARY_CRED_FALLBACK_DL8: SchemaAttribute = Sc
|
|||
uuid: UUID_SCHEMA_ATTR_ALLOW_PRIMARY_CRED_FALLBACK,
|
||||
name: Attribute::AllowPrimaryCredFallback,
|
||||
description: "Allow fallback to primary password if no POSIX password exists".to_string(),
|
||||
|
||||
multivalue: false,
|
||||
syntax: SyntaxType::Boolean,
|
||||
..Default::default()
|
||||
|
@ -838,57 +723,13 @@ pub static ref SCHEMA_ATTR_APPLICATION_PASSWORD_DL8: SchemaAttribute = SchemaAtt
|
|||
uuid: UUID_SCHEMA_ATTR_APPLICATION_PASSWORD,
|
||||
name: Attribute::ApplicationPassword,
|
||||
description: "A set of application passwords".to_string(),
|
||||
|
||||
multivalue: true,
|
||||
indexed: true,
|
||||
syntax: SyntaxType::ApplicationPassword,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// === 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 {
|
||||
uuid: UUID_SCHEMA_CLASS_PERSON,
|
||||
name: EntryClass::Person.into(),
|
||||
|
@ -961,24 +802,6 @@ pub static ref SCHEMA_CLASS_DYNGROUP: SchemaClass = SchemaClass {
|
|||
..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 {
|
||||
uuid: UUID_SCHEMA_CLASS_ACCOUNT_POLICY,
|
||||
name: EntryClass::AccountPolicy.into(),
|
||||
|
@ -998,40 +821,6 @@ pub static ref SCHEMA_CLASS_ACCOUNT_POLICY_DL8: SchemaClass = SchemaClass {
|
|||
..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 {
|
||||
uuid: UUID_SCHEMA_CLASS_ACCOUNT,
|
||||
name: EntryClass::Account.into(),
|
||||
|
@ -1056,29 +845,6 @@ pub static ref SCHEMA_CLASS_ACCOUNT_DL5: SchemaClass = SchemaClass {
|
|||
..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 {
|
||||
uuid: UUID_SCHEMA_CLASS_SERVICE_ACCOUNT,
|
||||
name: EntryClass::ServiceAccount.into(),
|
||||
|
@ -1100,23 +866,6 @@ pub static ref SCHEMA_CLASS_SERVICE_ACCOUNT_DL7: SchemaClass = SchemaClass {
|
|||
..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 {
|
||||
uuid: UUID_SCHEMA_CLASS_SYNC_ACCOUNT,
|
||||
name: EntryClass::SyncAccount.into(),
|
||||
|
@ -1133,100 +882,6 @@ pub static ref SCHEMA_CLASS_SYNC_ACCOUNT_DL7: SchemaClass = SchemaClass {
|
|||
..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 {
|
||||
uuid: UUID_SCHEMA_CLASS_DOMAIN_INFO,
|
||||
name: EntryClass::DomainInfo.into(),
|
||||
|
@ -1290,83 +945,6 @@ pub static ref SCHEMA_CLASS_SYSTEM_CONFIG: SchemaClass = SchemaClass {
|
|||
..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 {
|
||||
uuid: UUID_SCHEMA_CLASS_OAUTH2_RS,
|
||||
name: EntryClass::OAuth2ResourceServer.into(),
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
use crate::constants::entries::{Attribute, EntryClass};
|
||||
use crate::constants::uuids::*;
|
||||
use crate::schema::{SchemaAttribute, SchemaClass};
|
||||
use crate::value::IndexType;
|
||||
use crate::value::SyntaxType;
|
||||
|
||||
lazy_static!(
|
||||
|
@ -11,8 +10,6 @@ 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()
|
||||
|
@ -22,8 +19,6 @@ pub static ref SCHEMA_ATTR_DISPLAYNAME_DL7: 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, IndexType::SubString],
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::Utf8String,
|
||||
..Default::default()
|
||||
|
@ -33,8 +28,6 @@ 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,
|
||||
|
@ -46,8 +39,6 @@ pub static ref SCHEMA_ATTR_MAIL_DL7: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_MAIL,
|
||||
name: Attribute::Mail,
|
||||
description: "Mail addresses of the object".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality, IndexType::SubString],
|
||||
unique: true,
|
||||
multivalue: true,
|
||||
sync_allowed: true,
|
||||
|
@ -59,8 +50,6 @@ pub static ref SCHEMA_ATTR_EC_KEY_PRIVATE: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_EC_KEY_PRIVATE,
|
||||
name: Attribute::IdVerificationEcKey,
|
||||
description: "Account verification private key".to_string(),
|
||||
|
||||
index: vec![IndexType::Presence],
|
||||
unique: false,
|
||||
sync_allowed: false,
|
||||
syntax: SyntaxType::EcKeyPrivate,
|
||||
|
@ -82,8 +71,6 @@ pub static ref SCHEMA_ATTR_PRIMARY_CREDENTIAL: SchemaAttribute = SchemaAttribute
|
|||
uuid: UUID_SCHEMA_ATTR_PRIMARY_CREDENTIAL,
|
||||
name: Attribute::PrimaryCredential,
|
||||
description: "Primary credential material of the account for authentication interactively".to_string(),
|
||||
|
||||
index: vec![IndexType::Presence],
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::Credential,
|
||||
..Default::default()
|
||||
|
@ -93,8 +80,6 @@ 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()
|
||||
|
@ -104,8 +89,6 @@ pub static ref SCHEMA_ATTR_LEGALNAME_DL7: 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, IndexType::SubString],
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::Utf8String,
|
||||
..Default::default()
|
||||
|
@ -115,8 +98,6 @@ pub static ref SCHEMA_ATTR_NAME_HISTORY: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_NAME_HISTORY,
|
||||
name: Attribute::NameHistory,
|
||||
description: "The history of names that a person has had".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
multivalue: true,
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::AuditLogString,
|
||||
|
@ -137,8 +118,6 @@ pub static ref SCHEMA_ATTR_DOMAIN_NAME: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_DOMAIN_NAME,
|
||||
name: Attribute::DomainName,
|
||||
description: "The domain's DNS name for webauthn and SPN generation purposes".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality, IndexType::Presence],
|
||||
unique: true,
|
||||
syntax: SyntaxType::Utf8StringIname,
|
||||
..Default::default()
|
||||
|
@ -168,8 +147,6 @@ pub static ref SCHEMA_ATTR_DOMAIN_DISPLAY_NAME: SchemaAttribute = SchemaAttribut
|
|||
uuid: UUID_SCHEMA_ATTR_DOMAIN_DISPLAY_NAME,
|
||||
name: Attribute::DomainDisplayName,
|
||||
description: "The user-facing display name of the Kanidm domain".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
syntax: SyntaxType::Utf8String,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -178,8 +155,6 @@ pub static ref SCHEMA_ATTR_DOMAIN_UUID: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_DOMAIN_UUID,
|
||||
name: Attribute::DomainUuid,
|
||||
description: "The domain's uuid, used in CSN and trust relationships".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
unique: true,
|
||||
syntax: SyntaxType::Uuid,
|
||||
..Default::default()
|
||||
|
@ -189,8 +164,6 @@ pub static ref SCHEMA_ATTR_DOMAIN_SSID: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_DOMAIN_SSID,
|
||||
name: Attribute::DomainSsid,
|
||||
description: "The domains site-wide SSID for device autoconfiguration of wireless".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
unique: true,
|
||||
syntax: SyntaxType::Utf8String,
|
||||
..Default::default()
|
||||
|
@ -237,8 +210,6 @@ pub static ref SCHEMA_ATTR_GIDNUMBER: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_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(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
unique: true,
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::Uint32,
|
||||
|
@ -296,8 +267,6 @@ pub static ref SCHEMA_ATTR_UNIX_PASSWORD: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_UNIX_PASSWORD,
|
||||
name: Attribute::UnixPassword,
|
||||
description: "A POSIX user's UNIX login password".to_string(),
|
||||
|
||||
index: vec![IndexType::Presence],
|
||||
syntax: SyntaxType::Credential,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -306,8 +275,6 @@ pub static ref SCHEMA_ATTR_NSUNIQUEID: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_NSUNIQUEID,
|
||||
name: Attribute::NsUniqueId,
|
||||
description: "A unique id compatibility for 389-ds/dsee".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
unique: true,
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::NsUniqueId,
|
||||
|
@ -348,8 +315,6 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_NAME: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_NAME,
|
||||
name: Attribute::OAuth2RsName,
|
||||
description: "The unique name of an external Oauth2 resource".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
unique: true,
|
||||
syntax: SyntaxType::Utf8StringIname,
|
||||
..Default::default()
|
||||
|
@ -397,8 +362,6 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_CLAIM_MAP_DL4: SchemaAttribute = SchemaAttr
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_CLAIM_MAP,
|
||||
name: Attribute::OAuth2RsClaimMap,
|
||||
description: "A set of custom claims mapped to group memberships of accounts".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
multivalue: true,
|
||||
// CHANGE ME
|
||||
syntax: SyntaxType::OauthClaimMap,
|
||||
|
@ -409,8 +372,6 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_SCOPE_MAP: SchemaAttribute = SchemaAttribut
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_SCOPE_MAP,
|
||||
name: Attribute::OAuth2RsScopeMap,
|
||||
description: "A reference to a group mapped to scopes for the associated oauth2 resource server".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::OauthScopeMap,
|
||||
..Default::default()
|
||||
|
@ -420,8 +381,6 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_SUP_SCOPE_MAP: SchemaAttribute = SchemaAttr
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_SUP_SCOPE_MAP,
|
||||
name: Attribute::OAuth2RsSupScopeMap,
|
||||
description: "A reference to a group mapped to scopes for the associated oauth2 resource server".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::OauthScopeMap,
|
||||
..Default::default()
|
||||
|
@ -459,8 +418,6 @@ pub static ref SCHEMA_ATTR_OAUTH2_CONSENT_SCOPE_MAP: SchemaAttribute = SchemaAtt
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_CONSENT_SCOPE_MAP,
|
||||
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(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::OauthScopeMap,
|
||||
..Default::default()
|
||||
|
@ -507,8 +464,6 @@ pub static ref SCHEMA_ATTR_JWS_ES256_PRIVATE_KEY: SchemaAttribute = SchemaAttrib
|
|||
uuid: UUID_SCHEMA_ATTR_JWS_ES256_PRIVATE_KEY,
|
||||
name: Attribute::JwsEs256PrivateKey,
|
||||
description: "An es256 private key for jws".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
unique: true,
|
||||
syntax: SyntaxType::JwsKeyEs256,
|
||||
..Default::default()
|
||||
|
@ -546,8 +501,6 @@ pub static ref SCHEMA_ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN: SchemaAttribute = Sch
|
|||
uuid: UUID_SCHEMA_ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN,
|
||||
name: Attribute::CredentialUpdateIntentToken,
|
||||
description: "The status of a credential update intent token".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::IntentToken,
|
||||
..Default::default()
|
||||
|
@ -557,8 +510,6 @@ pub static ref SCHEMA_ATTR_PASSKEYS: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_PASSKEYS,
|
||||
name: Attribute::PassKeys,
|
||||
description: "A set of registered passkeys".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
multivalue: true,
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::Passkey,
|
||||
|
@ -569,8 +520,6 @@ pub static ref SCHEMA_ATTR_ATTESTED_PASSKEYS: SchemaAttribute = SchemaAttribute
|
|||
uuid: UUID_SCHEMA_ATTR_ATTESTED_PASSKEYS,
|
||||
name: Attribute::AttestedPasskeys,
|
||||
description: "A set of registered device keys".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
multivalue: true,
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::AttestedPasskey,
|
||||
|
@ -599,8 +548,6 @@ pub static ref SCHEMA_ATTR_API_TOKEN_SESSION: SchemaAttribute = SchemaAttribute
|
|||
uuid: UUID_SCHEMA_ATTR_API_TOKEN_SESSION,
|
||||
name: Attribute::ApiTokenSession,
|
||||
description: "A session entry related to an issued API token".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
unique: true,
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::ApiToken,
|
||||
|
@ -611,8 +558,6 @@ pub static ref SCHEMA_ATTR_USER_AUTH_TOKEN_SESSION: SchemaAttribute = SchemaAttr
|
|||
uuid: UUID_SCHEMA_ATTR_USER_AUTH_TOKEN_SESSION,
|
||||
name: Attribute::UserAuthTokenSession,
|
||||
description: "A session entry related to an issued user auth token".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
unique: true,
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::Session,
|
||||
|
@ -623,8 +568,6 @@ pub static ref SCHEMA_ATTR_OAUTH2_SESSION: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_SESSION,
|
||||
name: Attribute::OAuth2Session,
|
||||
description: "A session entry to an active oauth2 session, bound to a parent user auth token".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::Oauth2Session,
|
||||
..Default::default()
|
||||
|
@ -634,8 +577,6 @@ pub static ref SCHEMA_ATTR_SYNC_TOKEN_SESSION: SchemaAttribute = SchemaAttribute
|
|||
uuid: UUID_SCHEMA_ATTR_SYNC_TOKEN_SESSION,
|
||||
name: Attribute::SyncTokenSession,
|
||||
description: "A session entry related to an issued sync token".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
unique: true,
|
||||
syntax: SyntaxType::ApiToken,
|
||||
..Default::default()
|
||||
|
@ -654,8 +595,6 @@ pub static ref SCHEMA_ATTR_GRANT_UI_HINT: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_GRANT_UI_HINT,
|
||||
name: Attribute::GrantUiHint,
|
||||
description: "A UI hint that is granted via membership to a group".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::UiHint,
|
||||
..Default::default()
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
use crate::constants::entries::{Attribute, EntryClass};
|
||||
use crate::constants::uuids::*;
|
||||
use crate::schema::{SchemaAttribute, SchemaClass};
|
||||
use crate::value::IndexType;
|
||||
use crate::value::SyntaxType;
|
||||
|
||||
lazy_static!(
|
||||
|
@ -11,8 +10,6 @@ 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()
|
||||
|
@ -22,8 +19,6 @@ pub static ref SCHEMA_ATTR_DISPLAYNAME_DL7: 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, IndexType::SubString],
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::Utf8String,
|
||||
..Default::default()
|
||||
|
@ -33,8 +28,6 @@ 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,
|
||||
|
@ -46,8 +39,6 @@ pub static ref SCHEMA_ATTR_MAIL_DL7: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_MAIL,
|
||||
name: Attribute::Mail,
|
||||
description: "Mail addresses of the object".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality, IndexType::SubString],
|
||||
unique: true,
|
||||
multivalue: true,
|
||||
sync_allowed: true,
|
||||
|
@ -59,8 +50,6 @@ pub static ref SCHEMA_ATTR_EC_KEY_PRIVATE: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_EC_KEY_PRIVATE,
|
||||
name: Attribute::IdVerificationEcKey,
|
||||
description: "Account verification private key".to_string(),
|
||||
|
||||
index: vec![IndexType::Presence],
|
||||
unique: false,
|
||||
sync_allowed: false,
|
||||
syntax: SyntaxType::EcKeyPrivate,
|
||||
|
@ -82,8 +71,6 @@ pub static ref SCHEMA_ATTR_PRIMARY_CREDENTIAL: SchemaAttribute = SchemaAttribute
|
|||
uuid: UUID_SCHEMA_ATTR_PRIMARY_CREDENTIAL,
|
||||
name: Attribute::PrimaryCredential,
|
||||
description: "Primary credential material of the account for authentication interactively".to_string(),
|
||||
|
||||
index: vec![IndexType::Presence],
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::Credential,
|
||||
..Default::default()
|
||||
|
@ -93,8 +80,6 @@ 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()
|
||||
|
@ -104,8 +89,6 @@ pub static ref SCHEMA_ATTR_LEGALNAME_DL7: 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, IndexType::SubString],
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::Utf8String,
|
||||
..Default::default()
|
||||
|
@ -115,8 +98,6 @@ pub static ref SCHEMA_ATTR_NAME_HISTORY: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_NAME_HISTORY,
|
||||
name: Attribute::NameHistory,
|
||||
description: "The history of names that a person has had".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
multivalue: true,
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::AuditLogString,
|
||||
|
@ -137,8 +118,6 @@ pub static ref SCHEMA_ATTR_DOMAIN_NAME: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_DOMAIN_NAME,
|
||||
name: Attribute::DomainName,
|
||||
description: "The domain's DNS name for webauthn and SPN generation purposes".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality, IndexType::Presence],
|
||||
unique: true,
|
||||
syntax: SyntaxType::Utf8StringIname,
|
||||
..Default::default()
|
||||
|
@ -168,8 +147,6 @@ pub static ref SCHEMA_ATTR_DOMAIN_DISPLAY_NAME: SchemaAttribute = SchemaAttribut
|
|||
uuid: UUID_SCHEMA_ATTR_DOMAIN_DISPLAY_NAME,
|
||||
name: Attribute::DomainDisplayName,
|
||||
description: "The user-facing display name of the Kanidm domain".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
syntax: SyntaxType::Utf8String,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -178,8 +155,6 @@ pub static ref SCHEMA_ATTR_DOMAIN_UUID: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_DOMAIN_UUID,
|
||||
name: Attribute::DomainUuid,
|
||||
description: "The domain's uuid, used in CSN and trust relationships".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
unique: true,
|
||||
syntax: SyntaxType::Uuid,
|
||||
..Default::default()
|
||||
|
@ -189,8 +164,6 @@ pub static ref SCHEMA_ATTR_DOMAIN_SSID: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_DOMAIN_SSID,
|
||||
name: Attribute::DomainSsid,
|
||||
description: "The domains site-wide SSID for device autoconfiguration of wireless".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
unique: true,
|
||||
syntax: SyntaxType::Utf8String,
|
||||
..Default::default()
|
||||
|
@ -237,8 +210,6 @@ pub static ref SCHEMA_ATTR_GIDNUMBER: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_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(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
unique: true,
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::Uint32,
|
||||
|
@ -296,8 +267,6 @@ pub static ref SCHEMA_ATTR_UNIX_PASSWORD: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_UNIX_PASSWORD,
|
||||
name: Attribute::UnixPassword,
|
||||
description: "A POSIX user's UNIX login password".to_string(),
|
||||
|
||||
index: vec![IndexType::Presence],
|
||||
syntax: SyntaxType::Credential,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -306,8 +275,6 @@ pub static ref SCHEMA_ATTR_NSUNIQUEID: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_NSUNIQUEID,
|
||||
name: Attribute::NsUniqueId,
|
||||
description: "A unique id compatibility for 389-ds/dsee".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
unique: true,
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::NsUniqueId,
|
||||
|
@ -348,8 +315,6 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_NAME: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_NAME,
|
||||
name: Attribute::OAuth2RsName,
|
||||
description: "The unique name of an external Oauth2 resource".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
unique: true,
|
||||
syntax: SyntaxType::Utf8StringIname,
|
||||
..Default::default()
|
||||
|
@ -397,8 +362,6 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_CLAIM_MAP_DL4: SchemaAttribute = SchemaAttr
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_CLAIM_MAP,
|
||||
name: Attribute::OAuth2RsClaimMap,
|
||||
description: "A set of custom claims mapped to group memberships of accounts".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
multivalue: true,
|
||||
// CHANGE ME
|
||||
syntax: SyntaxType::OauthClaimMap,
|
||||
|
@ -409,8 +372,6 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_SCOPE_MAP: SchemaAttribute = SchemaAttribut
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_SCOPE_MAP,
|
||||
name: Attribute::OAuth2RsScopeMap,
|
||||
description: "A reference to a group mapped to scopes for the associated oauth2 resource server".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::OauthScopeMap,
|
||||
..Default::default()
|
||||
|
@ -420,8 +381,6 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_SUP_SCOPE_MAP: SchemaAttribute = SchemaAttr
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_SUP_SCOPE_MAP,
|
||||
name: Attribute::OAuth2RsSupScopeMap,
|
||||
description: "A reference to a group mapped to scopes for the associated oauth2 resource server".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::OauthScopeMap,
|
||||
..Default::default()
|
||||
|
@ -459,8 +418,6 @@ pub static ref SCHEMA_ATTR_OAUTH2_CONSENT_SCOPE_MAP: SchemaAttribute = SchemaAtt
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_CONSENT_SCOPE_MAP,
|
||||
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(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::OauthScopeMap,
|
||||
..Default::default()
|
||||
|
@ -507,8 +464,6 @@ pub static ref SCHEMA_ATTR_JWS_ES256_PRIVATE_KEY: SchemaAttribute = SchemaAttrib
|
|||
uuid: UUID_SCHEMA_ATTR_JWS_ES256_PRIVATE_KEY,
|
||||
name: Attribute::JwsEs256PrivateKey,
|
||||
description: "An es256 private key for jws".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
unique: true,
|
||||
syntax: SyntaxType::JwsKeyEs256,
|
||||
..Default::default()
|
||||
|
@ -546,8 +501,6 @@ pub static ref SCHEMA_ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN: SchemaAttribute = Sch
|
|||
uuid: UUID_SCHEMA_ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN,
|
||||
name: Attribute::CredentialUpdateIntentToken,
|
||||
description: "The status of a credential update intent token".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::IntentToken,
|
||||
..Default::default()
|
||||
|
@ -557,8 +510,6 @@ pub static ref SCHEMA_ATTR_PASSKEYS: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_PASSKEYS,
|
||||
name: Attribute::PassKeys,
|
||||
description: "A set of registered passkeys".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
multivalue: true,
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::Passkey,
|
||||
|
@ -569,8 +520,6 @@ pub static ref SCHEMA_ATTR_ATTESTED_PASSKEYS: SchemaAttribute = SchemaAttribute
|
|||
uuid: UUID_SCHEMA_ATTR_ATTESTED_PASSKEYS,
|
||||
name: Attribute::AttestedPasskeys,
|
||||
description: "A set of registered device keys".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
multivalue: true,
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::AttestedPasskey,
|
||||
|
@ -599,8 +548,6 @@ pub static ref SCHEMA_ATTR_API_TOKEN_SESSION: SchemaAttribute = SchemaAttribute
|
|||
uuid: UUID_SCHEMA_ATTR_API_TOKEN_SESSION,
|
||||
name: Attribute::ApiTokenSession,
|
||||
description: "A session entry related to an issued API token".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
unique: true,
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::ApiToken,
|
||||
|
@ -611,8 +558,6 @@ pub static ref SCHEMA_ATTR_USER_AUTH_TOKEN_SESSION: SchemaAttribute = SchemaAttr
|
|||
uuid: UUID_SCHEMA_ATTR_USER_AUTH_TOKEN_SESSION,
|
||||
name: Attribute::UserAuthTokenSession,
|
||||
description: "A session entry related to an issued user auth token".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
unique: true,
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::Session,
|
||||
|
@ -623,8 +568,6 @@ pub static ref SCHEMA_ATTR_OAUTH2_SESSION: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_SESSION,
|
||||
name: Attribute::OAuth2Session,
|
||||
description: "A session entry to an active oauth2 session, bound to a parent user auth token".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::Oauth2Session,
|
||||
..Default::default()
|
||||
|
@ -634,8 +577,6 @@ pub static ref SCHEMA_ATTR_SYNC_TOKEN_SESSION: SchemaAttribute = SchemaAttribute
|
|||
uuid: UUID_SCHEMA_ATTR_SYNC_TOKEN_SESSION,
|
||||
name: Attribute::SyncTokenSession,
|
||||
description: "A session entry related to an issued sync token".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
unique: true,
|
||||
syntax: SyntaxType::ApiToken,
|
||||
..Default::default()
|
||||
|
@ -654,8 +595,6 @@ pub static ref SCHEMA_ATTR_GRANT_UI_HINT: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_GRANT_UI_HINT,
|
||||
name: Attribute::GrantUiHint,
|
||||
description: "A UI hint that is granted via membership to a group".to_string(),
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::UiHint,
|
||||
..Default::default()
|
||||
|
|
|
@ -695,7 +695,6 @@ mod tests {
|
|||
|
||||
let e = entry_init!(
|
||||
(Attribute::Class, EntryClass::Person.to_value()),
|
||||
(Attribute::Class, EntryClass::System.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testperson")),
|
||||
(Attribute::DisplayName, Value::new_iname("testperson")),
|
||||
(
|
||||
|
@ -726,7 +725,6 @@ mod tests {
|
|||
|
||||
let e = entry_init!(
|
||||
(Attribute::Class, EntryClass::Person.to_value()),
|
||||
(Attribute::Class, EntryClass::System.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testperson")),
|
||||
(Attribute::DisplayName, Value::new_iname("testperson")),
|
||||
(
|
||||
|
|
|
@ -109,7 +109,7 @@ impl DynGroup {
|
|||
nd_group.purge_ava(Attribute::DynMember);
|
||||
}
|
||||
|
||||
// Insert it to the dyngroup cache with the compiled/resolved filter for
|
||||
// Insert it to the dyngroup cache with the parsed filter for
|
||||
// fast matching in other paths.
|
||||
if dyn_groups.insts.insert(uuid, scope_i).is_none() == expect {
|
||||
error!("{} cache uuid conflict {}", Attribute::DynGroup, uuid);
|
||||
|
@ -175,6 +175,11 @@ impl DynGroup {
|
|||
) -> Result<BTreeSet<Uuid>, OperationError> {
|
||||
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 (n_dyn_groups, entries): (Vec<&Entry<_, _>>, Vec<_>) = cand.iter().partition(|entry| {
|
||||
|
@ -202,9 +207,7 @@ impl DynGroup {
|
|||
let dg_filter_valid = dg_filter
|
||||
.validate(qs.get_schema())
|
||||
.map_err(OperationError::SchemaViolation)
|
||||
.and_then(|f| {
|
||||
f.resolve(&ident_internal, None, Some(qs.get_resolve_filter_cache()))
|
||||
})?;
|
||||
.and_then(|f| f.resolve(&ident_internal, None, qs.get_resolve_filter_cache()))?;
|
||||
|
||||
// Did any of our modified entries match our dyn group filter?
|
||||
let matches: Vec<_> = entries
|
||||
|
@ -291,6 +294,11 @@ impl DynGroup {
|
|||
) -> Result<BTreeSet<Uuid>, OperationError> {
|
||||
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();
|
||||
|
||||
// Probably should be filter here instead.
|
||||
|
@ -338,9 +346,7 @@ impl DynGroup {
|
|||
let dg_filter_valid = dg_filter
|
||||
.validate(qs.get_schema())
|
||||
.map_err(OperationError::SchemaViolation)
|
||||
.and_then(|f| {
|
||||
f.resolve(&ident_internal, None, Some(qs.get_resolve_filter_cache()))
|
||||
})?;
|
||||
.and_then(|f| f.resolve(&ident_internal, None, qs.get_resolve_filter_cache()))?;
|
||||
|
||||
let matches: Vec<_> = pre_entries
|
||||
.iter()
|
||||
|
|
|
@ -22,7 +22,6 @@ mod jwskeygen;
|
|||
mod keyobject;
|
||||
mod memberof;
|
||||
mod namehistory;
|
||||
mod protected;
|
||||
mod refint;
|
||||
mod session;
|
||||
mod spn;
|
||||
|
@ -44,6 +43,7 @@ trait Plugin {
|
|||
Err(OperationError::InvalidState)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn pre_create(
|
||||
_qs: &mut QueryServerWriteTransaction,
|
||||
// List of what we will commit that is valid?
|
||||
|
@ -243,13 +243,13 @@ impl Plugins {
|
|||
attrunique::AttrUnique::pre_create_transform(qs, cand, ce)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "plugins::run_pre_create", skip_all)]
|
||||
#[instrument(level = "trace", name = "plugins::run_pre_create", skip_all)]
|
||||
pub fn run_pre_create(
|
||||
qs: &mut QueryServerWriteTransaction,
|
||||
cand: &[Entry<EntrySealed, EntryNew>],
|
||||
ce: &CreateEvent,
|
||||
_qs: &mut QueryServerWriteTransaction,
|
||||
_cand: &[Entry<EntrySealed, EntryNew>],
|
||||
_ce: &CreateEvent,
|
||||
) -> Result<(), OperationError> {
|
||||
protected::Protected::pre_create(qs, cand, ce)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "plugins::run_post_create", skip_all)]
|
||||
|
@ -269,7 +269,6 @@ impl Plugins {
|
|||
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
|
||||
me: &ModifyEvent,
|
||||
) -> Result<(), OperationError> {
|
||||
protected::Protected::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)?;
|
||||
cred_import::CredImport::pre_modify(qs, pre_cand, cand, me)?;
|
||||
|
@ -305,7 +304,6 @@ impl Plugins {
|
|||
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
|
||||
me: &BatchModifyEvent,
|
||||
) -> Result<(), OperationError> {
|
||||
protected::Protected::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)?;
|
||||
cred_import::CredImport::pre_batch_modify(qs, pre_cand, cand, me)?;
|
||||
|
@ -340,7 +338,6 @@ impl Plugins {
|
|||
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
|
||||
de: &DeleteEvent,
|
||||
) -> Result<(), OperationError> {
|
||||
protected::Protected::pre_delete(qs, cand, de)?;
|
||||
memberof::MemberOf::pre_delete(qs, cand, de)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,690 +0,0 @@
|
|||
// System protected objects. Items matching specific requirements
|
||||
// may only have certain modifications performed.
|
||||
|
||||
use hashbrown::HashSet;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::event::{CreateEvent, DeleteEvent, ModifyEvent};
|
||||
use crate::modify::Modify;
|
||||
use crate::plugins::Plugin;
|
||||
use crate::prelude::*;
|
||||
|
||||
pub struct Protected {}
|
||||
|
||||
// Here is the declaration of all the attrs that can be altered by
|
||||
// a call on a system object. We trust they are allowed because
|
||||
// schema will have checked this, and we don't allow class changes!
|
||||
|
||||
lazy_static! {
|
||||
static ref ALLOWED_ATTRS: HashSet<Attribute> = {
|
||||
let attrs = vec![
|
||||
// Allow modification of some schema class types to allow local extension
|
||||
// of schema types.
|
||||
Attribute::Must,
|
||||
Attribute::May,
|
||||
// modification of some domain info types for local configuratiomn.
|
||||
Attribute::DomainSsid,
|
||||
Attribute::DomainLdapBasedn,
|
||||
Attribute::LdapMaxQueryableAttrs,
|
||||
Attribute::LdapAllowUnixPwBind,
|
||||
Attribute::FernetPrivateKeyStr,
|
||||
Attribute::Es256PrivateKeyDer,
|
||||
Attribute::KeyActionRevoke,
|
||||
Attribute::KeyActionRotate,
|
||||
Attribute::IdVerificationEcKey,
|
||||
Attribute::BadlistPassword,
|
||||
Attribute::DeniedName,
|
||||
Attribute::DomainDisplayName,
|
||||
Attribute::Image,
|
||||
// modification of account policy values for dyngroup.
|
||||
Attribute::AuthSessionExpiry,
|
||||
Attribute::AuthPasswordMinimumLength,
|
||||
Attribute::CredentialTypeMinimum,
|
||||
Attribute::PrivilegeExpiry,
|
||||
Attribute::WebauthnAttestationCaList,
|
||||
Attribute::LimitSearchMaxResults,
|
||||
Attribute::LimitSearchMaxFilterTest,
|
||||
Attribute::AllowPrimaryCredFallback,
|
||||
];
|
||||
|
||||
let mut m = HashSet::with_capacity(attrs.len());
|
||||
m.extend(attrs);
|
||||
|
||||
m
|
||||
};
|
||||
|
||||
static ref PROTECTED_ENTRYCLASSES: Vec<EntryClass> =
|
||||
vec![
|
||||
EntryClass::System,
|
||||
EntryClass::DomainInfo,
|
||||
EntryClass::SystemInfo,
|
||||
EntryClass::SystemConfig,
|
||||
EntryClass::DynGroup,
|
||||
EntryClass::SyncObject,
|
||||
EntryClass::Tombstone,
|
||||
EntryClass::Recycled,
|
||||
];
|
||||
}
|
||||
|
||||
impl Plugin for Protected {
|
||||
fn id() -> &'static str {
|
||||
"plugin_protected"
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "protected_pre_create", skip_all)]
|
||||
fn pre_create(
|
||||
_qs: &mut QueryServerWriteTransaction,
|
||||
// List of what we will commit that is valid?
|
||||
cand: &[Entry<EntrySealed, EntryNew>],
|
||||
ce: &CreateEvent,
|
||||
) -> Result<(), OperationError> {
|
||||
if ce.ident.is_internal() {
|
||||
trace!("Internal operation, not enforcing system object protection");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
cand.iter().try_fold((), |(), cand| {
|
||||
if PROTECTED_ENTRYCLASSES
|
||||
.iter()
|
||||
.any(|c| cand.attribute_equality(Attribute::Class, &c.to_partialvalue()))
|
||||
{
|
||||
trace!("Rejecting operation during pre_create check");
|
||||
Err(OperationError::SystemProtectedObject)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "protected_pre_modify", skip_all)]
|
||||
fn pre_modify(
|
||||
_qs: &mut QueryServerWriteTransaction,
|
||||
_pre_cand: &[Arc<EntrySealedCommitted>],
|
||||
cand: &mut Vec<EntryInvalidCommitted>,
|
||||
me: &ModifyEvent,
|
||||
) -> Result<(), OperationError> {
|
||||
if me.ident.is_internal() {
|
||||
trace!("Internal operation, not enforcing system object protection");
|
||||
return Ok(());
|
||||
}
|
||||
// Prevent adding class: system, domain_info, tombstone, or recycled.
|
||||
me.modlist.iter().try_fold((), |(), m| match m {
|
||||
Modify::Present(a, v) => {
|
||||
if a == Attribute::Class.as_ref()
|
||||
&& PROTECTED_ENTRYCLASSES.iter().any(|c| v == &c.to_value())
|
||||
{
|
||||
trace!("Rejecting operation during pre_modify check");
|
||||
Err(OperationError::SystemProtectedObject)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
_ => Ok(()),
|
||||
})?;
|
||||
|
||||
// HARD block mods on tombstone or recycle. We soft block on the rest as they may
|
||||
// have some allowed attrs.
|
||||
cand.iter().try_fold((), |(), cand| {
|
||||
if cand.attribute_equality(Attribute::Class, &EntryClass::Tombstone.into())
|
||||
|| cand.attribute_equality(Attribute::Class, &EntryClass::Recycled.into())
|
||||
{
|
||||
Err(OperationError::SystemProtectedObject)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})?;
|
||||
|
||||
// if class: system, check the mods are "allowed"
|
||||
let system_pres = cand.iter().any(|c| {
|
||||
// We don't need to check for domain info here because domain_info has a class
|
||||
// system also. We just need to block it from being created.
|
||||
c.attribute_equality(Attribute::Class, &EntryClass::System.into())
|
||||
});
|
||||
|
||||
trace!("class: system -> {}", system_pres);
|
||||
// No system types being altered, return.
|
||||
if !system_pres {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Something altered is system, check if it's allowed.
|
||||
me.modlist.into_iter().try_fold((), |(), m| {
|
||||
// Already hit an error, move on.
|
||||
let a = match m {
|
||||
Modify::Present(a, _)
|
||||
| Modify::Removed(a, _)
|
||||
| Modify::Set(a, _)
|
||||
| Modify::Purged(a) => Some(a),
|
||||
Modify::Assert(_, _) => None,
|
||||
};
|
||||
if let Some(attr) = a {
|
||||
match ALLOWED_ATTRS.contains(attr) {
|
||||
true => Ok(()),
|
||||
false => {
|
||||
trace!("If you're getting this, you need to modify the ALLOWED_ATTRS list");
|
||||
Err(OperationError::SystemProtectedObject)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Was not a mod needing checking
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "protected_pre_batch_modify", skip_all)]
|
||||
fn pre_batch_modify(
|
||||
_qs: &mut QueryServerWriteTransaction,
|
||||
_pre_cand: &[Arc<EntrySealedCommitted>],
|
||||
cand: &mut Vec<EntryInvalidCommitted>,
|
||||
me: &BatchModifyEvent,
|
||||
) -> Result<(), OperationError> {
|
||||
if me.ident.is_internal() {
|
||||
trace!("Internal operation, not enforcing system object protection");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
me.modset
|
||||
.values()
|
||||
.flat_map(|ml| ml.iter())
|
||||
.try_fold((), |(), m| match m {
|
||||
Modify::Present(a, v) => {
|
||||
if a == Attribute::Class.as_ref()
|
||||
&& PROTECTED_ENTRYCLASSES.iter().any(|c| v == &c.to_value())
|
||||
{
|
||||
trace!("Rejecting operation during pre_batch_modify check");
|
||||
Err(OperationError::SystemProtectedObject)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
_ => Ok(()),
|
||||
})?;
|
||||
|
||||
// HARD block mods on tombstone or recycle. We soft block on the rest as they may
|
||||
// have some allowed attrs.
|
||||
cand.iter().try_fold((), |(), cand| {
|
||||
if cand.attribute_equality(Attribute::Class, &EntryClass::Tombstone.into())
|
||||
|| cand.attribute_equality(Attribute::Class, &EntryClass::Recycled.into())
|
||||
{
|
||||
Err(OperationError::SystemProtectedObject)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})?;
|
||||
|
||||
// if class: system, check the mods are "allowed"
|
||||
let system_pres = cand.iter().any(|c| {
|
||||
// We don't need to check for domain info here because domain_info has a class
|
||||
// system also. We just need to block it from being created.
|
||||
c.attribute_equality(Attribute::Class, &EntryClass::System.into())
|
||||
});
|
||||
|
||||
trace!("{}: system -> {}", Attribute::Class, system_pres);
|
||||
// No system types being altered, return.
|
||||
if !system_pres {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Something altered is system, check if it's allowed.
|
||||
me.modset
|
||||
.values()
|
||||
.flat_map(|ml| ml.iter())
|
||||
.try_fold((), |(), m| {
|
||||
// Already hit an error, move on.
|
||||
let a = match m {
|
||||
Modify::Present(a, _) | Modify::Removed(a, _) | Modify::Set(a, _) | Modify::Purged(a) => Some(a),
|
||||
Modify::Assert(_, _) => None,
|
||||
};
|
||||
if let Some(attr) = a {
|
||||
match ALLOWED_ATTRS.contains(attr) {
|
||||
true => Ok(()),
|
||||
false => {
|
||||
|
||||
trace!("Rejecting operation during pre_batch_modify check, if you're getting this check ALLOWED_ATTRS");
|
||||
Err(OperationError::SystemProtectedObject)
|
||||
},
|
||||
}
|
||||
} else {
|
||||
// Was not a mod needing checking
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "protected_pre_delete", skip_all)]
|
||||
fn pre_delete(
|
||||
_qs: &mut QueryServerWriteTransaction,
|
||||
// Should these be EntrySealed
|
||||
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
|
||||
de: &DeleteEvent,
|
||||
) -> Result<(), OperationError> {
|
||||
if de.ident.is_internal() {
|
||||
trace!("Internal operation, not enforcing system object protection");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
cand.iter().try_fold((), |(), cand| {
|
||||
if PROTECTED_ENTRYCLASSES
|
||||
.iter()
|
||||
.any(|c| cand.attribute_equality(Attribute::Class, &c.to_partialvalue()))
|
||||
{
|
||||
trace!("Rejecting operation during pre_delete check");
|
||||
Err(OperationError::SystemProtectedObject)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
const UUID_TEST_ACCOUNT: Uuid = uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
|
||||
const UUID_TEST_GROUP: Uuid = uuid::uuid!("81ec1640-3637-4a2f-8a52-874fa3c3c92f");
|
||||
const UUID_TEST_ACP: Uuid = uuid::uuid!("acae81d6-5ea7-4bd8-8f7f-fcec4c0dd647");
|
||||
|
||||
lazy_static! {
|
||||
pub static ref TEST_ACCOUNT: EntryInitNew = entry_init!(
|
||||
(Attribute::Class, EntryClass::Account.to_value()),
|
||||
(Attribute::Class, EntryClass::ServiceAccount.to_value()),
|
||||
(Attribute::Class, EntryClass::MemberOf.to_value()),
|
||||
(Attribute::Name, Value::new_iname("test_account_1")),
|
||||
(Attribute::DisplayName, Value::new_utf8s("test_account_1")),
|
||||
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT)),
|
||||
(Attribute::MemberOf, Value::Refer(UUID_TEST_GROUP))
|
||||
);
|
||||
pub static ref TEST_GROUP: EntryInitNew = entry_init!(
|
||||
(Attribute::Class, EntryClass::Group.to_value()),
|
||||
(Attribute::Name, Value::new_iname("test_group_a")),
|
||||
(Attribute::Uuid, Value::Uuid(UUID_TEST_GROUP)),
|
||||
(Attribute::Member, Value::Refer(UUID_TEST_ACCOUNT))
|
||||
);
|
||||
pub static ref ALLOW_ALL: EntryInitNew = entry_init!(
|
||||
(Attribute::Class, EntryClass::Object.to_value()),
|
||||
(
|
||||
Attribute::Class,
|
||||
EntryClass::AccessControlProfile.to_value()
|
||||
),
|
||||
(
|
||||
Attribute::Class,
|
||||
EntryClass::AccessControlTargetScope.to_value()
|
||||
),
|
||||
(
|
||||
Attribute::Class,
|
||||
EntryClass::AccessControlReceiverGroup.to_value()
|
||||
),
|
||||
(Attribute::Class, EntryClass::AccessControlModify.to_value()),
|
||||
(Attribute::Class, EntryClass::AccessControlCreate.to_value()),
|
||||
(Attribute::Class, EntryClass::AccessControlDelete.to_value()),
|
||||
(Attribute::Class, EntryClass::AccessControlSearch.to_value()),
|
||||
(
|
||||
Attribute::Name,
|
||||
Value::new_iname("idm_admins_acp_allow_all_test")
|
||||
),
|
||||
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACP)),
|
||||
(Attribute::AcpReceiverGroup, Value::Refer(UUID_TEST_GROUP)),
|
||||
(
|
||||
Attribute::AcpTargetScope,
|
||||
Value::new_json_filter_s("{\"pres\":\"class\"}").expect("filter")
|
||||
),
|
||||
(Attribute::AcpSearchAttr, Value::from(Attribute::Name)),
|
||||
(Attribute::AcpSearchAttr, Value::from(Attribute::Class)),
|
||||
(Attribute::AcpSearchAttr, Value::from(Attribute::Uuid)),
|
||||
(Attribute::AcpSearchAttr, Value::new_iutf8("classname")),
|
||||
(
|
||||
Attribute::AcpSearchAttr,
|
||||
Value::new_iutf8(Attribute::AttributeName.as_ref())
|
||||
),
|
||||
(Attribute::AcpModifyClass, EntryClass::System.to_value()),
|
||||
(Attribute::AcpModifyClass, Value::new_iutf8("domain_info")),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Value::from(Attribute::Class)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Value::from(Attribute::DisplayName)
|
||||
),
|
||||
(Attribute::AcpModifyRemovedAttr, Value::from(Attribute::May)),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Value::from(Attribute::Must)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Value::from(Attribute::DomainName)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Value::from(Attribute::DomainDisplayName)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Value::from(Attribute::DomainUuid)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Value::from(Attribute::DomainSsid)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Value::from(Attribute::FernetPrivateKeyStr)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Value::from(Attribute::Es256PrivateKeyDer)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Value::from(Attribute::PrivateCookieKey)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Value::from(Attribute::Class)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Value::from(Attribute::DisplayName)
|
||||
),
|
||||
(Attribute::AcpModifyPresentAttr, Value::from(Attribute::May)),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Value::from(Attribute::Must)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Value::from(Attribute::DomainName)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Value::from(Attribute::DomainDisplayName)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Value::from(Attribute::DomainUuid)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Value::from(Attribute::DomainSsid)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Value::from(Attribute::FernetPrivateKeyStr)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Value::from(Attribute::Es256PrivateKeyDer)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Value::from(Attribute::PrivateCookieKey)
|
||||
),
|
||||
(Attribute::AcpCreateClass, EntryClass::Object.to_value()),
|
||||
(Attribute::AcpCreateClass, EntryClass::Account.to_value()),
|
||||
(Attribute::AcpCreateClass, EntryClass::Person.to_value()),
|
||||
(Attribute::AcpCreateClass, EntryClass::System.to_value()),
|
||||
(Attribute::AcpCreateClass, EntryClass::DomainInfo.to_value()),
|
||||
(Attribute::AcpCreateAttr, Value::from(Attribute::Name)),
|
||||
(Attribute::AcpCreateAttr, EntryClass::Class.to_value(),),
|
||||
(
|
||||
Attribute::AcpCreateAttr,
|
||||
Value::from(Attribute::Description),
|
||||
),
|
||||
(
|
||||
Attribute::AcpCreateAttr,
|
||||
Value::from(Attribute::DisplayName),
|
||||
),
|
||||
(Attribute::AcpCreateAttr, Value::from(Attribute::DomainName),),
|
||||
(
|
||||
Attribute::AcpCreateAttr,
|
||||
Value::from(Attribute::DomainDisplayName)
|
||||
),
|
||||
(Attribute::AcpCreateAttr, Value::from(Attribute::DomainUuid)),
|
||||
(Attribute::AcpCreateAttr, Value::from(Attribute::DomainSsid)),
|
||||
(Attribute::AcpCreateAttr, Value::from(Attribute::Uuid)),
|
||||
(
|
||||
Attribute::AcpCreateAttr,
|
||||
Value::from(Attribute::FernetPrivateKeyStr)
|
||||
),
|
||||
(
|
||||
Attribute::AcpCreateAttr,
|
||||
Value::from(Attribute::Es256PrivateKeyDer)
|
||||
),
|
||||
(
|
||||
Attribute::AcpCreateAttr,
|
||||
Value::from(Attribute::PrivateCookieKey)
|
||||
),
|
||||
(Attribute::AcpCreateAttr, Value::from(Attribute::Version))
|
||||
);
|
||||
pub static ref PRELOAD: Vec<EntryInitNew> =
|
||||
vec![TEST_ACCOUNT.clone(), TEST_GROUP.clone(), ALLOW_ALL.clone()];
|
||||
pub static ref E_TEST_ACCOUNT: Arc<EntrySealedCommitted> =
|
||||
Arc::new(TEST_ACCOUNT.clone().into_sealed_committed());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pre_create_deny() {
|
||||
// Test creating with class: system is rejected.
|
||||
let e = entry_init!(
|
||||
(Attribute::Class, EntryClass::Account.to_value()),
|
||||
(Attribute::Class, EntryClass::Person.to_value()),
|
||||
(Attribute::Class, EntryClass::System.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testperson")),
|
||||
(
|
||||
Attribute::DisplayName,
|
||||
Value::Utf8("testperson".to_string())
|
||||
)
|
||||
);
|
||||
|
||||
let create = vec![e];
|
||||
let preload = PRELOAD.clone();
|
||||
|
||||
run_create_test!(
|
||||
Err(OperationError::SystemProtectedObject),
|
||||
preload,
|
||||
create,
|
||||
Some(E_TEST_ACCOUNT.clone()),
|
||||
|_| {}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pre_modify_system_deny() {
|
||||
// Test modify of class to a system is denied
|
||||
let e = entry_init!(
|
||||
(Attribute::Class, EntryClass::Account.to_value()),
|
||||
(Attribute::Class, EntryClass::Person.to_value()),
|
||||
(Attribute::Class, EntryClass::System.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testperson")),
|
||||
(
|
||||
Attribute::DisplayName,
|
||||
Value::Utf8("testperson".to_string())
|
||||
)
|
||||
);
|
||||
|
||||
let mut preload = PRELOAD.clone();
|
||||
preload.push(e);
|
||||
|
||||
run_modify_test!(
|
||||
Err(OperationError::SystemProtectedObject),
|
||||
preload,
|
||||
filter!(f_eq(Attribute::Name, PartialValue::new_iname("testperson"))),
|
||||
modlist!([
|
||||
m_purge(Attribute::DisplayName),
|
||||
m_pres(Attribute::DisplayName, &Value::new_utf8s("system test")),
|
||||
]),
|
||||
Some(E_TEST_ACCOUNT.clone()),
|
||||
|_| {},
|
||||
|_| {}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pre_modify_class_add_deny() {
|
||||
// Show that adding a system class is denied
|
||||
// TODO: replace this with a `SchemaClass` object
|
||||
let e = entry_init!(
|
||||
(Attribute::Class, EntryClass::Object.to_value()),
|
||||
(Attribute::Class, EntryClass::ClassType.to_value()),
|
||||
(Attribute::ClassName, Value::new_iutf8("testclass")),
|
||||
(
|
||||
Attribute::Uuid,
|
||||
Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
|
||||
),
|
||||
(
|
||||
Attribute::Description,
|
||||
Value::Utf8("class test".to_string())
|
||||
)
|
||||
);
|
||||
let mut preload = PRELOAD.clone();
|
||||
preload.push(e);
|
||||
|
||||
run_modify_test!(
|
||||
Ok(()),
|
||||
preload,
|
||||
filter!(f_eq(
|
||||
Attribute::ClassName,
|
||||
PartialValue::new_iutf8("testclass")
|
||||
)),
|
||||
modlist!([
|
||||
m_pres(Attribute::May, &Value::from(Attribute::Name)),
|
||||
m_pres(Attribute::Must, &Value::from(Attribute::Name)),
|
||||
]),
|
||||
Some(E_TEST_ACCOUNT.clone()),
|
||||
|_| {},
|
||||
|_| {}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pre_delete_deny() {
|
||||
// Test deleting with class: system is rejected.
|
||||
let e = entry_init!(
|
||||
(Attribute::Class, EntryClass::Account.to_value()),
|
||||
(Attribute::Class, EntryClass::Person.to_value()),
|
||||
(Attribute::Class, EntryClass::System.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testperson")),
|
||||
(
|
||||
Attribute::DisplayName,
|
||||
Value::Utf8("testperson".to_string())
|
||||
)
|
||||
);
|
||||
|
||||
let mut preload = PRELOAD.clone();
|
||||
preload.push(e);
|
||||
|
||||
run_delete_test!(
|
||||
Err(OperationError::SystemProtectedObject),
|
||||
preload,
|
||||
filter!(f_eq(Attribute::Name, PartialValue::new_iname("testperson"))),
|
||||
Some(E_TEST_ACCOUNT.clone()),
|
||||
|_| {}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_modify_domain() {
|
||||
// Can edit *my* domain_ssid and domain_name
|
||||
// Show that adding a system class is denied
|
||||
let e = entry_init!(
|
||||
(Attribute::Class, EntryClass::DomainInfo.to_value()),
|
||||
(Attribute::Name, Value::new_iname("domain_example.net.au")),
|
||||
(Attribute::Uuid, Value::Uuid(uuid::uuid!("96fd1112-28bc-48ae-9dda-5acb4719aaba"))),
|
||||
(
|
||||
Attribute::Description,
|
||||
Value::new_utf8s("Demonstration of a remote domain's info being created for uuid generation in test_modify_domain")
|
||||
),
|
||||
(Attribute::DomainUuid, Value::Uuid(uuid::uuid!("96fd1112-28bc-48ae-9dda-5acb4719aaba"))),
|
||||
(Attribute::DomainName, Value::new_iname("example.net.au")),
|
||||
(Attribute::DomainDisplayName, Value::Utf8("example.net.au".to_string())),
|
||||
(Attribute::DomainSsid, Value::Utf8("Example_Wifi".to_string())),
|
||||
(Attribute::Version, Value::Uint32(1))
|
||||
);
|
||||
|
||||
let mut preload = PRELOAD.clone();
|
||||
preload.push(e);
|
||||
|
||||
run_modify_test!(
|
||||
Ok(()),
|
||||
preload,
|
||||
filter!(f_eq(
|
||||
Attribute::Name,
|
||||
PartialValue::new_iname("domain_example.net.au")
|
||||
)),
|
||||
modlist!([
|
||||
m_purge(Attribute::DomainSsid),
|
||||
m_pres(Attribute::DomainSsid, &Value::new_utf8s("NewExampleWifi")),
|
||||
]),
|
||||
Some(E_TEST_ACCOUNT.clone()),
|
||||
|_| {},
|
||||
|_| {}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ext_create_domain() {
|
||||
// can not add a domain_info type - note the lack of class: system
|
||||
let e = entry_init!(
|
||||
(Attribute::Class, EntryClass::DomainInfo.to_value()),
|
||||
(Attribute::Name, Value::new_iname("domain_example.net.au")),
|
||||
(Attribute::Uuid, Value::Uuid(uuid::uuid!("96fd1112-28bc-48ae-9dda-5acb4719aaba"))),
|
||||
(
|
||||
Attribute::Description,
|
||||
Value::new_utf8s("Demonstration of a remote domain's info being created for uuid generation in test_modify_domain")
|
||||
),
|
||||
(Attribute::DomainUuid, Value::Uuid(uuid::uuid!("96fd1112-28bc-48ae-9dda-5acb4719aaba"))),
|
||||
(Attribute::DomainName, Value::new_iname("example.net.au")),
|
||||
(Attribute::DomainDisplayName, Value::Utf8("example.net.au".to_string())),
|
||||
(Attribute::DomainSsid, Value::Utf8("Example_Wifi".to_string())),
|
||||
(Attribute::Version, Value::Uint32(1))
|
||||
);
|
||||
|
||||
let create = vec![e];
|
||||
let preload = PRELOAD.clone();
|
||||
|
||||
run_create_test!(
|
||||
Err(OperationError::SystemProtectedObject),
|
||||
preload,
|
||||
create,
|
||||
Some(E_TEST_ACCOUNT.clone()),
|
||||
|_| {}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_domain() {
|
||||
// On the real thing we have a class: system, but to prove the point ...
|
||||
let e = entry_init!(
|
||||
(Attribute::Class, EntryClass::DomainInfo.to_value()),
|
||||
(Attribute::Name, Value::new_iname("domain_example.net.au")),
|
||||
(Attribute::Uuid, Value::Uuid(uuid::uuid!("96fd1112-28bc-48ae-9dda-5acb4719aaba"))),
|
||||
(
|
||||
Attribute::Description,
|
||||
Value::new_utf8s("Demonstration of a remote domain's info being created for uuid generation in test_modify_domain")
|
||||
),
|
||||
(Attribute::DomainUuid, Value::Uuid(uuid::uuid!("96fd1112-28bc-48ae-9dda-5acb4719aaba"))),
|
||||
(Attribute::DomainName, Value::new_iname("example.net.au")),
|
||||
(Attribute::DomainDisplayName, Value::Utf8("example.net.au".to_string())),
|
||||
(Attribute::DomainSsid, Value::Utf8("Example_Wifi".to_string())),
|
||||
(Attribute::Version, Value::Uint32(1))
|
||||
);
|
||||
|
||||
let mut preload = PRELOAD.clone();
|
||||
preload.push(e);
|
||||
|
||||
run_delete_test!(
|
||||
Err(OperationError::SystemProtectedObject),
|
||||
preload,
|
||||
filter!(f_eq(
|
||||
Attribute::Name,
|
||||
PartialValue::new_iname("domain_example.net.au")
|
||||
)),
|
||||
Some(E_TEST_ACCOUNT.clone()),
|
||||
|_| {}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
use super::proto::*;
|
||||
use crate::plugins::Plugins;
|
||||
use crate::prelude::*;
|
||||
use crate::server::ChangeFlag;
|
||||
use crate::server::{ChangeFlag, ServerPhase};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::sync::Arc;
|
||||
|
||||
|
@ -343,7 +343,7 @@ impl QueryServerWriteTransaction<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
#[instrument(level = "info", skip_all)]
|
||||
fn consumer_apply_changes_v1(
|
||||
&mut self,
|
||||
ctx_domain_version: DomainVersion,
|
||||
|
@ -548,7 +548,7 @@ impl QueryServerWriteTransaction<'_> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
#[instrument(level = "info", skip_all)]
|
||||
fn consumer_apply_refresh_v1(
|
||||
&mut self,
|
||||
ctx_domain_version: DomainVersion,
|
||||
|
@ -583,6 +583,7 @@ impl QueryServerWriteTransaction<'_> {
|
|||
};
|
||||
|
||||
// == ⚠️ 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!
|
||||
self.be_txn
|
||||
|
@ -597,7 +598,6 @@ impl QueryServerWriteTransaction<'_> {
|
|||
self.reset_server_uuid()?;
|
||||
|
||||
// Delete all entries - *proper delete, not just tombstone!*
|
||||
|
||||
self.be_txn
|
||||
.danger_delete_all_db_content()
|
||||
.inspect_err(|err| {
|
||||
|
@ -609,6 +609,12 @@ impl QueryServerWriteTransaction<'_> {
|
|||
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
|
||||
// else will build upon!
|
||||
self.consumer_refresh_create_entries(ctx_schema_entries)
|
||||
|
@ -621,6 +627,9 @@ impl QueryServerWriteTransaction<'_> {
|
|||
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
|
||||
// and recreated before we start to import.
|
||||
self.reindex(false).inspect_err(|err| {
|
||||
|
@ -652,7 +661,10 @@ impl QueryServerWriteTransaction<'_> {
|
|||
| ChangeFlag::KEY_MATERIAL,
|
||||
);
|
||||
|
||||
// That's it! We are GOOD to go!
|
||||
// Domain info is now ready.
|
||||
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.
|
||||
self.consumer_refresh_create_entries(ctx_entries)
|
||||
|
@ -672,6 +684,9 @@ impl QueryServerWriteTransaction<'_> {
|
|||
error!(?err, "Unable to update RUV with supplier ranges.");
|
||||
})?;
|
||||
|
||||
// Refresh complete
|
||||
self.set_phase(ServerPhase::Running);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,16 +1,17 @@
|
|||
use super::profiles::{
|
||||
AccessControlCreateResolved, AccessControlReceiverCondition, AccessControlTargetCondition,
|
||||
};
|
||||
use super::protected::PROTECTED_ENTRY_CLASSES;
|
||||
use crate::prelude::*;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
pub(super) enum CreateResult {
|
||||
Denied,
|
||||
Deny,
|
||||
Grant,
|
||||
}
|
||||
|
||||
enum IResult {
|
||||
Denied,
|
||||
Deny,
|
||||
Grant,
|
||||
Ignore,
|
||||
}
|
||||
|
@ -25,25 +26,25 @@ pub(super) fn apply_create_access<'a>(
|
|||
|
||||
// This module can never yield a grant.
|
||||
match protected_filter_entry(ident, entry) {
|
||||
IResult::Denied => denied = true,
|
||||
IResult::Deny => denied = true,
|
||||
IResult::Grant | IResult::Ignore => {}
|
||||
}
|
||||
|
||||
match create_filter_entry(ident, related_acp, entry) {
|
||||
IResult::Denied => denied = true,
|
||||
IResult::Deny => denied = true,
|
||||
IResult::Grant => grant = true,
|
||||
IResult::Ignore => {}
|
||||
}
|
||||
|
||||
if denied {
|
||||
// Something explicitly said no.
|
||||
CreateResult::Denied
|
||||
CreateResult::Deny
|
||||
} else if grant {
|
||||
// Something said yes
|
||||
CreateResult::Grant
|
||||
} else {
|
||||
// Nothing said yes.
|
||||
CreateResult::Denied
|
||||
CreateResult::Deny
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,7 +61,7 @@ fn create_filter_entry<'a>(
|
|||
}
|
||||
IdentType::Synch(_) => {
|
||||
security_critical!("Blocking sync check");
|
||||
return IResult::Denied;
|
||||
return IResult::Deny;
|
||||
}
|
||||
IdentType::User(_) => {}
|
||||
};
|
||||
|
@ -69,7 +70,7 @@ fn create_filter_entry<'a>(
|
|||
match ident.access_scope() {
|
||||
AccessScope::ReadOnly | AccessScope::Synchronise => {
|
||||
security_access!("denied ❌ - identity access scope is not permitted to create");
|
||||
return IResult::Denied;
|
||||
return IResult::Deny;
|
||||
}
|
||||
AccessScope::ReadWrite => {
|
||||
// As you were
|
||||
|
@ -96,7 +97,7 @@ fn create_filter_entry<'a>(
|
|||
Some(s) => s.collect(),
|
||||
None => {
|
||||
admin_error!("Class set failed to build - corrupted entry?");
|
||||
return IResult::Denied;
|
||||
return IResult::Deny;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -173,22 +174,22 @@ fn protected_filter_entry(ident: &Identity, entry: &Entry<EntryInit, EntryNew>)
|
|||
}
|
||||
IdentType::Synch(_) => {
|
||||
security_access!("sync agreements may not directly create entities");
|
||||
IResult::Denied
|
||||
IResult::Deny
|
||||
}
|
||||
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
|
||||
if let Some(classes) = entry.get_ava_as_iutf8(Attribute::Class) {
|
||||
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::Denied
|
||||
} else {
|
||||
IResult::Ignore
|
||||
IResult::Deny
|
||||
}
|
||||
} else {
|
||||
// Nothing to check.
|
||||
// Nothing to check - this entry will fail to create anyway because it has
|
||||
// no classes
|
||||
IResult::Ignore
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
use super::profiles::{
|
||||
AccessControlDeleteResolved, AccessControlReceiverCondition, AccessControlTargetCondition,
|
||||
};
|
||||
use super::protected::PROTECTED_ENTRY_CLASSES;
|
||||
use crate::prelude::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub(super) enum DeleteResult {
|
||||
Denied,
|
||||
Deny,
|
||||
Grant,
|
||||
}
|
||||
|
||||
enum IResult {
|
||||
Denied,
|
||||
Deny,
|
||||
Grant,
|
||||
Ignore,
|
||||
}
|
||||
|
@ -24,25 +25,25 @@ pub(super) fn apply_delete_access<'a>(
|
|||
let mut grant = false;
|
||||
|
||||
match protected_filter_entry(ident, entry) {
|
||||
IResult::Denied => denied = true,
|
||||
IResult::Deny => denied = true,
|
||||
IResult::Grant | IResult::Ignore => {}
|
||||
}
|
||||
|
||||
match delete_filter_entry(ident, related_acp, entry) {
|
||||
IResult::Denied => denied = true,
|
||||
IResult::Deny => denied = true,
|
||||
IResult::Grant => grant = true,
|
||||
IResult::Ignore => {}
|
||||
}
|
||||
|
||||
if denied {
|
||||
// Something explicitly said no.
|
||||
DeleteResult::Denied
|
||||
DeleteResult::Deny
|
||||
} else if grant {
|
||||
// Something said yes
|
||||
DeleteResult::Grant
|
||||
} else {
|
||||
// Nothing said yes.
|
||||
DeleteResult::Denied
|
||||
DeleteResult::Deny
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,7 +60,7 @@ fn delete_filter_entry<'a>(
|
|||
}
|
||||
IdentType::Synch(_) => {
|
||||
security_critical!("Blocking sync check");
|
||||
return IResult::Denied;
|
||||
return IResult::Deny;
|
||||
}
|
||||
IdentType::User(_) => {}
|
||||
};
|
||||
|
@ -68,7 +69,7 @@ fn delete_filter_entry<'a>(
|
|||
match ident.access_scope() {
|
||||
AccessScope::ReadOnly | AccessScope::Synchronise => {
|
||||
security_access!("denied ❌ - identity access scope is not permitted to delete");
|
||||
return IResult::Denied;
|
||||
return IResult::Deny;
|
||||
}
|
||||
AccessScope::ReadWrite => {
|
||||
// As you were
|
||||
|
@ -152,28 +153,30 @@ fn protected_filter_entry(ident: &Identity, entry: &Arc<EntrySealedCommitted>) -
|
|||
}
|
||||
IdentType::Synch(_) => {
|
||||
security_access!("sync agreements may not directly delete entities");
|
||||
IResult::Denied
|
||||
IResult::Deny
|
||||
}
|
||||
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.
|
||||
if entry.get_uuid() <= UUID_ANONYMOUS {
|
||||
security_access!("attempt to delete system builtin entry");
|
||||
return IResult::Denied;
|
||||
return IResult::Deny;
|
||||
}
|
||||
|
||||
// Checks exhausted, no more input from us
|
||||
// Prevent deleting some protected types.
|
||||
if let Some(classes) = entry.get_ava_as_iutf8(Attribute::Class) {
|
||||
if classes.is_disjoint(&PROTECTED_ENTRY_CLASSES) {
|
||||
// It's different, go ahead
|
||||
IResult::Ignore
|
||||
} else {
|
||||
// Block the mod, something is present
|
||||
security_access!("attempt to create with protected class type");
|
||||
IResult::Deny
|
||||
}
|
||||
} else {
|
||||
// Nothing to check - this entry will fail to create anyway because it has
|
||||
// no classes
|
||||
IResult::Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,12 +50,13 @@ mod create;
|
|||
mod delete;
|
||||
mod modify;
|
||||
pub mod profiles;
|
||||
mod protected;
|
||||
mod search;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Access {
|
||||
Grant,
|
||||
Denied,
|
||||
Deny,
|
||||
Allow(BTreeSet<Attribute>),
|
||||
}
|
||||
|
||||
|
@ -63,7 +64,7 @@ impl From<&Access> for ScimAttributeEffectiveAccess {
|
|||
fn from(value: &Access) -> Self {
|
||||
match value {
|
||||
Access::Grant => Self::Grant,
|
||||
Access::Denied => Self::Denied,
|
||||
Access::Deny => Self::Deny,
|
||||
Access::Allow(set) => Self::Allow(set.clone()),
|
||||
}
|
||||
}
|
||||
|
@ -72,7 +73,7 @@ impl From<&Access> for ScimAttributeEffectiveAccess {
|
|||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum AccessClass {
|
||||
Grant,
|
||||
Denied,
|
||||
Deny,
|
||||
Allow(BTreeSet<AttrString>),
|
||||
}
|
||||
|
||||
|
@ -86,12 +87,22 @@ pub struct AccessEffectivePermission {
|
|||
pub search: Access,
|
||||
pub modify_pres: Access,
|
||||
pub modify_rem: Access,
|
||||
pub modify_class: AccessClass,
|
||||
pub modify_pres_class: AccessClass,
|
||||
pub modify_rem_class: AccessClass,
|
||||
}
|
||||
|
||||
pub enum AccessResult {
|
||||
pub enum AccessBasicResult {
|
||||
// Deny this operation unconditionally.
|
||||
Denied,
|
||||
Deny,
|
||||
// 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.
|
||||
Grant,
|
||||
// This module makes no decisions about this entry.
|
||||
|
@ -99,24 +110,37 @@ pub enum AccessResult {
|
|||
// Limit the allowed attr set to this - this doesn't
|
||||
// allow anything, it constrains what might be allowed
|
||||
// by a later module.
|
||||
Constrain(BTreeSet<Attribute>),
|
||||
// Allow these attributes within constraints.
|
||||
Allow(BTreeSet<Attribute>),
|
||||
/*
|
||||
Constrain {
|
||||
attr: BTreeSet<Attribute>,
|
||||
},
|
||||
*/
|
||||
Allow { attr: BTreeSet<Attribute> },
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub enum AccessResultClass<'a> {
|
||||
pub enum AccessModResult<'a> {
|
||||
// Deny this operation unconditionally.
|
||||
Denied,
|
||||
// Unbounded allow, provided no denied exists.
|
||||
Grant,
|
||||
Deny,
|
||||
// Unbounded allow, provided no deny state exists.
|
||||
// Grant,
|
||||
// This module makes no decisions about this entry.
|
||||
Ignore,
|
||||
// Limit the allowed attr set to this - this doesn't
|
||||
// allow anything, it constrains what might be allowed.
|
||||
Constrain(BTreeSet<&'a str>),
|
||||
// Allow these attributes within constraints.
|
||||
Allow(BTreeSet<&'a str>),
|
||||
// allow anything, it constrains what might be allowed
|
||||
// by a later module.
|
||||
Constrain {
|
||||
pres_attr: BTreeSet<Attribute>,
|
||||
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>,
|
||||
},
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
|
@ -303,7 +327,7 @@ pub trait AccessControlsTransaction<'a> {
|
|||
.into_iter()
|
||||
.filter(|e| {
|
||||
match apply_search_access(ident, related_acp.as_slice(), e) {
|
||||
SearchResult::Denied => false,
|
||||
SearchResult::Deny => false,
|
||||
SearchResult::Grant => true,
|
||||
SearchResult::Allow(allowed_attrs) => {
|
||||
// The allow set constrained.
|
||||
|
@ -401,7 +425,7 @@ pub trait AccessControlsTransaction<'a> {
|
|||
.into_iter()
|
||||
.filter_map(|entry| {
|
||||
match apply_search_access(&se.ident, &search_related_acp, &entry) {
|
||||
SearchResult::Denied => {
|
||||
SearchResult::Deny => {
|
||||
None
|
||||
}
|
||||
SearchResult::Grant => {
|
||||
|
@ -536,7 +560,8 @@ pub trait AccessControlsTransaction<'a> {
|
|||
// 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
|
||||
// policy.
|
||||
let mut requested_classes: BTreeSet<&str> = Default::default();
|
||||
let mut requested_pres_classes: BTreeSet<&str> = Default::default();
|
||||
let mut requested_rem_classes: BTreeSet<&str> = Default::default();
|
||||
|
||||
for modify in me.modlist.iter() {
|
||||
match modify {
|
||||
|
@ -548,27 +573,33 @@ pub trait AccessControlsTransaction<'a> {
|
|||
// 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".
|
||||
requested_classes.extend(v.to_str())
|
||||
requested_pres_classes.extend(v.to_str())
|
||||
}
|
||||
}
|
||||
Modify::Removed(a, v) => {
|
||||
if a == Attribute::Class.as_ref() {
|
||||
requested_classes.extend(v.to_str())
|
||||
requested_rem_classes.extend(v.to_str())
|
||||
}
|
||||
}
|
||||
Modify::Set(a, v) => {
|
||||
if a == Attribute::Class.as_ref() {
|
||||
// flatten to remove the option down to an iterator
|
||||
requested_classes.extend(v.as_iutf8_iter().into_iter().flatten())
|
||||
// This is a reasonably complex case - we actually have to contemplate
|
||||
// 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_rem, "Requested remove set");
|
||||
debug!(?requested_classes, "Requested class set");
|
||||
debug!(?requested_pres, "Requested present attribute set");
|
||||
debug!(?requested_rem, "Requested remove attribute set");
|
||||
debug!(?requested_pres_classes, "Requested present class set");
|
||||
debug!(?requested_rem_classes, "Requested remove class set");
|
||||
|
||||
let sync_agmts = self.get_sync_agreements();
|
||||
|
||||
|
@ -576,9 +607,16 @@ pub trait AccessControlsTransaction<'a> {
|
|||
debug!(entry_id = %e.get_display_id());
|
||||
|
||||
match apply_modify_access(&me.ident, related_acp.as_slice(), sync_agmts, e) {
|
||||
ModifyResult::Denied => false,
|
||||
ModifyResult::Deny => false,
|
||||
ModifyResult::Grant => true,
|
||||
ModifyResult::Allow { pres, rem, cls } => {
|
||||
ModifyResult::Allow {
|
||||
pres,
|
||||
rem,
|
||||
pres_cls,
|
||||
rem_cls,
|
||||
} => {
|
||||
let mut decision = true;
|
||||
|
||||
if !requested_pres.is_subset(&pres) {
|
||||
security_error!("requested_pres is not a subset of allowed");
|
||||
security_error!(
|
||||
|
@ -586,23 +624,41 @@ pub trait AccessControlsTransaction<'a> {
|
|||
requested_pres,
|
||||
pres
|
||||
);
|
||||
false
|
||||
} else if !requested_rem.is_subset(&rem) {
|
||||
decision = false
|
||||
};
|
||||
|
||||
if !requested_rem.is_subset(&rem) {
|
||||
security_error!("requested_rem is not a subset of allowed");
|
||||
security_error!("requested_rem: {:?} !⊆ allowed: {:?}", requested_rem, rem);
|
||||
false
|
||||
} else if !requested_classes.is_subset(&cls) {
|
||||
security_error!("requested_classes is not a subset of allowed");
|
||||
decision = false;
|
||||
};
|
||||
|
||||
if !requested_pres_classes.is_subset(&pres_cls) {
|
||||
security_error!("requested_pres_classes is not a subset of allowed");
|
||||
security_error!(
|
||||
"requested_classes: {:?} !⊆ allowed: {:?}",
|
||||
requested_classes,
|
||||
cls
|
||||
"requested_pres_classes: {:?} !⊆ allowed: {:?}",
|
||||
requested_pres_classes,
|
||||
pres_cls
|
||||
);
|
||||
false
|
||||
} else {
|
||||
decision = false;
|
||||
};
|
||||
|
||||
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.");
|
||||
true
|
||||
} // if acc == false
|
||||
}
|
||||
|
||||
// Yield the result
|
||||
decision
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -668,47 +724,55 @@ pub trait AccessControlsTransaction<'a> {
|
|||
})
|
||||
.collect();
|
||||
|
||||
// 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
|
||||
// policy.
|
||||
let requested_classes: BTreeSet<&str> = modlist
|
||||
.iter()
|
||||
.filter_map(|m| match m {
|
||||
let mut requested_pres_classes: BTreeSet<&str> = Default::default();
|
||||
let mut requested_rem_classes: BTreeSet<&str> = Default::default();
|
||||
|
||||
for modify in modlist.iter() {
|
||||
match modify {
|
||||
Modify::Present(a, v) => {
|
||||
if a == Attribute::Class.as_ref() {
|
||||
// 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
|
||||
requested_pres_classes.extend(v.to_str())
|
||||
}
|
||||
}
|
||||
Modify::Removed(a, v) => {
|
||||
if a == Attribute::Class.as_ref() {
|
||||
v.to_str()
|
||||
} else {
|
||||
None
|
||||
requested_rem_classes.extend(v.to_str())
|
||||
}
|
||||
}
|
||||
Modify::Set(a, v) => {
|
||||
if a == Attribute::Class.as_ref() {
|
||||
// This is a reasonably complex case - we actually have to contemplate
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
debug!(?requested_pres, "Requested present set");
|
||||
debug!(?requested_rem, "Requested remove set");
|
||||
debug!(?requested_classes, "Requested class set");
|
||||
debug!(?requested_pres_classes, "Requested present class set");
|
||||
debug!(?requested_rem_classes, "Requested remove class set");
|
||||
debug!(entry_id = %e.get_display_id());
|
||||
|
||||
let sync_agmts = self.get_sync_agreements();
|
||||
|
||||
match apply_modify_access(&me.ident, related_acp.as_slice(), sync_agmts, e) {
|
||||
ModifyResult::Denied => false,
|
||||
ModifyResult::Deny => false,
|
||||
ModifyResult::Grant => true,
|
||||
ModifyResult::Allow { pres, rem, cls } => {
|
||||
ModifyResult::Allow {
|
||||
pres,
|
||||
rem,
|
||||
pres_cls,
|
||||
rem_cls,
|
||||
} => {
|
||||
let mut decision = true;
|
||||
|
||||
if !requested_pres.is_subset(&pres) {
|
||||
security_error!("requested_pres is not a subset of allowed");
|
||||
security_error!(
|
||||
|
@ -716,23 +780,41 @@ pub trait AccessControlsTransaction<'a> {
|
|||
requested_pres,
|
||||
pres
|
||||
);
|
||||
false
|
||||
} else if !requested_rem.is_subset(&rem) {
|
||||
decision = false
|
||||
};
|
||||
|
||||
if !requested_rem.is_subset(&rem) {
|
||||
security_error!("requested_rem is not a subset of allowed");
|
||||
security_error!("requested_rem: {:?} !⊆ allowed: {:?}", requested_rem, rem);
|
||||
false
|
||||
} else if !requested_classes.is_subset(&cls) {
|
||||
security_error!("requested_classes is not a subset of allowed");
|
||||
decision = false;
|
||||
};
|
||||
|
||||
if !requested_pres_classes.is_subset(&pres_cls) {
|
||||
security_error!("requested_pres_classes is not a subset of allowed");
|
||||
security_error!(
|
||||
"requested_classes: {:?} !⊆ allowed: {:?}",
|
||||
requested_classes,
|
||||
cls
|
||||
requested_pres_classes,
|
||||
pres_cls
|
||||
);
|
||||
false
|
||||
} else {
|
||||
security_access!("passed pres, rem, classes check.");
|
||||
true
|
||||
} // if acc == false
|
||||
decision = false;
|
||||
};
|
||||
|
||||
if !requested_rem_classes.is_subset(&rem_cls) {
|
||||
security_error!("requested_rem_classes is not a subset of allowed");
|
||||
security_error!(
|
||||
"requested_classes: {:?} !⊆ allowed: {:?}",
|
||||
requested_rem_classes,
|
||||
rem_cls
|
||||
);
|
||||
decision = false;
|
||||
}
|
||||
|
||||
if decision {
|
||||
debug!("passed pres, rem, classes check.");
|
||||
}
|
||||
|
||||
// Yield the result
|
||||
decision
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -780,7 +862,7 @@ pub trait AccessControlsTransaction<'a> {
|
|||
// For each entry
|
||||
let r = entries.iter().all(|e| {
|
||||
match apply_create_access(&ce.ident, related_acp.as_slice(), e) {
|
||||
CreateResult::Denied => false,
|
||||
CreateResult::Deny => false,
|
||||
CreateResult::Grant => true,
|
||||
}
|
||||
});
|
||||
|
@ -836,7 +918,7 @@ pub trait AccessControlsTransaction<'a> {
|
|||
// For each entry
|
||||
let r = entries.iter().all(|e| {
|
||||
match apply_delete_access(&de.ident, related_acp.as_slice(), e) {
|
||||
DeleteResult::Denied => false,
|
||||
DeleteResult::Deny => false,
|
||||
DeleteResult::Grant => true,
|
||||
}
|
||||
});
|
||||
|
@ -925,7 +1007,7 @@ pub trait AccessControlsTransaction<'a> {
|
|||
) -> AccessEffectivePermission {
|
||||
// == search ==
|
||||
let search_effective = match apply_search_access(ident, search_related_acp, entry) {
|
||||
SearchResult::Denied => Access::Denied,
|
||||
SearchResult::Deny => Access::Deny,
|
||||
SearchResult::Grant => Access::Grant,
|
||||
SearchResult::Allow(allowed_attrs) => {
|
||||
// Bound by requested attrs?
|
||||
|
@ -934,14 +1016,30 @@ pub trait AccessControlsTransaction<'a> {
|
|||
};
|
||||
|
||||
// == modify ==
|
||||
let (modify_pres, modify_rem, modify_class) =
|
||||
let (modify_pres, modify_rem, modify_pres_class, modify_rem_class) =
|
||||
match apply_modify_access(ident, modify_related_acp, sync_agmts, entry) {
|
||||
ModifyResult::Denied => (Access::Denied, Access::Denied, AccessClass::Denied),
|
||||
ModifyResult::Grant => (Access::Grant, Access::Grant, AccessClass::Grant),
|
||||
ModifyResult::Allow { pres, rem, cls } => (
|
||||
ModifyResult::Deny => (
|
||||
Access::Deny,
|
||||
Access::Deny,
|
||||
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(rem.into_iter().collect()),
|
||||
AccessClass::Allow(cls.into_iter().map(|s| s.into()).collect()),
|
||||
AccessClass::Allow(pres_cls.into_iter().map(|s| s.into()).collect()),
|
||||
AccessClass::Allow(rem_cls.into_iter().map(|s| s.into()).collect()),
|
||||
),
|
||||
};
|
||||
|
||||
|
@ -949,7 +1047,7 @@ pub trait AccessControlsTransaction<'a> {
|
|||
let delete_status = apply_delete_access(ident, delete_related_acp, entry);
|
||||
|
||||
let delete = match delete_status {
|
||||
DeleteResult::Denied => false,
|
||||
DeleteResult::Deny => false,
|
||||
DeleteResult::Grant => true,
|
||||
};
|
||||
|
||||
|
@ -960,7 +1058,8 @@ pub trait AccessControlsTransaction<'a> {
|
|||
search: search_effective,
|
||||
modify_pres,
|
||||
modify_rem,
|
||||
modify_class,
|
||||
modify_pres_class,
|
||||
modify_rem_class,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2166,6 +2265,8 @@ mod tests {
|
|||
"name class",
|
||||
// And the class allowed is account
|
||||
EntryClass::Account.into(),
|
||||
// And the class allowed is account
|
||||
EntryClass::Account.into(),
|
||||
);
|
||||
// Allow member, class is group. IE not account
|
||||
let acp_deny = AccessControlModify::from_raw(
|
||||
|
@ -2182,8 +2283,8 @@ mod tests {
|
|||
"member class",
|
||||
// Allow rem name and class
|
||||
"member class",
|
||||
// And the class allowed is account
|
||||
"group",
|
||||
EntryClass::Group.into(),
|
||||
EntryClass::Group.into(),
|
||||
);
|
||||
// Does not have a pres or rem class in attrs
|
||||
let acp_no_class = AccessControlModify::from_raw(
|
||||
|
@ -2201,7 +2302,8 @@ mod tests {
|
|||
// Allow rem name and class
|
||||
"name class",
|
||||
// And the class allowed is NOT an account ...
|
||||
"group",
|
||||
EntryClass::Group.into(),
|
||||
EntryClass::Group.into(),
|
||||
);
|
||||
|
||||
// Test allowed pres
|
||||
|
@ -2287,6 +2389,7 @@ mod tests {
|
|||
"name class",
|
||||
// And the class allowed is account
|
||||
EntryClass::Account.into(),
|
||||
EntryClass::Account.into(),
|
||||
);
|
||||
|
||||
test_acp_modify!(&me_pres_ro, vec![acp_allow.clone()], &r_set, false);
|
||||
|
@ -2614,7 +2717,8 @@ mod tests {
|
|||
search: Access::Allow(btreeset![Attribute::Name]),
|
||||
modify_pres: Access::Allow(BTreeSet::new()),
|
||||
modify_rem: Access::Allow(BTreeSet::new()),
|
||||
modify_class: AccessClass::Allow(BTreeSet::new()),
|
||||
modify_pres_class: AccessClass::Allow(BTreeSet::new()),
|
||||
modify_rem_class: AccessClass::Allow(BTreeSet::new()),
|
||||
}]
|
||||
)
|
||||
}
|
||||
|
@ -2647,6 +2751,7 @@ mod tests {
|
|||
Attribute::Name.as_ref(),
|
||||
Attribute::Name.as_ref(),
|
||||
EntryClass::Object.into(),
|
||||
EntryClass::Object.into(),
|
||||
)],
|
||||
&r_set,
|
||||
vec![AccessEffectivePermission {
|
||||
|
@ -2656,7 +2761,8 @@ mod tests {
|
|||
search: Access::Allow(BTreeSet::new()),
|
||||
modify_pres: Access::Allow(btreeset![Attribute::Name]),
|
||||
modify_rem: Access::Allow(btreeset![Attribute::Name]),
|
||||
modify_class: AccessClass::Allow(btreeset![EntryClass::Object.into()]),
|
||||
modify_pres_class: AccessClass::Allow(btreeset![EntryClass::Object.into()]),
|
||||
modify_rem_class: AccessClass::Allow(btreeset![EntryClass::Object.into()]),
|
||||
}]
|
||||
)
|
||||
}
|
||||
|
@ -2796,6 +2902,7 @@ mod tests {
|
|||
&format!("{} {}", Attribute::UserAuthTokenSession, Attribute::Name),
|
||||
// And the class allowed is account, we don't use it though.
|
||||
EntryClass::Account.into(),
|
||||
EntryClass::Account.into(),
|
||||
);
|
||||
|
||||
// NOTE! Syntax doesn't matter here, we just need to assert if the attr exists
|
||||
|
@ -3296,6 +3403,7 @@ mod tests {
|
|||
"name class",
|
||||
// And the class allowed is account
|
||||
EntryClass::Account.into(),
|
||||
EntryClass::Account.into(),
|
||||
);
|
||||
|
||||
// Test allowed pres
|
||||
|
@ -3424,4 +3532,185 @@ mod tests {
|
|||
// Finally test it!
|
||||
test_acp_search_reduce!(&se_anon_ro, vec![acp], r_set, ex_anon_some);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_access_protected_deny_create() {
|
||||
sketching::test_init();
|
||||
|
||||
let ev1 = entry_init!(
|
||||
(Attribute::Class, EntryClass::Account.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testperson1")),
|
||||
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
|
||||
);
|
||||
let r1_set = vec![ev1];
|
||||
|
||||
let ev2 = entry_init!(
|
||||
(Attribute::Class, EntryClass::Account.to_value()),
|
||||
(Attribute::Class, EntryClass::System.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testperson1")),
|
||||
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
|
||||
);
|
||||
|
||||
let r2_set = vec![ev2];
|
||||
|
||||
let ce_admin = CreateEvent::new_impersonate_identity(
|
||||
Identity::from_impersonate_entry_readwrite(E_TEST_ACCOUNT_1.clone()),
|
||||
vec![],
|
||||
);
|
||||
|
||||
let acp = AccessControlCreate::from_raw(
|
||||
"test_create",
|
||||
Uuid::new_v4(),
|
||||
// Apply to admin
|
||||
UUID_TEST_GROUP_1,
|
||||
// To create matching filter testperson
|
||||
// Can this be empty?
|
||||
filter_valid!(f_eq(
|
||||
Attribute::Name,
|
||||
PartialValue::new_iname("testperson1")
|
||||
)),
|
||||
// classes
|
||||
EntryClass::Account.into(),
|
||||
// attrs
|
||||
"class name uuid",
|
||||
);
|
||||
|
||||
// Test allowed to create
|
||||
test_acp_create!(&ce_admin, vec![acp.clone()], &r1_set, true);
|
||||
// Test reject create (not allowed attr)
|
||||
test_acp_create!(&ce_admin, vec![acp.clone()], &r2_set, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_access_protected_deny_delete() {
|
||||
sketching::test_init();
|
||||
|
||||
let ev1 = entry_init!(
|
||||
(Attribute::Class, EntryClass::Account.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testperson1")),
|
||||
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
|
||||
)
|
||||
.into_sealed_committed();
|
||||
let r1_set = vec![Arc::new(ev1)];
|
||||
|
||||
let ev2 = entry_init!(
|
||||
(Attribute::Class, EntryClass::Account.to_value()),
|
||||
(Attribute::Class, EntryClass::System.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testperson1")),
|
||||
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
|
||||
)
|
||||
.into_sealed_committed();
|
||||
|
||||
let r2_set = vec![Arc::new(ev2)];
|
||||
|
||||
let de = DeleteEvent::new_impersonate_entry(
|
||||
E_TEST_ACCOUNT_1.clone(),
|
||||
filter_all!(f_eq(
|
||||
Attribute::Name,
|
||||
PartialValue::new_iname("testperson1")
|
||||
)),
|
||||
);
|
||||
|
||||
let acp = AccessControlDelete::from_raw(
|
||||
"test_delete",
|
||||
Uuid::new_v4(),
|
||||
// Apply to admin
|
||||
UUID_TEST_GROUP_1,
|
||||
// To delete testperson
|
||||
filter_valid!(f_eq(
|
||||
Attribute::Name,
|
||||
PartialValue::new_iname("testperson1")
|
||||
)),
|
||||
);
|
||||
|
||||
// Test allowed to delete
|
||||
test_acp_delete!(&de, vec![acp.clone()], &r1_set, true);
|
||||
// Test not allowed to delete
|
||||
test_acp_delete!(&de, vec![acp.clone()], &r2_set, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_access_protected_deny_modify() {
|
||||
sketching::test_init();
|
||||
|
||||
let ev1 = entry_init!(
|
||||
(Attribute::Class, EntryClass::Account.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testperson1")),
|
||||
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
|
||||
)
|
||||
.into_sealed_committed();
|
||||
let r1_set = vec![Arc::new(ev1)];
|
||||
|
||||
let ev2 = entry_init!(
|
||||
(Attribute::Class, EntryClass::Account.to_value()),
|
||||
(Attribute::Class, EntryClass::System.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testperson1")),
|
||||
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT_1))
|
||||
)
|
||||
.into_sealed_committed();
|
||||
|
||||
let r2_set = vec![Arc::new(ev2)];
|
||||
|
||||
// Allow name and class, class is account
|
||||
let acp_allow = AccessControlModify::from_raw(
|
||||
"test_modify_allow",
|
||||
Uuid::new_v4(),
|
||||
// Apply to admin
|
||||
UUID_TEST_GROUP_1,
|
||||
// To modify testperson
|
||||
filter_valid!(f_eq(
|
||||
Attribute::Name,
|
||||
PartialValue::new_iname("testperson1")
|
||||
)),
|
||||
// Allow pres disp name and class
|
||||
"displayname class",
|
||||
// Allow rem disp name and class
|
||||
"displayname class",
|
||||
// And the classes allowed to add/rem are as such
|
||||
"system recycled",
|
||||
"system recycled",
|
||||
);
|
||||
|
||||
let me_pres = ModifyEvent::new_impersonate_entry(
|
||||
E_TEST_ACCOUNT_1.clone(),
|
||||
filter_all!(f_eq(
|
||||
Attribute::Name,
|
||||
PartialValue::new_iname("testperson1")
|
||||
)),
|
||||
modlist!([m_pres(Attribute::DisplayName, &Value::new_utf8s("value"))]),
|
||||
);
|
||||
|
||||
// Test allowed pres
|
||||
test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r1_set, true);
|
||||
|
||||
// Test not allowed pres (due to system class)
|
||||
test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r2_set, false);
|
||||
|
||||
// Test that we can not remove class::system
|
||||
let me_rem_sys = ModifyEvent::new_impersonate_entry(
|
||||
E_TEST_ACCOUNT_1.clone(),
|
||||
filter_all!(f_eq(
|
||||
Attribute::Class,
|
||||
PartialValue::new_iname("testperson1")
|
||||
)),
|
||||
modlist!([m_remove(
|
||||
Attribute::Class,
|
||||
&EntryClass::System.to_partialvalue()
|
||||
)]),
|
||||
);
|
||||
|
||||
test_acp_modify!(&me_rem_sys, vec![acp_allow.clone()], &r2_set, false);
|
||||
|
||||
// Ensure that we can't add recycled.
|
||||
let me_pres = ModifyEvent::new_impersonate_entry(
|
||||
E_TEST_ACCOUNT_1.clone(),
|
||||
filter_all!(f_eq(
|
||||
Attribute::Name,
|
||||
PartialValue::new_iname("testperson1")
|
||||
)),
|
||||
modlist!([m_pres(Attribute::Class, &EntryClass::Recycled.to_value())]),
|
||||
);
|
||||
|
||||
test_acp_modify!(&me_pres, vec![acp_allow.clone()], &r1_set, false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,25 @@
|
|||
use crate::prelude::*;
|
||||
use hashbrown::HashMap;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use super::profiles::{
|
||||
AccessControlModify, AccessControlModifyResolved, AccessControlReceiverCondition,
|
||||
AccessControlTargetCondition,
|
||||
};
|
||||
use super::{AccessResult, AccessResultClass};
|
||||
use super::protected::{
|
||||
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;
|
||||
|
||||
pub(super) enum ModifyResult<'a> {
|
||||
Denied,
|
||||
Deny,
|
||||
Grant,
|
||||
Allow {
|
||||
pres: BTreeSet<Attribute>,
|
||||
rem: BTreeSet<Attribute>,
|
||||
cls: BTreeSet<&'a str>,
|
||||
pres_cls: BTreeSet<&'a str>,
|
||||
rem_cls: BTreeSet<&'a str>,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -27,12 +31,17 @@ pub(super) fn apply_modify_access<'a>(
|
|||
) -> ModifyResult<'a> {
|
||||
let mut denied = false;
|
||||
let mut grant = false;
|
||||
|
||||
let mut constrain_pres = BTreeSet::default();
|
||||
let mut allow_pres = BTreeSet::default();
|
||||
let mut constrain_rem = BTreeSet::default();
|
||||
let mut allow_rem = BTreeSet::default();
|
||||
let mut constrain_cls = BTreeSet::default();
|
||||
let mut allow_cls = BTreeSet::default();
|
||||
|
||||
let mut constrain_pres_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.
|
||||
// - needed for checking entry manager conditions.
|
||||
|
@ -43,28 +52,53 @@ pub(super) fn apply_modify_access<'a>(
|
|||
// kind of being three operations all in one.
|
||||
|
||||
match modify_ident_test(ident) {
|
||||
AccessResult::Denied => denied = true,
|
||||
AccessResult::Grant => grant = true,
|
||||
AccessResult::Ignore => {}
|
||||
AccessResult::Constrain(mut set) => constrain_pres.append(&mut set),
|
||||
AccessResult::Allow(mut set) => allow_pres.append(&mut set),
|
||||
AccessBasicResult::Deny => denied = true,
|
||||
AccessBasicResult::Grant => grant = true,
|
||||
AccessBasicResult::Ignore => {}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// Check with protected if we should proceed.
|
||||
|
||||
// If it's a sync entry, constrain it.
|
||||
match modify_sync_constrain(ident, entry, sync_agreements) {
|
||||
AccessResult::Denied => denied = true,
|
||||
AccessResult::Constrain(mut set) => {
|
||||
constrain_rem.extend(set.iter().cloned());
|
||||
constrain_pres.append(&mut set)
|
||||
AccessModResult::Deny => denied = true,
|
||||
AccessModResult::Constrain {
|
||||
mut pres_attr,
|
||||
mut rem_attr,
|
||||
..
|
||||
} => {
|
||||
constrain_rem.append(&mut rem_attr);
|
||||
constrain_pres.append(&mut pres_attr);
|
||||
}
|
||||
// Can't grant.
|
||||
AccessResult::Grant |
|
||||
// AccessModResult::Grant |
|
||||
// Can't allow
|
||||
AccessResult::Allow(_) |
|
||||
AccessResult::Ignore => {}
|
||||
AccessModResult::Allow { .. } | AccessModResult::Ignore => {}
|
||||
}
|
||||
|
||||
// Setup the acp's here
|
||||
|
@ -122,35 +156,27 @@ pub(super) fn apply_modify_access<'a>(
|
|||
.collect();
|
||||
|
||||
match modify_pres_test(scoped_acp.as_slice()) {
|
||||
AccessResult::Denied => denied = true,
|
||||
AccessModResult::Deny => denied = true,
|
||||
// Can never return a unilateral grant.
|
||||
AccessResult::Grant => {}
|
||||
AccessResult::Ignore => {}
|
||||
AccessResult::Constrain(mut set) => constrain_pres.append(&mut set),
|
||||
AccessResult::Allow(mut set) => allow_pres.append(&mut set),
|
||||
// AccessModResult::Grant => {}
|
||||
AccessModResult::Ignore => {}
|
||||
AccessModResult::Constrain { .. } => {}
|
||||
AccessModResult::Allow {
|
||||
mut pres_attr,
|
||||
mut rem_attr,
|
||||
mut pres_class,
|
||||
mut rem_class,
|
||||
} => {
|
||||
allow_pres.append(&mut pres_attr);
|
||||
allow_rem.append(&mut rem_attr);
|
||||
allow_pres_cls.append(&mut pres_class);
|
||||
allow_rem_cls.append(&mut rem_class);
|
||||
}
|
||||
|
||||
match modify_rem_test(scoped_acp.as_slice()) {
|
||||
AccessResult::Denied => denied = true,
|
||||
// Can never return a unilateral grant.
|
||||
AccessResult::Grant => {}
|
||||
AccessResult::Ignore => {}
|
||||
AccessResult::Constrain(mut set) => constrain_rem.append(&mut set),
|
||||
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 {
|
||||
ModifyResult::Denied
|
||||
ModifyResult::Deny
|
||||
} else if grant {
|
||||
ModifyResult::Grant
|
||||
} else {
|
||||
|
@ -168,31 +194,48 @@ pub(super) fn apply_modify_access<'a>(
|
|||
allow_rem
|
||||
};
|
||||
|
||||
let allowed_cls = if !constrain_cls.is_empty() {
|
||||
let mut allowed_pres_cls = if !constrain_pres_cls.is_empty() {
|
||||
// bit_and
|
||||
&constrain_cls & &allow_cls
|
||||
&constrain_pres_cls & &allow_pres_cls
|
||||
} else {
|
||||
allow_cls
|
||||
allow_pres_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 {
|
||||
pres: allowed_pres,
|
||||
rem: allowed_rem,
|
||||
cls: allowed_cls,
|
||||
pres_cls: allowed_pres_cls,
|
||||
rem_cls: allowed_rem_cls,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn modify_ident_test(ident: &Identity) -> AccessResult {
|
||||
fn modify_ident_test(ident: &Identity) -> AccessBasicResult {
|
||||
match &ident.origin {
|
||||
IdentType::Internal => {
|
||||
trace!("Internal operation, bypassing access check");
|
||||
// No need to check ACS
|
||||
return AccessResult::Grant;
|
||||
return AccessBasicResult::Grant;
|
||||
}
|
||||
IdentType::Synch(_) => {
|
||||
security_critical!("Blocking sync check");
|
||||
return AccessResult::Denied;
|
||||
return AccessBasicResult::Deny;
|
||||
}
|
||||
IdentType::User(_) => {}
|
||||
};
|
||||
|
@ -201,53 +244,56 @@ fn modify_ident_test(ident: &Identity) -> AccessResult {
|
|||
match ident.access_scope() {
|
||||
AccessScope::ReadOnly | AccessScope::Synchronise => {
|
||||
security_access!("denied ❌ - identity access scope is not permitted to modify");
|
||||
return AccessResult::Denied;
|
||||
return AccessBasicResult::Deny;
|
||||
}
|
||||
AccessScope::ReadWrite => {
|
||||
// As you were
|
||||
}
|
||||
};
|
||||
|
||||
AccessResult::Ignore
|
||||
AccessBasicResult::Ignore
|
||||
}
|
||||
|
||||
fn modify_pres_test(scoped_acp: &[&AccessControlModify]) -> AccessResult {
|
||||
let allowed_pres: BTreeSet<Attribute> = scoped_acp
|
||||
fn modify_pres_test<'a>(scoped_acp: &[&'a AccessControlModify]) -> AccessModResult<'a> {
|
||||
let pres_attr: BTreeSet<Attribute> = scoped_acp
|
||||
.iter()
|
||||
.flat_map(|acp| acp.presattrs.iter().cloned())
|
||||
.collect();
|
||||
AccessResult::Allow(allowed_pres)
|
||||
}
|
||||
|
||||
fn modify_rem_test(scoped_acp: &[&AccessControlModify]) -> AccessResult {
|
||||
let allowed_rem: BTreeSet<Attribute> = scoped_acp
|
||||
let rem_attr: BTreeSet<Attribute> = scoped_acp
|
||||
.iter()
|
||||
.flat_map(|acp| acp.remattrs.iter().cloned())
|
||||
.collect();
|
||||
AccessResult::Allow(allowed_rem)
|
||||
}
|
||||
|
||||
// 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
|
||||
let pres_class: BTreeSet<&'a str> = scoped_acp
|
||||
.iter()
|
||||
.flat_map(|acp| acp.classes.iter().map(|s| s.as_str()))
|
||||
.flat_map(|acp| acp.pres_classes.iter().map(|s| s.as_str()))
|
||||
.collect();
|
||||
AccessResultClass::Allow(allowed_classes)
|
||||
|
||||
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(
|
||||
fn modify_sync_constrain<'a>(
|
||||
ident: &Identity,
|
||||
entry: &Arc<EntrySealedCommitted>,
|
||||
sync_agreements: &HashMap<Uuid, BTreeSet<Attribute>>,
|
||||
) -> AccessResult {
|
||||
) -> AccessModResult<'a> {
|
||||
match &ident.origin {
|
||||
IdentType::Internal => AccessResult::Ignore,
|
||||
IdentType::Internal => AccessModResult::Ignore,
|
||||
IdentType::Synch(_) => {
|
||||
// Allowed to mod sync objects. Later we'll probably need to check the limits of what
|
||||
// it can do if we go that way.
|
||||
AccessResult::Ignore
|
||||
AccessModResult::Ignore
|
||||
}
|
||||
IdentType::User(_) => {
|
||||
// We need to meet these conditions.
|
||||
|
@ -259,7 +305,7 @@ fn modify_sync_constrain(
|
|||
.unwrap_or(false);
|
||||
|
||||
if !is_sync {
|
||||
return AccessResult::Ignore;
|
||||
return AccessModResult::Ignore;
|
||||
}
|
||||
|
||||
if let Some(sync_uuid) = entry.get_ava_single_refer(Attribute::SyncParentUuid) {
|
||||
|
@ -274,11 +320,115 @@ fn modify_sync_constrain(
|
|||
set.extend(sync_yield_authority.iter().cloned())
|
||||
}
|
||||
|
||||
AccessResult::Constrain(set)
|
||||
AccessModResult::Constrain {
|
||||
pres_attr: set.clone(),
|
||||
rem_attr: set,
|
||||
pres_cls: None,
|
||||
rem_cls: None,
|
||||
}
|
||||
} else {
|
||||
warn!(entry = ?entry.get_uuid(), "sync_parent_uuid not found on sync object, preventing all access");
|
||||
AccessResult::Denied
|
||||
AccessModResult::Deny
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify if the modification runs into limits that are defined by our protection rules.
|
||||
fn modify_protected_attrs<'a>(
|
||||
ident: &Identity,
|
||||
entry: &Arc<EntrySealedCommitted>,
|
||||
) -> AccessModResult<'a> {
|
||||
match &ident.origin {
|
||||
IdentType::Internal | IdentType::Synch(_) => {
|
||||
// We don't constraint or influence these.
|
||||
AccessModResult::Ignore
|
||||
}
|
||||
IdentType::User(_) => {
|
||||
if let Some(classes) = entry.get_ava_as_iutf8(Attribute::Class) {
|
||||
if classes.is_disjoint(&PROTECTED_MOD_ENTRY_CLASSES) {
|
||||
// Not protected, go ahead
|
||||
AccessModResult::Ignore
|
||||
} else {
|
||||
// Okay, the entry is protected, apply the full ruleset.
|
||||
modify_protected_entry_attrs(classes)
|
||||
}
|
||||
} else {
|
||||
// Nothing to check - this entry will fail to modify anyway because it has
|
||||
// no classes
|
||||
AccessModResult::Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn modify_protected_entry_attrs<'a>(classes: &BTreeSet<String>) -> AccessModResult<'a> {
|
||||
// This is where the majority of the logic is - this contains the modification
|
||||
// rules as they apply.
|
||||
|
||||
// First check for the hard-deny rules.
|
||||
if !classes.is_disjoint(&LOCKED_ENTRY_CLASSES) {
|
||||
// Hard deny attribute modifications to these types.
|
||||
return AccessModResult::Deny;
|
||||
}
|
||||
|
||||
let mut constrain_attrs = BTreeSet::default();
|
||||
|
||||
// Allows removal of the recycled class specifically on recycled entries.
|
||||
if classes.contains(EntryClass::Recycled.into()) {
|
||||
constrain_attrs.extend([Attribute::Class]);
|
||||
}
|
||||
|
||||
if classes.contains(EntryClass::ClassType.into()) {
|
||||
constrain_attrs.extend([Attribute::May, Attribute::Must]);
|
||||
}
|
||||
|
||||
if classes.contains(EntryClass::SystemConfig.into()) {
|
||||
constrain_attrs.extend([Attribute::BadlistPassword]);
|
||||
}
|
||||
|
||||
// Allow domain settings.
|
||||
if classes.contains(EntryClass::DomainInfo.into()) {
|
||||
constrain_attrs.extend([
|
||||
Attribute::DomainSsid,
|
||||
Attribute::DomainLdapBasedn,
|
||||
Attribute::LdapMaxQueryableAttrs,
|
||||
Attribute::LdapAllowUnixPwBind,
|
||||
Attribute::FernetPrivateKeyStr,
|
||||
Attribute::Es256PrivateKeyDer,
|
||||
Attribute::KeyActionRevoke,
|
||||
Attribute::KeyActionRotate,
|
||||
Attribute::IdVerificationEcKey,
|
||||
Attribute::DeniedName,
|
||||
Attribute::DomainDisplayName,
|
||||
Attribute::Image,
|
||||
]);
|
||||
}
|
||||
|
||||
// Allow account policy related attributes to be changed on dyngroup
|
||||
if classes.contains(EntryClass::DynGroup.into()) {
|
||||
constrain_attrs.extend([
|
||||
Attribute::AuthSessionExpiry,
|
||||
Attribute::AuthPasswordMinimumLength,
|
||||
Attribute::CredentialTypeMinimum,
|
||||
Attribute::PrivilegeExpiry,
|
||||
Attribute::WebauthnAttestationCaList,
|
||||
Attribute::LimitSearchMaxResults,
|
||||
Attribute::LimitSearchMaxFilterTest,
|
||||
Attribute::AllowPrimaryCredFallback,
|
||||
]);
|
||||
}
|
||||
|
||||
// If we don't constrain the attributes at all, we have to deny the change
|
||||
// from proceeding.
|
||||
if constrain_attrs.is_empty() {
|
||||
AccessModResult::Deny
|
||||
} else {
|
||||
AccessModResult::Constrain {
|
||||
pres_attr: constrain_attrs.clone(),
|
||||
rem_attr: constrain_attrs,
|
||||
pres_cls: None,
|
||||
rem_cls: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -266,9 +266,10 @@ pub struct AccessControlModifyResolved<'a> {
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct AccessControlModify {
|
||||
pub acp: AccessControlProfile,
|
||||
pub classes: Vec<AttrString>,
|
||||
pub presattrs: Vec<Attribute>,
|
||||
pub remattrs: Vec<Attribute>,
|
||||
pub pres_classes: Vec<AttrString>,
|
||||
pub rem_classes: Vec<AttrString>,
|
||||
}
|
||||
|
||||
impl AccessControlModify {
|
||||
|
@ -293,14 +294,25 @@ impl AccessControlModify {
|
|||
.map(|i| i.map(Attribute::from).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
let classes = value
|
||||
let classes: Vec<AttrString> = value
|
||||
.get_ava_iter_iutf8(Attribute::AcpModifyClass)
|
||||
.map(|i| i.map(AttrString::from).collect())
|
||||
.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 {
|
||||
acp: AccessControlProfile::try_from(qs, value)?,
|
||||
classes,
|
||||
pres_classes,
|
||||
rem_classes,
|
||||
presattrs,
|
||||
remattrs,
|
||||
})
|
||||
|
@ -316,7 +328,8 @@ impl AccessControlModify {
|
|||
targetscope: Filter<FilterValid>,
|
||||
presattrs: &str,
|
||||
remattrs: &str,
|
||||
classes: &str,
|
||||
pres_classes: &str,
|
||||
rem_classes: &str,
|
||||
) -> Self {
|
||||
AccessControlModify {
|
||||
acp: AccessControlProfile {
|
||||
|
@ -325,7 +338,14 @@ impl AccessControlModify {
|
|||
receiver: AccessControlReceiver::Group(btreeset!(receiver)),
|
||||
target: AccessControlTarget::Scope(targetscope),
|
||||
},
|
||||
classes: classes.split_whitespace().map(AttrString::from).collect(),
|
||||
pres_classes: pres_classes
|
||||
.split_whitespace()
|
||||
.map(AttrString::from)
|
||||
.collect(),
|
||||
rem_classes: rem_classes
|
||||
.split_whitespace()
|
||||
.map(AttrString::from)
|
||||
.collect(),
|
||||
presattrs: presattrs.split_whitespace().map(Attribute::from).collect(),
|
||||
remattrs: remattrs.split_whitespace().map(Attribute::from).collect(),
|
||||
}
|
||||
|
@ -340,7 +360,8 @@ impl AccessControlModify {
|
|||
target: AccessControlTarget,
|
||||
presattrs: &str,
|
||||
remattrs: &str,
|
||||
classes: &str,
|
||||
pres_classes: &str,
|
||||
rem_classes: &str,
|
||||
) -> Self {
|
||||
AccessControlModify {
|
||||
acp: AccessControlProfile {
|
||||
|
@ -349,7 +370,14 @@ impl AccessControlModify {
|
|||
receiver: AccessControlReceiver::EntryManager,
|
||||
target,
|
||||
},
|
||||
classes: classes.split_whitespace().map(AttrString::from).collect(),
|
||||
pres_classes: pres_classes
|
||||
.split_whitespace()
|
||||
.map(AttrString::from)
|
||||
.collect(),
|
||||
rem_classes: rem_classes
|
||||
.split_whitespace()
|
||||
.map(AttrString::from)
|
||||
.collect(),
|
||||
presattrs: presattrs.split_whitespace().map(Attribute::from).collect(),
|
||||
remattrs: remattrs.split_whitespace().map(Attribute::from).collect(),
|
||||
}
|
||||
|
|
83
server/lib/src/server/access/protected.rs
Normal file
83
server/lib/src/server/access/protected.rs
Normal file
|
@ -0,0 +1,83 @@
|
|||
use crate::prelude::EntryClass;
|
||||
use std::collections::BTreeSet;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
/// These entry classes may not be created or deleted, and may invoke some protection rules
|
||||
/// if on an entry.
|
||||
pub static PROTECTED_ENTRY_CLASSES: LazyLock<BTreeSet<String>> = LazyLock::new(|| {
|
||||
let classes = vec![
|
||||
EntryClass::System,
|
||||
EntryClass::DomainInfo,
|
||||
EntryClass::SystemInfo,
|
||||
EntryClass::SystemConfig,
|
||||
EntryClass::DynGroup,
|
||||
EntryClass::SyncObject,
|
||||
EntryClass::Tombstone,
|
||||
EntryClass::Recycled,
|
||||
];
|
||||
|
||||
BTreeSet::from_iter(classes.into_iter().map(|ec| ec.into()))
|
||||
});
|
||||
|
||||
/// Entries with these classes are protected from modifications - not that
|
||||
/// sync object is not present here as there are separate rules for that in
|
||||
/// the modification access module.
|
||||
///
|
||||
/// Recycled is also not protected here as it needs to be able to be removed
|
||||
/// by a recycle bin admin.
|
||||
pub static PROTECTED_MOD_ENTRY_CLASSES: LazyLock<BTreeSet<String>> = LazyLock::new(|| {
|
||||
let classes = vec![
|
||||
EntryClass::System,
|
||||
EntryClass::DomainInfo,
|
||||
EntryClass::SystemInfo,
|
||||
EntryClass::SystemConfig,
|
||||
EntryClass::DynGroup,
|
||||
// EntryClass::SyncObject,
|
||||
EntryClass::Tombstone,
|
||||
EntryClass::Recycled,
|
||||
];
|
||||
|
||||
BTreeSet::from_iter(classes.into_iter().map(|ec| ec.into()))
|
||||
});
|
||||
|
||||
/// These classes may NOT be added to ANY ENTRY
|
||||
pub static PROTECTED_MOD_PRES_ENTRY_CLASSES: LazyLock<BTreeSet<String>> = LazyLock::new(|| {
|
||||
let classes = vec![
|
||||
EntryClass::System,
|
||||
EntryClass::DomainInfo,
|
||||
EntryClass::SystemInfo,
|
||||
EntryClass::SystemConfig,
|
||||
EntryClass::DynGroup,
|
||||
EntryClass::SyncObject,
|
||||
EntryClass::Tombstone,
|
||||
EntryClass::Recycled,
|
||||
];
|
||||
|
||||
BTreeSet::from_iter(classes.into_iter().map(|ec| ec.into()))
|
||||
});
|
||||
|
||||
/// These classes may NOT be removed from ANY ENTRY
|
||||
pub static PROTECTED_MOD_REM_ENTRY_CLASSES: LazyLock<BTreeSet<String>> = LazyLock::new(|| {
|
||||
let classes = vec![
|
||||
EntryClass::System,
|
||||
EntryClass::DomainInfo,
|
||||
EntryClass::SystemInfo,
|
||||
EntryClass::SystemConfig,
|
||||
EntryClass::DynGroup,
|
||||
EntryClass::SyncObject,
|
||||
EntryClass::Tombstone,
|
||||
// EntryClass::Recycled,
|
||||
];
|
||||
|
||||
BTreeSet::from_iter(classes.into_iter().map(|ec| ec.into()))
|
||||
});
|
||||
|
||||
/// Entries with these classes may not be modified under any circumstance.
|
||||
pub static LOCKED_ENTRY_CLASSES: LazyLock<BTreeSet<String>> = LazyLock::new(|| {
|
||||
let classes = vec![
|
||||
EntryClass::Tombstone,
|
||||
// EntryClass::Recycled,
|
||||
];
|
||||
|
||||
BTreeSet::from_iter(classes.into_iter().map(|ec| ec.into()))
|
||||
});
|
|
@ -4,11 +4,11 @@ use std::collections::BTreeSet;
|
|||
use super::profiles::{
|
||||
AccessControlReceiverCondition, AccessControlSearchResolved, AccessControlTargetCondition,
|
||||
};
|
||||
use super::AccessResult;
|
||||
use super::AccessSrchResult;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub(super) enum SearchResult {
|
||||
Denied,
|
||||
Deny,
|
||||
Grant,
|
||||
Allow(BTreeSet<Attribute>),
|
||||
}
|
||||
|
@ -23,32 +23,32 @@ pub(super) fn apply_search_access(
|
|||
// that.
|
||||
let mut denied = false;
|
||||
let mut grant = false;
|
||||
let mut constrain = BTreeSet::default();
|
||||
let constrain = BTreeSet::default();
|
||||
let mut allow = BTreeSet::default();
|
||||
|
||||
// The access control profile
|
||||
match search_filter_entry(ident, related_acp, entry) {
|
||||
AccessResult::Denied => denied = true,
|
||||
AccessResult::Grant => grant = true,
|
||||
AccessResult::Ignore => {}
|
||||
AccessResult::Constrain(mut set) => constrain.append(&mut set),
|
||||
AccessResult::Allow(mut set) => allow.append(&mut set),
|
||||
AccessSrchResult::Deny => denied = true,
|
||||
AccessSrchResult::Grant => grant = true,
|
||||
AccessSrchResult::Ignore => {}
|
||||
// AccessSrchResult::Constrain { mut attr } => constrain.append(&mut attr),
|
||||
AccessSrchResult::Allow { mut attr } => allow.append(&mut attr),
|
||||
};
|
||||
|
||||
match search_oauth2_filter_entry(ident, entry) {
|
||||
AccessResult::Denied => denied = true,
|
||||
AccessResult::Grant => grant = true,
|
||||
AccessResult::Ignore => {}
|
||||
AccessResult::Constrain(mut set) => constrain.append(&mut set),
|
||||
AccessResult::Allow(mut set) => allow.append(&mut set),
|
||||
AccessSrchResult::Deny => denied = true,
|
||||
AccessSrchResult::Grant => grant = true,
|
||||
AccessSrchResult::Ignore => {}
|
||||
// AccessSrchResult::Constrain { mut attr } => constrain.append(&mut attr),
|
||||
AccessSrchResult::Allow { mut attr } => allow.append(&mut attr),
|
||||
};
|
||||
|
||||
match search_sync_account_filter_entry(ident, entry) {
|
||||
AccessResult::Denied => denied = true,
|
||||
AccessResult::Grant => grant = true,
|
||||
AccessResult::Ignore => {}
|
||||
AccessResult::Constrain(mut set) => constrain.append(&mut set),
|
||||
AccessResult::Allow(mut set) => allow.append(&mut set),
|
||||
AccessSrchResult::Deny => denied = true,
|
||||
AccessSrchResult::Grant => grant = true,
|
||||
AccessSrchResult::Ignore => {}
|
||||
// AccessSrchResult::Constrain{ mut attr } => constrain.append(&mut attr),
|
||||
AccessSrchResult::Allow { mut attr } => allow.append(&mut attr),
|
||||
};
|
||||
|
||||
// We'll add more modules later.
|
||||
|
@ -56,7 +56,7 @@ pub(super) fn apply_search_access(
|
|||
// Now finalise the decision.
|
||||
|
||||
if denied {
|
||||
SearchResult::Denied
|
||||
SearchResult::Deny
|
||||
} else if grant {
|
||||
SearchResult::Grant
|
||||
} else {
|
||||
|
@ -74,17 +74,17 @@ fn search_filter_entry(
|
|||
ident: &Identity,
|
||||
related_acp: &[AccessControlSearchResolved],
|
||||
entry: &Arc<EntrySealedCommitted>,
|
||||
) -> AccessResult {
|
||||
) -> AccessSrchResult {
|
||||
// If this is an internal search, return our working set.
|
||||
match &ident.origin {
|
||||
IdentType::Internal => {
|
||||
trace!(uuid = ?entry.get_display_id(), "Internal operation, bypassing access check");
|
||||
// No need to check ACS
|
||||
return AccessResult::Grant;
|
||||
return AccessSrchResult::Grant;
|
||||
}
|
||||
IdentType::Synch(_) => {
|
||||
security_debug!(uuid = ?entry.get_display_id(), "Blocking sync check");
|
||||
return AccessResult::Denied;
|
||||
return AccessSrchResult::Deny;
|
||||
}
|
||||
IdentType::User(_) => {}
|
||||
};
|
||||
|
@ -95,7 +95,7 @@ fn search_filter_entry(
|
|||
security_debug!(
|
||||
"denied ❌ - identity access scope 'Synchronise' is not permitted to search"
|
||||
);
|
||||
return AccessResult::Denied;
|
||||
return AccessSrchResult::Deny;
|
||||
}
|
||||
AccessScope::ReadOnly | AccessScope::ReadWrite => {
|
||||
// As you were
|
||||
|
@ -161,16 +161,21 @@ fn search_filter_entry(
|
|||
.flatten()
|
||||
.collect();
|
||||
|
||||
AccessResult::Allow(allowed_attrs)
|
||||
AccessSrchResult::Allow {
|
||||
attr: allowed_attrs,
|
||||
}
|
||||
}
|
||||
|
||||
fn search_oauth2_filter_entry(ident: &Identity, entry: &Arc<EntrySealedCommitted>) -> AccessResult {
|
||||
fn search_oauth2_filter_entry(
|
||||
ident: &Identity,
|
||||
entry: &Arc<EntrySealedCommitted>,
|
||||
) -> AccessSrchResult {
|
||||
match &ident.origin {
|
||||
IdentType::Internal | IdentType::Synch(_) => AccessResult::Ignore,
|
||||
IdentType::Internal | IdentType::Synch(_) => AccessSrchResult::Ignore,
|
||||
IdentType::User(iuser) => {
|
||||
if iuser.entry.get_uuid() == UUID_ANONYMOUS {
|
||||
debug!("Anonymous can't access OAuth2 entries, ignoring");
|
||||
return AccessResult::Ignore;
|
||||
return AccessSrchResult::Ignore;
|
||||
}
|
||||
|
||||
let contains_o2_rs = entry
|
||||
|
@ -190,16 +195,18 @@ fn search_oauth2_filter_entry(ident: &Identity, entry: &Arc<EntrySealedCommitted
|
|||
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");
|
||||
|
||||
return AccessResult::Allow(btreeset!(
|
||||
return AccessSrchResult::Allow {
|
||||
attr: btreeset!(
|
||||
Attribute::Class,
|
||||
Attribute::DisplayName,
|
||||
Attribute::Uuid,
|
||||
Attribute::Name,
|
||||
Attribute::OAuth2RsOriginLanding,
|
||||
Attribute::Image
|
||||
));
|
||||
),
|
||||
};
|
||||
}
|
||||
AccessResult::Ignore
|
||||
AccessSrchResult::Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -207,9 +214,9 @@ fn search_oauth2_filter_entry(ident: &Identity, entry: &Arc<EntrySealedCommitted
|
|||
fn search_sync_account_filter_entry(
|
||||
ident: &Identity,
|
||||
entry: &Arc<EntrySealedCommitted>,
|
||||
) -> AccessResult {
|
||||
) -> AccessSrchResult {
|
||||
match &ident.origin {
|
||||
IdentType::Internal | IdentType::Synch(_) => AccessResult::Ignore,
|
||||
IdentType::Internal | IdentType::Synch(_) => AccessSrchResult::Ignore,
|
||||
IdentType::User(iuser) => {
|
||||
// Is the user a synced object?
|
||||
let is_user_sync_account = iuser
|
||||
|
@ -244,16 +251,18 @@ fn search_sync_account_filter_entry(
|
|||
// We finally got here!
|
||||
security_debug!(entry = ?entry.get_uuid(), ident = ?iuser.entry.get_uuid2rdn(), "ident is a synchronised account from this sync account");
|
||||
|
||||
return AccessResult::Allow(btreeset!(
|
||||
return AccessSrchResult::Allow {
|
||||
attr: btreeset!(
|
||||
Attribute::Class,
|
||||
Attribute::Uuid,
|
||||
Attribute::SyncCredentialPortal
|
||||
));
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fall through
|
||||
AccessResult::Ignore
|
||||
AccessSrchResult::Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ use crate::value::{CredentialType, EXTRACT_VAL_DN};
|
|||
use crate::valueset::uuid_to_proto_string;
|
||||
use crate::valueset::ScimValueIntermediate;
|
||||
use crate::valueset::*;
|
||||
use concread::arcache::{ARCacheBuilder, ARCacheReadTxn};
|
||||
use concread::arcache::{ARCacheBuilder, ARCacheReadTxn, ARCacheWriteTxn};
|
||||
use concread::cowcell::*;
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use kanidm_proto::internal::{DomainInfo as ProtoDomainInfo, ImageValue, UiHint};
|
||||
|
@ -205,6 +205,13 @@ pub struct QueryServerWriteTransaction<'a> {
|
|||
pub(super) changed_uuid: HashSet<Uuid>,
|
||||
_db_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<
|
||||
'a,
|
||||
(IdentityId, Arc<Filter<FilterValid>>),
|
||||
|
@ -260,7 +267,7 @@ pub trait QueryServerTransaction<'a> {
|
|||
|
||||
fn get_domain_image_value(&self) -> Option<ImageValue>;
|
||||
|
||||
fn get_resolve_filter_cache(&mut self) -> &mut ResolveFilterCacheReadTxn<'a>;
|
||||
fn get_resolve_filter_cache(&mut self) -> Option<&mut ResolveFilterCacheReadTxn<'a>>;
|
||||
|
||||
// Because of how borrowck in rust works, if we need to get two inner types we have to get them
|
||||
// in a single fn.
|
||||
|
@ -269,7 +276,7 @@ pub trait QueryServerTransaction<'a> {
|
|||
&mut self,
|
||||
) -> (
|
||||
&mut Self::BackendTransactionType,
|
||||
&mut ResolveFilterCacheReadTxn<'a>,
|
||||
Option<&mut ResolveFilterCacheReadTxn<'a>>,
|
||||
);
|
||||
|
||||
/// Conduct a search and apply access controls to yield a set of entries that
|
||||
|
@ -326,11 +333,15 @@ pub trait QueryServerTransaction<'a> {
|
|||
// NOTE: Filters are validated in event conversion.
|
||||
|
||||
let (be_txn, resolve_filter_cache) = self.get_resolve_filter_cache_and_be_txn();
|
||||
|
||||
let idxmeta = be_txn.get_idxmeta_ref();
|
||||
|
||||
trace!(resolve_filter_cache = %resolve_filter_cache.is_some());
|
||||
|
||||
// Now resolve all references and indexes.
|
||||
let vfr = se
|
||||
.filter
|
||||
.resolve(&se.ident, Some(idxmeta), Some(resolve_filter_cache))
|
||||
.resolve(&se.ident, Some(idxmeta), resolve_filter_cache)
|
||||
.map_err(|e| {
|
||||
admin_error!(?e, "search filter resolve failure");
|
||||
e
|
||||
|
@ -366,7 +377,7 @@ pub trait QueryServerTransaction<'a> {
|
|||
|
||||
let vfr = ee
|
||||
.filter
|
||||
.resolve(&ee.ident, Some(idxmeta), Some(resolve_filter_cache))
|
||||
.resolve(&ee.ident, Some(idxmeta), resolve_filter_cache)
|
||||
.map_err(|e| {
|
||||
admin_error!(?e, "Failed to resolve filter");
|
||||
e
|
||||
|
@ -1444,17 +1455,17 @@ impl<'a> QueryServerTransaction<'a> for QueryServerReadTransaction<'a> {
|
|||
&self.key_providers
|
||||
}
|
||||
|
||||
fn get_resolve_filter_cache(&mut self) -> &mut ResolveFilterCacheReadTxn<'a> {
|
||||
&mut self.resolve_filter_cache
|
||||
fn get_resolve_filter_cache(&mut self) -> Option<&mut ResolveFilterCacheReadTxn<'a>> {
|
||||
Some(&mut self.resolve_filter_cache)
|
||||
}
|
||||
|
||||
fn get_resolve_filter_cache_and_be_txn(
|
||||
&mut self,
|
||||
) -> (
|
||||
&mut BackendReadTransaction<'a>,
|
||||
&mut ResolveFilterCacheReadTxn<'a>,
|
||||
Option<&mut ResolveFilterCacheReadTxn<'a>>,
|
||||
) {
|
||||
(&mut self.be_txn, &mut self.resolve_filter_cache)
|
||||
(&mut self.be_txn, Some(&mut self.resolve_filter_cache))
|
||||
}
|
||||
|
||||
fn pw_badlist(&self) -> &HashSet<String> {
|
||||
|
@ -1678,17 +1689,25 @@ impl<'a> QueryServerTransaction<'a> for QueryServerWriteTransaction<'a> {
|
|||
&self.key_providers
|
||||
}
|
||||
|
||||
fn get_resolve_filter_cache(&mut self) -> &mut ResolveFilterCacheReadTxn<'a> {
|
||||
&mut self.resolve_filter_cache
|
||||
fn get_resolve_filter_cache(&mut self) -> Option<&mut ResolveFilterCacheReadTxn<'a>> {
|
||||
if self.resolve_filter_cache_clear || *self.phase < ServerPhase::SchemaReady {
|
||||
None
|
||||
} else {
|
||||
Some(&mut self.resolve_filter_cache)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_resolve_filter_cache_and_be_txn(
|
||||
&mut self,
|
||||
) -> (
|
||||
&mut BackendWriteTransaction<'a>,
|
||||
&mut ResolveFilterCacheReadTxn<'a>,
|
||||
Option<&mut ResolveFilterCacheReadTxn<'a>>,
|
||||
) {
|
||||
(&mut self.be_txn, &mut self.resolve_filter_cache)
|
||||
if self.resolve_filter_cache_clear || *self.phase < ServerPhase::SchemaReady {
|
||||
(&mut self.be_txn, None)
|
||||
} else {
|
||||
(&mut self.be_txn, Some(&mut self.resolve_filter_cache))
|
||||
}
|
||||
}
|
||||
|
||||
fn pw_badlist(&self) -> &HashSet<String> {
|
||||
|
@ -2003,6 +2022,8 @@ impl QueryServer {
|
|||
_db_ticket: db_ticket,
|
||||
_write_ticket: write_ticket,
|
||||
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(),
|
||||
key_providers: self.key_providers.write(),
|
||||
})
|
||||
|
@ -2152,16 +2173,13 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
|||
))
|
||||
}?;
|
||||
|
||||
// TODO: Clear the filter resolve cache.
|
||||
// currently we can't do this because of the limits of types with arccache txns. The only
|
||||
// thing this impacts is if something in indexed though, and the backend does handle
|
||||
// incorrectly indexed items correctly.
|
||||
// Since we reloaded the schema, we need to reload the filter cache since it
|
||||
// may have incorrect or outdated information about indexes now.
|
||||
self.resolve_filter_cache_clear = true;
|
||||
|
||||
// Trigger reloads on services that require post-schema reloads.
|
||||
// Mainly this is plugins.
|
||||
if *self.phase >= ServerPhase::SchemaReady {
|
||||
DynGroup::reload(self)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -2584,7 +2602,14 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
|||
self.changed_flags.remove(ChangeFlag::OAUTH2)
|
||||
}
|
||||
|
||||
fn set_phase(&mut self, phase: ServerPhase) {
|
||||
/// Indicate that we are about to re-bootstrap this server. You should ONLY
|
||||
/// 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
|
||||
if phase > *self.phase {
|
||||
*self.phase = phase
|
||||
|
@ -2698,6 +2723,8 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
|||
changed_flags,
|
||||
changed_uuid: _,
|
||||
resolve_filter_cache: _,
|
||||
resolve_filter_cache_clear,
|
||||
mut resolve_filter_cache_write,
|
||||
} = self;
|
||||
debug_assert!(!committed);
|
||||
|
||||
|
@ -2711,6 +2738,12 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
|||
be_txn.set_db_ts_max(cid.ts)?;
|
||||
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.
|
||||
//
|
||||
// = Lets commit =
|
||||
|
|
|
@ -388,6 +388,65 @@ 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(
|
||||
Hash,
|
||||
Debug,
|
||||
|
|
|
@ -14,87 +14,73 @@ const ALLOWED_ATTRIBUTES: &[&str] = &[
|
|||
"role",
|
||||
"output_mode",
|
||||
"log_level",
|
||||
"ldap",
|
||||
];
|
||||
|
||||
fn parse_knobs(
|
||||
input: &syn::ItemFn,
|
||||
server_config: &Punctuated<ExprAssign, syn::token::Comma>,
|
||||
) -> TokenStream {
|
||||
// If type mismatch occurs, the current rustc points to the last statement.
|
||||
let (last_stmt_start_span, _last_stmt_end_span) = {
|
||||
let mut last_stmt = input
|
||||
.block
|
||||
.stmts
|
||||
.last()
|
||||
.map(ToTokens::into_token_stream)
|
||||
.unwrap_or_default()
|
||||
.into_iter();
|
||||
// `Span` on stable Rust has a limitation that only points to the first
|
||||
// token, not the whole tokens. We can work around this limitation by
|
||||
// using the first/last span of the tokens like
|
||||
// `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());
|
||||
(start, end)
|
||||
};
|
||||
#[derive(Default)]
|
||||
struct Flags {
|
||||
ldap: bool,
|
||||
}
|
||||
|
||||
// here we gather all the provided configuration in a struct like declaration
|
||||
// By now we have already checked that the configurations provided belong to the allowed subset
|
||||
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,})
|
||||
fn parse_attributes(
|
||||
args: &TokenStream,
|
||||
input: &syn::ItemFn,
|
||||
) -> Result<(proc_macro2::TokenStream, Flags), syn::Error> {
|
||||
let args: Punctuated<ExprAssign, syn::token::Comma> =
|
||||
Punctuated::<ExprAssign, Token![,]>::parse_terminated.parse(args.clone())?;
|
||||
|
||||
let args_are_allowed = args.pairs().all(|p| {
|
||||
ALLOWED_ATTRIBUTES.to_vec().contains(
|
||||
&p.value()
|
||||
.left
|
||||
.span()
|
||||
.source_text()
|
||||
.unwrap_or_default()
|
||||
.as_str(),
|
||||
)
|
||||
});
|
||||
|
||||
// Setup the config filling the remaining fields with the default values
|
||||
let default_config_struct = quote!(kanidmd_core::config::Configuration {
|
||||
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()
|
||||
.as_str()
|
||||
{
|
||||
"ldap" => {
|
||||
flags.ldap = true;
|
||||
field_modifications.extend(quote! {
|
||||
ldapbindaddress: Some("on".to_string()),})
|
||||
}
|
||||
_ => {
|
||||
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.
|
||||
field_modifications.extend(quote! {
|
||||
#field_name: #field_value,})
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let ts = quote!(kanidmd_core::config::Configuration {
|
||||
#field_modifications
|
||||
..kanidmd_core::config::Configuration::new_for_test()
|
||||
});
|
||||
|
||||
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
|
||||
Ok((ts, flags))
|
||||
}
|
||||
|
||||
pub(crate) fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
|
@ -115,31 +101,80 @@ pub(crate) fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
|||
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));
|
||||
}
|
||||
let args: Punctuated<ExprAssign, syn::token::Comma> =
|
||||
match Punctuated::<ExprAssign, Token![,]>::parse_terminated.parse(args.clone()) {
|
||||
Ok(it) => it,
|
||||
|
||||
// If type mismatch occurs, the current rustc points to the last statement.
|
||||
let (last_stmt_start_span, _last_stmt_end_span) = {
|
||||
let mut last_stmt = input
|
||||
.block
|
||||
.stmts
|
||||
.last()
|
||||
.map(ToTokens::into_token_stream)
|
||||
.unwrap_or_default()
|
||||
.into_iter();
|
||||
// `Span` on stable Rust has a limitation that only points to the first
|
||||
// token, not the whole tokens. We can work around this limitation by
|
||||
// using the first/last span of the tokens like
|
||||
// `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());
|
||||
(start, end)
|
||||
};
|
||||
|
||||
// Setup the config filling the remaining fields with the default values
|
||||
let (default_config_struct, flags) = match parse_attributes(&args, &input) {
|
||||
Ok(dc) => dc,
|
||||
Err(e) => return token_stream_with_error(args, e),
|
||||
};
|
||||
let args_are_allowed = args.pairs().all(|p| {
|
||||
ALLOWED_ATTRIBUTES.to_vec().contains(
|
||||
&p.value()
|
||||
.left
|
||||
.span()
|
||||
.source_text()
|
||||
.unwrap_or_default()
|
||||
.as_str(),
|
||||
)
|
||||
});
|
||||
if !args_are_allowed {
|
||||
let msg =
|
||||
"Currently only a subset of all the server configs can be set. Here is the full list";
|
||||
return token_stream_with_error(
|
||||
item,
|
||||
syn::Error::new_spanned(
|
||||
input.sig.fn_token,
|
||||
format!("{}: {}", msg, ALLOWED_ATTRIBUTES.join(", ")),
|
||||
),
|
||||
);
|
||||
|
||||
let rt = quote_spanned! {last_stmt_start_span=>
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
};
|
||||
|
||||
let header = quote! {
|
||||
#[::core::prelude::v1::test]
|
||||
};
|
||||
|
||||
let test_fn_args = if flags.ldap {
|
||||
quote! {
|
||||
&test_env
|
||||
}
|
||||
parse_knobs(&input, &args)
|
||||
} else {
|
||||
quote! {
|
||||
&test_env.rsclient
|
||||
}
|
||||
};
|
||||
|
||||
let test_fn = &input.sig.ident;
|
||||
let test_driver = Ident::new(&format!("tk_{}", test_fn), input.sig.span());
|
||||
|
||||
// Effectively we are just injecting a real test function around this which we will
|
||||
// call.
|
||||
let result = quote! {
|
||||
#input
|
||||
|
||||
#header
|
||||
fn #test_driver() {
|
||||
let body = async {
|
||||
let mut test_env = kanidmd_testkit::setup_async_test(#default_config_struct).await;
|
||||
|
||||
#test_fn(#test_fn_args).await;
|
||||
test_env.core_handle.shutdown().await;
|
||||
};
|
||||
#[allow(clippy::expect_used, clippy::diverging_sub_expression)]
|
||||
{
|
||||
return #rt
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed building the Runtime")
|
||||
.block_on(body);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
result.into()
|
||||
}
|
||||
|
||||
fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream {
|
||||
tokens.extend(TokenStream::from(error.into_compile_error()));
|
||||
tokens
|
||||
}
|
||||
|
|
|
@ -51,8 +51,9 @@ kanidm_build_profiles = { workspace = true }
|
|||
compact_jwt = { workspace = true }
|
||||
escargot = "0.5.13"
|
||||
# used for webdriver testing
|
||||
fantoccini = { version = "0.21.4" }
|
||||
fantoccini = { version = "0.21.5" }
|
||||
futures = { workspace = true }
|
||||
ldap3_client = { workspace = true }
|
||||
oauth2_ext = { workspace = true, default-features = false, features = [
|
||||
"reqwest",
|
||||
] }
|
||||
|
@ -63,7 +64,7 @@ tokio-openssl = { workspace = true }
|
|||
kanidm_lib_crypto = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
webauthn-authenticator-rs = { workspace = true }
|
||||
jsonschema = "0.29.0"
|
||||
jsonschema = "0.29.1"
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = ["escargot", "futures", "kanidm_build_profiles"]
|
||||
|
|
|
@ -10,16 +10,16 @@
|
|||
#![deny(clippy::needless_pass_by_value)]
|
||||
#![deny(clippy::trivially_copy_pass_by_ref)]
|
||||
|
||||
use std::net::TcpStream;
|
||||
use std::sync::atomic::{AtomicU16, Ordering};
|
||||
|
||||
use kanidm_client::{KanidmClient, KanidmClientBuilder};
|
||||
|
||||
use kanidm_proto::internal::{Filter, Modify, ModifyList};
|
||||
use kanidmd_core::config::{Configuration, IntegrationTestConfig};
|
||||
use kanidmd_core::{create_server_core, CoreHandle};
|
||||
use kanidmd_lib::prelude::{Attribute, NAME_SYSTEM_ADMINS};
|
||||
use std::net::TcpStream;
|
||||
use std::sync::atomic::{AtomicU16, Ordering};
|
||||
use tokio::task;
|
||||
use tracing::error;
|
||||
use url::Url;
|
||||
|
||||
pub const ADMIN_TEST_USER: &str = "admin";
|
||||
pub const ADMIN_TEST_PASSWORD: &str = "integration test admin password";
|
||||
|
@ -46,14 +46,9 @@ pub fn is_free_port(port: u16) -> bool {
|
|||
}
|
||||
|
||||
// Test external behaviours of the service.
|
||||
|
||||
// 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();
|
||||
|
||||
fn port_loop() -> u16 {
|
||||
let mut counter = 0;
|
||||
let port = loop {
|
||||
loop {
|
||||
let possible_port = PORT_ALLOC.fetch_add(1, Ordering::SeqCst);
|
||||
if is_free_port(possible_port) {
|
||||
break possible_port;
|
||||
|
@ -64,7 +59,21 @@ pub async fn setup_async_test(mut config: Configuration) -> (KanidmClient, CoreH
|
|||
tracing::error!("Unable to allocate port!");
|
||||
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 {
|
||||
admin_user: ADMIN_TEST_USER.to_string(),
|
||||
|
@ -75,6 +84,16 @@ pub async fn setup_async_test(mut config: Configuration) -> (KanidmClient, CoreH
|
|||
|
||||
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..
|
||||
config.address = format!("127.0.0.1:{}", port);
|
||||
config.integration_test_config = Some(int_config);
|
||||
|
@ -102,7 +121,11 @@ pub async fn setup_async_test(mut config: Configuration) -> (KanidmClient, CoreH
|
|||
|
||||
tracing::info!("Testkit server setup complete - {}", addr);
|
||||
|
||||
(rsclient, core_handle)
|
||||
AsyncTestEnvironment {
|
||||
rsclient,
|
||||
core_handle,
|
||||
ldap_url,
|
||||
}
|
||||
}
|
||||
|
||||
/// creates a user (username: `id`) and puts them into a group, creating it if need be.
|
||||
|
|
|
@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
|
|||
use tracing::info;
|
||||
|
||||
#[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)]
|
||||
struct OpenAPIResponse {
|
||||
pub openapi: String,
|
||||
|
|
|
@ -3,7 +3,7 @@ use kanidm_proto::constants::ATTR_DOMAIN_DISPLAY_NAME;
|
|||
use kanidmd_testkit::{ADMIN_TEST_PASSWORD, ADMIN_TEST_USER};
|
||||
|
||||
#[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
|
||||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||
.await
|
||||
|
@ -13,8 +13,9 @@ async fn test_idm_set_ldap_allow_unix_password_bind(rsclient: KanidmClient) {
|
|||
.await
|
||||
.expect("Failed to set LDAP allow unix password bind to true");
|
||||
}
|
||||
|
||||
#[kanidmd_testkit::test]
|
||||
async fn test_idm_domain_set_ldap_basedn(rsclient: KanidmClient) {
|
||||
async fn test_idm_domain_set_ldap_basedn(rsclient: &KanidmClient) {
|
||||
rsclient
|
||||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||
.await
|
||||
|
@ -27,7 +28,7 @@ async fn test_idm_domain_set_ldap_basedn(rsclient: KanidmClient) {
|
|||
}
|
||||
|
||||
#[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
|
||||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||
.await
|
||||
|
@ -40,7 +41,7 @@ async fn test_idm_domain_set_ldap_max_queryable_attrs(rsclient: KanidmClient) {
|
|||
}
|
||||
|
||||
#[kanidmd_testkit::test]
|
||||
async fn test_idm_domain_set_display_name(rsclient: KanidmClient) {
|
||||
async fn test_idm_domain_set_display_name(rsclient: &KanidmClient) {
|
||||
rsclient
|
||||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||
.await
|
||||
|
|
|
@ -4,7 +4,7 @@ use kanidmd_testkit::{create_user, ADMIN_TEST_PASSWORD, ADMIN_TEST_USER};
|
|||
use serde_json::Value;
|
||||
|
||||
#[kanidmd_testkit::test]
|
||||
async fn test_v1_group_id_patch(rsclient: KanidmClient) {
|
||||
async fn test_v1_group_id_patch(rsclient: &KanidmClient) {
|
||||
let res = rsclient
|
||||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
|
@ -25,7 +25,7 @@ async fn test_v1_group_id_patch(rsclient: KanidmClient) {
|
|||
}
|
||||
|
||||
#[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
|
||||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use kanidm_client::{http::header, KanidmClient};
|
||||
|
||||
#[kanidmd_testkit::test]
|
||||
async fn test_https_manifest(rsclient: KanidmClient) {
|
||||
async fn test_https_manifest(rsclient: &KanidmClient) {
|
||||
// We need to do manual reqwests here.
|
||||
let client = rsclient.client();
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ const DEFAULT_IP_ADDRESS: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
|
|||
// *test where we don't trust the x-forwarded-for header
|
||||
|
||||
#[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 res = client
|
||||
|
@ -32,7 +32,7 @@ async fn dont_trust_xff_send_header(rsclient: KanidmClient) {
|
|||
}
|
||||
|
||||
#[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 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
|
||||
|
||||
#[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 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.
|
||||
//
|
||||
#[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 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)]
|
||||
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 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)]
|
||||
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 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)]
|
||||
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 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)]
|
||||
async fn trust_xff_dont_send_header(rsclient: KanidmClient) {
|
||||
async fn trust_xff_dont_send_header(rsclient: &KanidmClient) {
|
||||
let client = rsclient.client();
|
||||
|
||||
let res = client
|
||||
|
|
|
@ -2,7 +2,7 @@ use kanidm_client::http::header;
|
|||
use kanidm_client::KanidmClient;
|
||||
|
||||
#[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.
|
||||
let client = rsclient.client();
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ static USER_B_NAME: &str = "valid_user_b";
|
|||
// These tests check that invalid requests return the expected error
|
||||
|
||||
#[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
|
||||
setup_server(&rsclient).await;
|
||||
create_user(&rsclient, USER_A_NAME).await;
|
||||
|
@ -46,7 +46,7 @@ async fn test_not_authenticated(rsclient: KanidmClient) {
|
|||
}
|
||||
|
||||
#[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;
|
||||
create_user(&rsclient, USER_A_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!
|
||||
// Each tests is named like `test_{api input}_response_{expected api output}_or_{expected api output}`
|
||||
#[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;
|
||||
create_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: KanidmCli
|
|||
// this function tests both possible POSITIVE outcomes if we start from
|
||||
// `Start`, that is WaitForCode or ProvideCode
|
||||
#[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;
|
||||
let user_a_uuid = create_user(&rsclient, USER_A_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: KanidmClien
|
|||
}
|
||||
|
||||
#[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;
|
||||
let user_a_uuid = create_user(&rsclient, USER_A_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: Kanid
|
|||
|
||||
// here we actually test the full idm flow by duplicating the server
|
||||
#[kanidmd_testkit::test]
|
||||
async fn test_full_identification_flow(rsclient: KanidmClient) {
|
||||
async fn test_full_identification_flow(rsclient: &KanidmClient) {
|
||||
setup_server(&rsclient).await;
|
||||
let user_a_uuid = create_user(&rsclient, USER_A_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,
|
||||
USER_A_NAME,
|
||||
valid_user_b_client,
|
||||
&valid_user_b_client,
|
||||
USER_B_NAME,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
valid_user_b_client,
|
||||
&valid_user_b_client,
|
||||
USER_B_NAME,
|
||||
valid_user_a_client,
|
||||
USER_A_NAME,
|
||||
|
|
|
@ -66,7 +66,7 @@ async fn get_webdriver_client() -> fantoccini::Client {
|
|||
|
||||
#[kanidmd_testkit::test]
|
||||
#[cfg(feature = "webdriver")]
|
||||
async fn test_webdriver_user_login(rsclient: kanidm_client::KanidmClient) {
|
||||
async fn test_webdriver_user_login(rsclient: &KanidmClient) {
|
||||
if !cfg!(feature = "webdriver") {
|
||||
println!("Skipping test as webdriver feature is not enabled!");
|
||||
return;
|
||||
|
@ -206,7 +206,7 @@ async fn test_webdriver_user_login(rsclient: kanidm_client::KanidmClient) {
|
|||
}
|
||||
|
||||
#[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;
|
||||
|
||||
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]
|
||||
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;
|
||||
assert!(rsclient
|
||||
.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]
|
||||
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;
|
||||
assert!(rsclient
|
||||
.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]
|
||||
/// 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;
|
||||
let res = rsclient
|
||||
.idm_group_get("idm_all_persons")
|
||||
|
|
16
server/testkit/tests/testkit/ldap_basic.rs
Normal file
16
server/testkit/tests/testkit/ldap_basic.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
use kanidmd_testkit::AsyncTestEnvironment;
|
||||
use ldap3_client::LdapClientBuilder;
|
||||
|
||||
#[kanidmd_testkit::test(ldap = true)]
|
||||
async fn test_ldap_basic_unix_bind(test_env: &AsyncTestEnvironment) {
|
||||
let ldap_url = test_env.ldap_url.as_ref().unwrap();
|
||||
|
||||
let mut ldap_client = LdapClientBuilder::new(ldap_url).build().await.unwrap();
|
||||
|
||||
// Bind as anonymous
|
||||
ldap_client.bind("".into(), "".into()).await.unwrap();
|
||||
|
||||
let whoami = ldap_client.whoami().await.unwrap();
|
||||
|
||||
assert_eq!(whoami, Some("u: anonymous@localhost".to_string()));
|
||||
}
|
|
@ -6,6 +6,7 @@ mod https_extractors;
|
|||
mod https_middleware;
|
||||
mod identity_verification_tests;
|
||||
mod integration;
|
||||
mod ldap_basic;
|
||||
mod mtls_test;
|
||||
mod oauth2_test;
|
||||
mod person;
|
||||
|
|
|
@ -40,7 +40,7 @@ use kanidmd_testkit::{
|
|||
/// If `true`, use the `code` passed in the callback URI's fragment, and
|
||||
/// require the query parameter to be empty.
|
||||
async fn test_oauth2_openid_basic_flow_impl(
|
||||
rsclient: KanidmClient,
|
||||
rsclient: &KanidmClient,
|
||||
response_mode: Option<&str>,
|
||||
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.
|
||||
#[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;
|
||||
}
|
||||
|
||||
|
@ -544,7 +544,7 @@ async fn test_oauth2_openid_basic_flow_mode_unset(rsclient: KanidmClient) {
|
|||
///
|
||||
/// The response should be returned as a query parameter.
|
||||
#[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;
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
#[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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
/// require the query parameter to be empty.
|
||||
async fn test_oauth2_openid_public_flow_impl(
|
||||
rsclient: KanidmClient,
|
||||
rsclient: &KanidmClient,
|
||||
response_mode: Option<&str>,
|
||||
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.
|
||||
#[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;
|
||||
}
|
||||
|
||||
|
@ -910,7 +910,7 @@ async fn test_oauth2_openid_public_flow_mode_unset(rsclient: KanidmClient) {
|
|||
///
|
||||
/// The response should be returned as a query parameter.
|
||||
#[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;
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
#[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;
|
||||
}
|
||||
|
||||
#[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
|
||||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
|
@ -960,7 +960,7 @@ async fn test_oauth2_token_post_bad_bodies(rsclient: KanidmClient) {
|
|||
}
|
||||
|
||||
#[kanidmd_testkit::test]
|
||||
async fn test_oauth2_token_revoke_post(rsclient: KanidmClient) {
|
||||
async fn test_oauth2_token_revoke_post(rsclient: &KanidmClient) {
|
||||
let res = rsclient
|
||||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
|
|
|
@ -4,7 +4,7 @@ use kanidmd_testkit::{create_user, ADMIN_TEST_PASSWORD, ADMIN_TEST_USER};
|
|||
use serde_json::Value;
|
||||
|
||||
#[kanidmd_testkit::test]
|
||||
async fn test_v1_person_id_patch(rsclient: KanidmClient) {
|
||||
async fn test_v1_person_id_patch(rsclient: &KanidmClient) {
|
||||
let res = rsclient
|
||||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
|
@ -25,7 +25,7 @@ async fn test_v1_person_id_patch(rsclient: KanidmClient) {
|
|||
}
|
||||
|
||||
#[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
|
||||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
|
|
|
@ -29,7 +29,7 @@ use kanidmd_testkit::{ADMIN_TEST_PASSWORD, ADMIN_TEST_USER};
|
|||
const UNIX_TEST_PASSWORD: &str = "unix test user password";
|
||||
|
||||
#[kanidmd_testkit::test]
|
||||
async fn test_server_create(rsclient: KanidmClient) {
|
||||
async fn test_server_create(rsclient: &KanidmClient) {
|
||||
let e: Entry = serde_json::from_str(
|
||||
r#"{
|
||||
"attrs": {
|
||||
|
@ -55,7 +55,7 @@ async fn test_server_create(rsclient: KanidmClient) {
|
|||
}
|
||||
|
||||
#[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.
|
||||
let pre_res = rsclient.whoami().await;
|
||||
// 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]
|
||||
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.
|
||||
let pre_res = rsclient.whoami().await;
|
||||
// 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]
|
||||
async fn test_server_search(rsclient: KanidmClient) {
|
||||
async fn test_server_search(rsclient: &KanidmClient) {
|
||||
// First show we are un-authenticated.
|
||||
let pre_res = rsclient.whoami().await;
|
||||
// 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.
|
||||
#[kanidmd_testkit::test]
|
||||
async fn test_server_rest_group_read(rsclient: KanidmClient) {
|
||||
async fn test_server_rest_group_read(rsclient: &KanidmClient) {
|
||||
let res = rsclient
|
||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
|
@ -151,7 +151,7 @@ async fn test_server_rest_group_read(rsclient: KanidmClient) {
|
|||
}
|
||||
|
||||
#[kanidmd_testkit::test]
|
||||
async fn test_server_rest_group_lifecycle(rsclient: KanidmClient) {
|
||||
async fn test_server_rest_group_lifecycle(rsclient: &KanidmClient) {
|
||||
let res = rsclient
|
||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
|
@ -263,7 +263,7 @@ async fn test_server_rest_group_lifecycle(rsclient: KanidmClient) {
|
|||
}
|
||||
|
||||
#[kanidmd_testkit::test]
|
||||
async fn test_server_rest_account_read(rsclient: KanidmClient) {
|
||||
async fn test_server_rest_account_read(rsclient: &KanidmClient) {
|
||||
let res = rsclient
|
||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
|
@ -279,7 +279,7 @@ async fn test_server_rest_account_read(rsclient: KanidmClient) {
|
|||
}
|
||||
|
||||
#[kanidmd_testkit::test]
|
||||
async fn test_server_rest_schema_read(rsclient: KanidmClient) {
|
||||
async fn test_server_rest_schema_read(rsclient: &KanidmClient) {
|
||||
let res = rsclient
|
||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
|
@ -313,7 +313,7 @@ async fn test_server_rest_schema_read(rsclient: KanidmClient) {
|
|||
|
||||
// Test resetting a radius cred, and then checking/viewing it.
|
||||
#[kanidmd_testkit::test]
|
||||
async fn test_server_radius_credential_lifecycle(rsclient: KanidmClient) {
|
||||
async fn test_server_radius_credential_lifecycle(rsclient: &KanidmClient) {
|
||||
let res = rsclient
|
||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
|
@ -384,7 +384,7 @@ async fn test_server_radius_credential_lifecycle(rsclient: KanidmClient) {
|
|||
}
|
||||
|
||||
#[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
|
||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
|
@ -439,7 +439,7 @@ async fn test_server_rest_person_account_lifecycle(rsclient: KanidmClient) {
|
|||
}
|
||||
|
||||
#[kanidmd_testkit::test]
|
||||
async fn test_server_rest_sshkey_lifecycle(rsclient: KanidmClient) {
|
||||
async fn test_server_rest_sshkey_lifecycle(rsclient: &KanidmClient) {
|
||||
let res = rsclient
|
||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
|
@ -509,7 +509,7 @@ async fn test_server_rest_sshkey_lifecycle(rsclient: KanidmClient) {
|
|||
}
|
||||
|
||||
#[kanidmd_testkit::test]
|
||||
async fn test_server_rest_domain_lifecycle(rsclient: KanidmClient) {
|
||||
async fn test_server_rest_domain_lifecycle(rsclient: &KanidmClient) {
|
||||
let res = rsclient
|
||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
|
@ -539,7 +539,7 @@ async fn test_server_rest_domain_lifecycle(rsclient: KanidmClient) {
|
|||
}
|
||||
|
||||
#[kanidmd_testkit::test]
|
||||
async fn test_server_rest_posix_lifecycle(rsclient: KanidmClient) {
|
||||
async fn test_server_rest_posix_lifecycle(rsclient: &KanidmClient) {
|
||||
let res = rsclient
|
||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
|
@ -660,7 +660,7 @@ async fn test_server_rest_posix_lifecycle(rsclient: KanidmClient) {
|
|||
}
|
||||
|
||||
#[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
|
||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
|
@ -760,7 +760,7 @@ async fn test_server_rest_posix_auth_lifecycle(rsclient: KanidmClient) {
|
|||
}
|
||||
|
||||
#[kanidmd_testkit::test]
|
||||
async fn test_server_rest_recycle_lifecycle(rsclient: KanidmClient) {
|
||||
async fn test_server_rest_recycle_lifecycle(rsclient: &KanidmClient) {
|
||||
let res = rsclient
|
||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
|
@ -814,7 +814,7 @@ async fn test_server_rest_recycle_lifecycle(rsclient: KanidmClient) {
|
|||
}
|
||||
|
||||
#[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
|
||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
|
@ -1027,7 +1027,7 @@ async fn test_server_rest_oauth2_basic_lifecycle(rsclient: KanidmClient) {
|
|||
}
|
||||
|
||||
#[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
|
||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
|
@ -1102,7 +1102,7 @@ async fn test_server_credential_update_session_pw(rsclient: KanidmClient) {
|
|||
}
|
||||
|
||||
#[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
|
||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
|
@ -1365,7 +1365,7 @@ async fn setup_demo_account_password(
|
|||
}
|
||||
|
||||
#[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 res = rsclient
|
||||
|
@ -1383,7 +1383,7 @@ async fn test_server_credential_update_session_passkey(rsclient: KanidmClient) {
|
|||
}
|
||||
|
||||
#[kanidmd_testkit::test]
|
||||
async fn test_server_api_token_lifecycle(rsclient: KanidmClient) {
|
||||
async fn test_server_api_token_lifecycle(rsclient: &KanidmClient) {
|
||||
let res = rsclient
|
||||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
|
@ -1566,7 +1566,7 @@ async fn test_server_api_token_lifecycle(rsclient: KanidmClient) {
|
|||
}
|
||||
|
||||
#[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
|
||||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
|
@ -1689,7 +1689,7 @@ async fn test_server_user_auth_token_lifecycle(rsclient: KanidmClient) {
|
|||
}
|
||||
|
||||
#[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 res = rsclient
|
||||
|
@ -1868,7 +1868,7 @@ async fn start_password_session(
|
|||
}
|
||||
|
||||
#[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)
|
||||
.await
|
||||
.expect("Failed to setup demo_account");
|
||||
|
@ -1891,7 +1891,7 @@ async fn test_server_user_auth_unprivileged(rsclient: KanidmClient) {
|
|||
}
|
||||
|
||||
#[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)
|
||||
.await
|
||||
.expect("Failed to setup demo_account");
|
||||
|
|
|
@ -9,7 +9,7 @@ use std::str::FromStr;
|
|||
use url::Url;
|
||||
|
||||
#[kanidmd_testkit::test]
|
||||
async fn test_sync_account_lifecycle(rsclient: KanidmClient) {
|
||||
async fn test_sync_account_lifecycle(rsclient: &KanidmClient) {
|
||||
let a_res = rsclient
|
||||
.auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
|
@ -104,7 +104,7 @@ async fn test_sync_account_lifecycle(rsclient: KanidmClient) {
|
|||
}
|
||||
|
||||
#[kanidmd_testkit::test]
|
||||
async fn test_scim_sync_entry_get(rsclient: KanidmClient) {
|
||||
async fn test_scim_sync_entry_get(rsclient: &KanidmClient) {
|
||||
let res = rsclient
|
||||
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||
.await;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue