mirror of
https://github.com/kanidm/kanidm.git
synced 2025-05-14 13:03:55 +02:00
Compare commits
3 commits
bb0e759134
...
26cbb2d8c1
Author | SHA1 | Date | |
---|---|---|---|
|
26cbb2d8c1 | ||
|
e2c6190693 | ||
|
a28c4e8cd4 |
.github/workflows
Cargo.lockCargo.tomlbook/src
examples
libs
platform
proto/src
pykanidm
scripts
server
core/src
daemon
lib-macros/src
lib/src
testkit-macros/src
testkit
tools
unix_integration
8
.github/workflows/clippy.yml
vendored
8
.github/workflows/clippy.yml
vendored
|
@ -19,7 +19,9 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
uses: mozilla-actions/sccache-action@v0.0.7
|
||||
with:
|
||||
version: "v0.4.2"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update && \
|
||||
|
@ -39,6 +41,8 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
uses: mozilla-actions/sccache-action@v0.0.7
|
||||
with:
|
||||
version: "v0.4.2"
|
||||
- name: "Run cargo fmt"
|
||||
run: cargo fmt --check
|
||||
|
|
4
.github/workflows/kanidm_individual_book.yml
vendored
4
.github/workflows/kanidm_individual_book.yml
vendored
|
@ -24,7 +24,9 @@ jobs:
|
|||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
uses: mozilla-actions/sccache-action@v0.0.7
|
||||
with:
|
||||
version: "v0.4.2"
|
||||
- name: Install deps
|
||||
run: |
|
||||
sudo apt-get update
|
||||
|
|
15
.github/workflows/rust_build.yml
vendored
15
.github/workflows/rust_build.yml
vendored
|
@ -27,7 +27,10 @@ jobs:
|
|||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
uses: mozilla-actions/sccache-action@v0.0.7
|
||||
with:
|
||||
version: "v0.4.2"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update && \
|
||||
|
@ -72,7 +75,10 @@ jobs:
|
|||
with:
|
||||
toolchain: ${{ matrix.rust_version }}
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
uses: mozilla-actions/sccache-action@v0.0.7
|
||||
with:
|
||||
version: "v0.4.2"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update && \
|
||||
|
@ -112,7 +118,10 @@ jobs:
|
|||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
uses: mozilla-actions/sccache-action@v0.0.7
|
||||
with:
|
||||
version: "v0.4.2"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update && \
|
||||
|
|
4
.github/workflows/windows_build.yml
vendored
4
.github/workflows/windows_build.yml
vendored
|
@ -28,7 +28,9 @@ jobs:
|
|||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
uses: mozilla-actions/sccache-action@v0.0.7
|
||||
with:
|
||||
version: "v0.4.2"
|
||||
- run: cargo build --locked -p kanidm_client -p kanidm_tools --bin kanidm
|
||||
# yamllint disable-line rule:line-length
|
||||
- run: cargo test -p kanidm_client -p kanidm_tools
|
||||
|
|
903
Cargo.lock
generated
903
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
12
Cargo.toml
12
Cargo.toml
|
@ -159,12 +159,12 @@ base64 = "^0.22.1"
|
|||
base64urlsafedata = "0.5.1"
|
||||
bitflags = "^2.8.0"
|
||||
bytes = "^1.9.0"
|
||||
clap = { version = "^4.5.34", features = ["derive", "env"] }
|
||||
clap = { version = "^4.5.27", features = ["derive", "env"] }
|
||||
clap_complete = "^4.5.42"
|
||||
# Forced by saffron/cron
|
||||
chrono = "^0.4.39"
|
||||
compact_jwt = { version = "^0.4.2", default-features = false }
|
||||
concread = "^0.5.5"
|
||||
concread = "^0.5.3"
|
||||
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.14.0"
|
||||
itertools = "0.13.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.13.0"
|
||||
lru = "^0.12.5"
|
||||
mathru = "^0.13.0"
|
||||
md-5 = "0.10.6"
|
||||
mimalloc = "0.1.43"
|
||||
notify-debouncer-full = { version = "0.5" }
|
||||
notify-debouncer-full = { version = "0.1" }
|
||||
num_enum = "^0.5.11"
|
||||
oauth2_ext = { version = "^4.4.2", package = "oauth2", default-features = false }
|
||||
openssl-sys = "^0.9"
|
||||
|
@ -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.6.0"
|
||||
whoami = "^1.5.2"
|
||||
walkdir = "2"
|
||||
|
||||
x509-cert = "0.2.5"
|
||||
|
|
|
@ -53,6 +53,7 @@
|
|||
|
||||
- [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,21 +58,6 @@ 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%
|
||||
|
@ -82,7 +67,7 @@ We advise that if you have a site with greater than approximately 2,000 users yo
|
|||
external system to allocate GID numbers serially or consistently to avoid potential duplication
|
||||
events.
|
||||
|
||||
We recommend the use of the range `65536` through `524287` (`unused C`) for manual allocation. This leaves the
|
||||
We recommend the use of the range `65536` through `524287` for manual allocation. This leaves the
|
||||
range `1000` through `65535` for OS/Distro purposes, and allows Kanidm to continue dynamic
|
||||
allocation in the range `1879048192` to `2147483647` if you choose a hybrid allocation approach.
|
||||
|
||||
|
|
|
@ -45,7 +45,6 @@ can take many forms such as.
|
|||
- firstname firstname lastname
|
||||
- firstname 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,6 +54,7 @@ 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 oauth2 add-redirect-url webapp 'https://webapp.example.com/oauth2/callback'
|
||||
kanidm system add-redirect-url webapp 'https://webapp.example.com/oauth2/callback'
|
||||
kanidm system oauth2 update-scope-map webapp email openid
|
||||
kanidm system oauth2 get webapp
|
||||
kanidm system oauth2 show-basic-secret webapp
|
||||
|
|
|
@ -5,45 +5,57 @@
|
|||
- 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 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.
|
||||
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.
|
||||
- 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 VERBOSE=true -e CI=true \
|
||||
docker run --rm -it -e CI=true \
|
||||
--mount "type=bind,src=$PWD,target=/src" \
|
||||
--workdir /src \
|
||||
rust:bookworm
|
||||
```
|
||||
1. In the container install dependencies with:
|
||||
```shell
|
||||
platform/debian/kanidm_ppa_automation/scripts/install_ci_build_dependencies.sh
|
||||
# The parameter given is which additional target debian architecture to enable (amd64, arm64, etc.)
|
||||
# If your native platform is amd64, running with arm64 is enough to cover both archs.
|
||||
platform/debian/kanidm_ppa_automation/scripts/install_ci_build_dependencies.sh arm64
|
||||
```
|
||||
1. Launch your desired target build:
|
||||
```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:
|
||||
1. In the container 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 as follows, see further down for details.
|
||||
The rough overview of steps is:
|
||||
1. Add cargo-deb specific metadata to the rust package and any static assets. Submit your changes as
|
||||
a PR.
|
||||
2. Add build steps to the separate packaging repo. Submit your changes as a PR.
|
||||
2. Add build instructions to the separate packaging repo. Submit your changes as a PR.
|
||||
3. Go back to the main repo to update the packaging submodule reference to aid running manual dev
|
||||
builds of the new package.
|
||||
|
||||
|
@ -60,8 +72,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/build_native.sh` build rules to include new binaries or packages with shared
|
||||
libraries.
|
||||
- Amend `scripts/crossbuild.sh` build rules to include new binaries or packages with shared
|
||||
libraries. Search for the lines starting with `cross build`.
|
||||
- Add any new build time system dependencies to `scripts/install_ci_build_dependencies.sh`, be aware
|
||||
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,4 +1,3 @@
|
|||
version = "2"
|
||||
bindaddress = "[::]:8443"
|
||||
ldapbindaddress = "127.0.0.1:3636"
|
||||
|
|
@ -1,6 +1,3 @@
|
|||
# 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,6 +1,3 @@
|
|||
# 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,20 +195,4 @@ 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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
# The main difference from the release_linux profile is using
|
||||
# per-package shared directories for a clearer separation and
|
||||
# thus more consistent install & sysadmin experience.
|
||||
|
||||
# Don't set the value for autodetect
|
||||
# cpu_flags = "none"
|
||||
server_admin_bind_path = "/var/run/kanidmd/sock"
|
||||
server_ui_pkg_path = "/usr/share/kanidmd/static"
|
||||
server_config_path = "/etc/kanidmd/server.toml"
|
||||
client_config_path = "/etc/kanidm/config"
|
||||
# TODO: unixd should migrate to it's own config dir as part of the sparkled migration.
|
||||
# No point in doing two back to back migrations.
|
||||
resolver_config_path = "/etc/kanidm/unixd"
|
||||
resolver_unix_shell_path = "/bin/bash"
|
|
@ -1 +1 @@
|
|||
Subproject commit 8d7579fb543632df74e609892c69ce9f368fdd02
|
||||
Subproject commit 942c7b69ca807cc38186b63ab02a391bac9eac7e
|
|
@ -12,7 +12,7 @@ Conflicts=nscd.service
|
|||
|
||||
[Service]
|
||||
DynamicUser=yes
|
||||
SupplementaryGroups=tss
|
||||
SupplementaryGroups=tss shadow
|
||||
UMask=0027
|
||||
CacheDirectory=kanidm-unixd
|
||||
RuntimeDirectory=kanidm-unixd
|
||||
|
|
|
@ -22,8 +22,6 @@ pub enum Attribute {
|
|||
AcpCreateClass,
|
||||
AcpEnable,
|
||||
AcpModifyClass,
|
||||
AcpModifyPresentClass,
|
||||
AcpModifyRemoveClass,
|
||||
AcpModifyPresentAttr,
|
||||
AcpModifyRemovedAttr,
|
||||
AcpReceiver,
|
||||
|
@ -82,7 +80,6 @@ pub enum Attribute {
|
|||
IdVerificationEcKey,
|
||||
Image,
|
||||
Index,
|
||||
Indexed,
|
||||
IpaNtHash,
|
||||
IpaSshPubKey,
|
||||
JwsEs256PrivateKey,
|
||||
|
@ -257,8 +254,6 @@ 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,
|
||||
|
@ -316,7 +311,6 @@ 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,
|
||||
|
@ -444,8 +438,6 @@ 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,
|
||||
|
@ -503,7 +495,6 @@ 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,8 +62,6 @@ pub const ATTR_ACP_CREATE_ATTR: &str = "acp_create_attr";
|
|||
pub const ATTR_ACP_CREATE_CLASS: &str = "acp_create_class";
|
||||
pub const ATTR_ACP_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";
|
||||
|
@ -126,7 +124,6 @@ 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
|
||||
Deny,
|
||||
Denied,
|
||||
/// 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::Deny => false,
|
||||
Self::Denied => 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.11.3"
|
||||
ruff = ">=0.5.1,<0.9.10"
|
||||
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.30"
|
||||
mkdocstrings = ">=0.27,<0.29"
|
||||
mkdocstrings-python = "^1.13.0"
|
||||
pook = "^2.1.3"
|
||||
|
||||
|
|
|
@ -21,8 +21,7 @@ sudo apt-get install -y \
|
|||
libsystemd-dev \
|
||||
libudev-dev \
|
||||
pkg-config \
|
||||
ripgrep \
|
||||
lld
|
||||
ripgrep
|
||||
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
|
||||
|
@ -37,7 +36,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",
|
||||
"./insecure_server.toml",
|
||||
"../../examples/insecure_server.toml",
|
||||
"--output",
|
||||
"json",
|
||||
]
|
||||
|
|
|
@ -44,7 +44,7 @@ fi
|
|||
|
||||
|
||||
# defaults
|
||||
KANIDM_CONFIG_FILE="./insecure_server.toml"
|
||||
KANIDM_CONFIG_FILE="../../examples/insecure_server.toml"
|
||||
KANIDM_URL="$(rg origin "${KANIDM_CONFIG_FILE}" | awk '{print $NF}' | tr -d '"')"
|
||||
KANIDM_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="./insecure_server.toml"
|
||||
export KANIDM_CONFIG="../../examples/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="./insecure_server.toml"
|
||||
export KANIDM_CONFIG="../../examples/insecure_server.toml"
|
||||
|
||||
mkdir -p /tmp/kanidm/client_ca
|
||||
|
||||
|
@ -48,7 +48,7 @@ fi
|
|||
|
||||
ATTEMPT=0
|
||||
|
||||
KANIDM_CONFIG_FILE="./insecure_server.toml"
|
||||
KANIDM_CONFIG_FILE="../../examples/insecure_server.toml"
|
||||
KANIDM_URL="$(rg origin "${KANIDM_CONFIG_FILE}" | awk '{print $NF}' | tr -d '"')"
|
||||
KANIDM_CA_PATH="/tmp/kanidm/ca.pem"
|
||||
|
||||
|
|
|
@ -191,7 +191,7 @@ impl QueryServerReadV1 {
|
|||
pub async fn handle_online_backup(
|
||||
&self,
|
||||
msg: OnlineBackupEvent,
|
||||
outpath: &Path,
|
||||
outpath: &str,
|
||||
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 = outpath.join(format!("backup-{}.json", timestamp));
|
||||
let dest_file = format!("{}/backup-{}.json", outpath, timestamp);
|
||||
|
||||
if dest_file.exists() {
|
||||
if Path::new(&dest_file).exists() {
|
||||
error!(
|
||||
"Online backup file {} already exists, will not overwrite it.",
|
||||
dest_file.display()
|
||||
dest_file
|
||||
);
|
||||
return Err(OperationError::InvalidState);
|
||||
}
|
||||
|
@ -218,14 +218,10 @@ impl QueryServerReadV1 {
|
|||
.get_be_txn()
|
||||
.backup(&dest_file)
|
||||
.map(|()| {
|
||||
info!("Online backup created {} successfully", dest_file.display());
|
||||
info!("Online backup created {} successfully", dest_file);
|
||||
})
|
||||
.map_err(|e| {
|
||||
error!(
|
||||
"Online backup failed to create {}: {:?}",
|
||||
dest_file.display(),
|
||||
e
|
||||
);
|
||||
error!("Online backup failed to create {}: {:?}", dest_file, e);
|
||||
OperationError::InvalidState
|
||||
})?;
|
||||
}
|
||||
|
@ -271,11 +267,7 @@ impl QueryServerReadV1 {
|
|||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Online backup cleanup error read dir {}: {}",
|
||||
outpath.display(),
|
||||
e
|
||||
);
|
||||
error!("Online backup cleanup error read dir {}: {}", outpath, e);
|
||||
return Err(OperationError::InvalidState);
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -112,19 +112,19 @@ impl IntervalActor {
|
|||
if !op.exists() {
|
||||
info!(
|
||||
"Online backup output folder '{}' does not exist, trying to create it.",
|
||||
outpath.display()
|
||||
outpath
|
||||
);
|
||||
fs::create_dir_all(&outpath).map_err(|e| {
|
||||
error!(
|
||||
"Online backup failed to create output directory '{}': {}",
|
||||
outpath.display(),
|
||||
outpath.clone(),
|
||||
e
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
if !op.is_dir() {
|
||||
error!("Online backup output '{}' is not a directory or we are missing permissions to access it.", outpath.display());
|
||||
error!("Online backup output '{}' is not a directory or we are missing permissions to access it.", outpath);
|
||||
return Err(());
|
||||
}
|
||||
|
||||
|
@ -148,7 +148,7 @@ impl IntervalActor {
|
|||
if let Err(e) = server
|
||||
.handle_online_backup(
|
||||
OnlineBackupEvent::new(),
|
||||
&outpath,
|
||||
outpath.clone().as_str(),
|
||||
versions,
|
||||
)
|
||||
.await
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
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};
|
||||
|
@ -7,16 +10,14 @@ 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::sync::broadcast;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_openssl::SslStream;
|
||||
use tokio_util::codec::{FramedRead, FramedWrite};
|
||||
|
||||
use crate::CoreAction;
|
||||
use tokio::sync::broadcast;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
struct LdapSession {
|
||||
uat: Option<LdapBoundToken>,
|
||||
}
|
||||
|
@ -48,14 +49,28 @@ async fn client_process_msg(
|
|||
.await
|
||||
}
|
||||
|
||||
async fn client_process<STREAM>(
|
||||
stream: STREAM,
|
||||
async fn client_process(
|
||||
tcpstream: TcpStream,
|
||||
tls_acceptor: SslAcceptor,
|
||||
client_address: net::SocketAddr,
|
||||
qe_r_ref: &'static QueryServerReadV1,
|
||||
) where
|
||||
STREAM: AsyncRead + AsyncWrite,
|
||||
{
|
||||
let (r, w) = tokio::io::split(stream);
|
||||
) {
|
||||
// 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(e) => {
|
||||
error!("LDAP TLS setup error, continuing -> {:?}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
if let Err(e) = SslStream::accept(Pin::new(&mut tlsstream)).await {
|
||||
error!("LDAP TLS accept error, continuing -> {:?}", e);
|
||||
return;
|
||||
};
|
||||
let (r, w) = tokio::io::split(tlsstream);
|
||||
let mut r = FramedRead::new(r, LdapCodec::default());
|
||||
let mut w = FramedWrite::new(w, LdapCodec::default());
|
||||
|
||||
|
@ -111,32 +126,7 @@ async fn client_process<STREAM>(
|
|||
}
|
||||
}
|
||||
|
||||
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]
|
||||
/// TLS LDAP Listener, hands off to [client_process]
|
||||
async fn ldap_tls_acceptor(
|
||||
listener: TcpListener,
|
||||
mut tls_acceptor: SslAcceptor,
|
||||
|
@ -155,10 +145,10 @@ async fn ldap_tls_acceptor(
|
|||
match accept_result {
|
||||
Ok((tcpstream, client_socket_addr)) => {
|
||||
let clone_tls_acceptor = tls_acceptor.clone();
|
||||
tokio::spawn(client_tls_accept(tcpstream, clone_tls_acceptor, client_socket_addr, qe_r_ref));
|
||||
tokio::spawn(client_process(tcpstream, clone_tls_acceptor, client_socket_addr, qe_r_ref));
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(?err, "LDAP acceptor error, continuing");
|
||||
Err(e) => {
|
||||
error!("LDAP acceptor error, continuing -> {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -171,34 +161,6 @@ 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>,
|
||||
|
@ -235,7 +197,10 @@ pub(crate) async fn create_ldap_server(
|
|||
tls_acceptor_reload_rx,
|
||||
))
|
||||
}
|
||||
None => tokio::spawn(ldap_plaintext_acceptor(listener, qe_r_ref, rx)),
|
||||
None => {
|
||||
error!("The server won't run without TLS!");
|
||||
return Err(());
|
||||
}
|
||||
};
|
||||
|
||||
info!("Created LDAP interface");
|
||||
|
|
|
@ -36,10 +36,9 @@ mod ldaps;
|
|||
mod repl;
|
||||
mod utils;
|
||||
|
||||
use crate::actors::{QueryServerReadV1, QueryServerWriteV1};
|
||||
use crate::admin::AdminActor;
|
||||
use crate::config::{Configuration, ServerRole};
|
||||
use crate::interval::IntervalActor;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::utils::touch_file_or_quit;
|
||||
use compact_jwt::{JwsHs256Signer, JwsSigner};
|
||||
use kanidm_proto::internal::OperationError;
|
||||
|
@ -51,14 +50,17 @@ 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> {
|
||||
|
@ -78,7 +80,7 @@ fn setup_backend_vacuum(
|
|||
let pool_size: u32 = config.threads as u32;
|
||||
|
||||
let cfg = BackendConfig::new(
|
||||
config.db_path.as_deref(),
|
||||
config.db_path.as_str(),
|
||||
pool_size,
|
||||
config.db_fs_type.unwrap_or_default(),
|
||||
config.db_arc_size,
|
||||
|
@ -333,7 +335,7 @@ pub fn dbscan_restore_quarantined_core(config: &Configuration, id: u64) {
|
|||
};
|
||||
}
|
||||
|
||||
pub fn backup_server_core(config: &Configuration, dst_path: &Path) {
|
||||
pub fn backup_server_core(config: &Configuration, dst_path: &str) {
|
||||
let schema = match Schema::new() {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
|
@ -369,11 +371,8 @@ pub fn backup_server_core(config: &Configuration, dst_path: &Path) {
|
|||
// Let the txn abort, even on success.
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
pub async fn restore_server_core(config: &Configuration, dst_path: &str) {
|
||||
touch_file_or_quit(config.db_path.as_str());
|
||||
|
||||
// First, we provide the in-memory schema so that core attrs are indexed correctly.
|
||||
let schema = match Schema::new() {
|
||||
|
@ -1012,7 +1011,7 @@ pub async fn create_server_core(
|
|||
let tls_accepter_reload_task_notify = tls_acceptor_reload_notify.clone();
|
||||
let tls_config = config.tls_config.clone();
|
||||
|
||||
let ldap_configured = config.ldapbindaddress.is_some();
|
||||
let ldap_configured = config.ldapaddress.is_some();
|
||||
let (ldap_tls_acceptor_reload_tx, ldap_tls_acceptor_reload_rx) = mpsc::channel(1);
|
||||
let (http_tls_acceptor_reload_tx, http_tls_acceptor_reload_rx) = mpsc::channel(1);
|
||||
|
||||
|
@ -1077,19 +1076,24 @@ pub async fn create_server_core(
|
|||
};
|
||||
|
||||
// If we have been requested to init LDAP, configure it now.
|
||||
let maybe_ldap_acceptor_handle = match &config.ldapbindaddress {
|
||||
let maybe_ldap_acceptor_handle = match &config.ldapaddress {
|
||||
Some(la) => {
|
||||
let opt_ldap_ssl_acceptor = maybe_tls_acceptor.clone();
|
||||
|
||||
let h = ldaps::create_ldap_server(
|
||||
la.as_str(),
|
||||
opt_ldap_ssl_acceptor,
|
||||
server_read_ref,
|
||||
broadcast_tx.subscribe(),
|
||||
ldap_tls_acceptor_reload_rx,
|
||||
)
|
||||
.await?;
|
||||
Some(h)
|
||||
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,
|
||||
server_read_ref,
|
||||
broadcast_tx.subscribe(),
|
||||
ldap_tls_acceptor_reload_rx,
|
||||
)
|
||||
.await?;
|
||||
Some(h)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
None => {
|
||||
debug!("LDAP not requested, skipping");
|
||||
|
|
|
@ -1,39 +1,32 @@
|
|||
use filetime::FileTime;
|
||||
use std::fs::File;
|
||||
use std::io::ErrorKind;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::time::SystemTime;
|
||||
|
||||
pub fn touch_file_or_quit<P: AsRef<Path>>(file_path: P) {
|
||||
pub fn touch_file_or_quit(file_path: &str) {
|
||||
/*
|
||||
Attempt to touch the file file_path, will quit the application if it fails for any reason.
|
||||
|
||||
Will also create a new file if it doesn't already exist.
|
||||
*/
|
||||
|
||||
let file_path: &Path = file_path.as_ref();
|
||||
|
||||
if file_path.exists() {
|
||||
if PathBuf::from(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.display()
|
||||
file_path
|
||||
),
|
||||
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.display()
|
||||
)
|
||||
error!("Permission denied writing to {}, quitting.", file_path)
|
||||
}
|
||||
_ => {
|
||||
error!(
|
||||
"Failed to write to {} due to error: {:?} ... quitting.",
|
||||
file_path.display(),
|
||||
e
|
||||
file_path, e
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -42,12 +35,11 @@ pub fn touch_file_or_quit<P: AsRef<Path>>(file_path: P) {
|
|||
}
|
||||
} else {
|
||||
match File::create(file_path) {
|
||||
Ok(_) => debug!("Successfully touched new file {}", file_path.display()),
|
||||
Ok(_) => debug!("Successfully touched new file {}", file_path),
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Failed to write to {} due to error: {:?} ... quitting.",
|
||||
file_path.display(),
|
||||
e
|
||||
file_path, e
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
|
|
@ -57,31 +57,6 @@ 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"]
|
||||
|
|
|
@ -10,15 +10,13 @@ Before=radiusd.service
|
|||
[Service]
|
||||
Type=notify
|
||||
DynamicUser=yes
|
||||
User=kanidmd_dyn
|
||||
Group=kanidmd
|
||||
StateDirectory=kanidmd
|
||||
StateDirectory=kanidm
|
||||
StateDirectoryMode=0750
|
||||
CacheDirectory=kanidmd
|
||||
CacheDirectoryMode=0750
|
||||
RuntimeDirectory=kanidmd
|
||||
RuntimeDirectoryMode=0755
|
||||
ExecStart=/usr/bin/kanidmd server
|
||||
ExecStart=/usr/sbin/kanidmd server -c /etc/kanidm/server.toml
|
||||
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
|
@ -1,2 +0,0 @@
|
|||
# This is a sysusers.d format config, please refer to man sysusers.d(5)
|
||||
g kanidmd -
|
|
@ -1,38 +0,0 @@
|
|||
#!/bin/sh
|
||||
# postinst script for kanidmd
|
||||
#
|
||||
# see: dh_installdeb(1)
|
||||
|
||||
set -e
|
||||
|
||||
|
||||
case "$1" in
|
||||
configure)
|
||||
echo "Creating the kanidmd group for config & cert ownership..."
|
||||
systemd-sysusers
|
||||
echo "Fixing ownership of server configuration ..."
|
||||
chown :kanidmd /etc/kanidmd/server.toml*
|
||||
|
||||
echo "============================="
|
||||
echo "Thanks for installing Kanidm!"
|
||||
echo "============================="
|
||||
echo "Please ensure you modify the configuration file at /etc/kanidmd/server.toml"
|
||||
echo "Only then: systemctl enable kanidmd.service"
|
||||
echo "Full examples are in /usr/share/kanidmd/"
|
||||
;;
|
||||
|
||||
abort-upgrade|abort-remove|abort-deconfigure)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "postinst called with unknown argument \`$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# dh_installdeb will replace this with shell code automatically
|
||||
# generated by other debhelper scripts.
|
||||
|
||||
#DEBHELPER#
|
||||
|
||||
exit 0
|
|
@ -1,51 +0,0 @@
|
|||
# Kanidm server minimal configuration - /etc/kanidm/server.toml
|
||||
# For a full example and documentation, see /usr/share/kanidmd/server.toml
|
||||
# or `example/server.toml` in the source repository
|
||||
|
||||
# NOTE: You must configure at least domain & origin below to allow the server to start!
|
||||
|
||||
# The webserver bind address. Requires TLS certificates.
|
||||
# If the port is set to 443 you may require the
|
||||
# NET_BIND_SERVICE capability.
|
||||
# Defaults to "127.0.0.1:8443"
|
||||
bindaddress = "127.0.0.1:8443"
|
||||
|
||||
# The path to the kanidm database.
|
||||
# The provided example uses systemd dynamic user pathing for security
|
||||
db_path = "/var/lib/private/kanidmd/kanidm.db"
|
||||
|
||||
# TLS chain and key in pem format. Both must be present.
|
||||
# If the server receives a SIGHUP, these files will be
|
||||
# re-read and reloaded if their content is valid.
|
||||
# These should be owned by root:kanidmd to give the service access.
|
||||
tls_chain = "/etc/kanidmd/chain.pem"
|
||||
tls_key = "/etc/kanidmd/key.pem"
|
||||
|
||||
log_level = "info"
|
||||
|
||||
# The DNS domain name of the server. This is used in a
|
||||
# number of security-critical contexts
|
||||
# such as webauthn, so it *must* match your DNS
|
||||
#
|
||||
# ⚠️ WARNING ⚠️
|
||||
#
|
||||
# Changing this value after first use WILL break many types of
|
||||
# registered credentials for accounts including but not limited
|
||||
# to: webauthn, oauth tokens, and more.
|
||||
# If you change this value you *must* run
|
||||
# `kanidmd domain rename` immediately after.
|
||||
# NOTE: You must set this value!
|
||||
#domain = "idm.example.com"
|
||||
#
|
||||
# The origin for webauthn. This is the url to the server,
|
||||
# with the port included if it is non-standard (any port
|
||||
# except 443). This must match or be a descendent of the
|
||||
# domain name you configure above. If these two items are
|
||||
# not consistent, the server WILL refuse to start!
|
||||
# origin = "https://idm.example.com"
|
||||
# NOTE: You must set this value!
|
||||
#origin = "https://idm.example.com:8443"
|
||||
|
||||
[online_backup]
|
||||
path = "/var/lib/private/kanidmd/backups/"
|
||||
schedule = "00 22 * * *"
|
|
@ -22,16 +22,14 @@ fi
|
|||
|
||||
mkdir -p "${KANI_TMP}"/client_ca
|
||||
|
||||
CONFIG_FILE=${CONFIG_FILE:="${SCRIPT_DIR}/insecure_server.toml"}
|
||||
CONFIG_FILE=${CONFIG_FILE:="${SCRIPT_DIR}/../../examples/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
|
||||
|
||||
# Save current directory and change to script directory without pushd
|
||||
OLD_DIR=$(pwd)
|
||||
cd "${SCRIPT_DIR}" || exit 1
|
||||
pushd "${SCRIPT_DIR}" > /dev/null 2>&1
|
||||
if [ -n "${1}" ]; then
|
||||
COMMAND=$*
|
||||
#shellcheck disable=SC2086
|
||||
|
@ -42,4 +40,4 @@ else
|
|||
#shellcheck disable=SC2086
|
||||
cargo run ${KANI_CARGO_OPTS} --bin kanidmd -- server -c "${CONFIG_FILE}"
|
||||
fi
|
||||
cd "${OLD_DIR}" || exit 1
|
||||
popd > /dev/null 2>&1
|
||||
|
|
|
@ -37,7 +37,7 @@ use kanidmd_core::admin::{
|
|||
AdminTaskRequest, AdminTaskResponse, ClientCodec, ProtoDomainInfo,
|
||||
ProtoDomainUpgradeCheckReport, ProtoDomainUpgradeCheckStatus,
|
||||
};
|
||||
use kanidmd_core::config::{CliConfig, Configuration, EnvironmentConfig, ServerConfigUntagged};
|
||||
use kanidmd_core::config::{Configuration, ServerConfig};
|
||||
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,13 +379,17 @@ fn check_file_ownership(opt: &KanidmdParser) -> Result<(), ExitCode> {
|
|||
}
|
||||
|
||||
// We have to do this because we can't use tracing until we've started the logging pipeline, and we can't start the logging pipeline until the tokio runtime's doing its thing.
|
||||
async fn start_daemon(opt: KanidmdParser, config: Configuration) -> ExitCode {
|
||||
async fn start_daemon(
|
||||
opt: KanidmdParser,
|
||||
mut config: Configuration,
|
||||
sconfig: ServerConfig,
|
||||
) -> ExitCode {
|
||||
// if we have a server config and it has an OTEL URL, then we'll start the logging pipeline now.
|
||||
|
||||
// TODO: only send to stderr when we're not in a TTY
|
||||
let sub = match sketching::otel::start_logging_pipeline(
|
||||
&config.otel_grpc_url,
|
||||
config.log_level,
|
||||
&sconfig.otel_grpc_url,
|
||||
sconfig.log_level.unwrap_or_default(),
|
||||
"kanidmd",
|
||||
) {
|
||||
Err(err) => {
|
||||
|
@ -419,8 +423,8 @@ async fn start_daemon(opt: KanidmdParser, config: Configuration) -> ExitCode {
|
|||
return err;
|
||||
};
|
||||
|
||||
if let Some(db_path) = config.db_path.as_ref() {
|
||||
let db_pathbuf = db_path.to_path_buf();
|
||||
if let Some(db_path) = sconfig.db_path.as_ref() {
|
||||
let db_pathbuf = PathBuf::from(db_path.as_str());
|
||||
// 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() {
|
||||
|
@ -460,11 +464,33 @@ async fn start_daemon(opt: KanidmdParser, config: Configuration) -> ExitCode {
|
|||
warn!("WARNING: DB folder {} has 'everyone' permission bits in the mode. This could be a security risk ...", db_par_path_buf.to_str().unwrap_or("invalid file path"));
|
||||
}
|
||||
}
|
||||
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 { .. }
|
||||
|
@ -475,15 +501,19 @@ async fn start_daemon(opt: KanidmdParser, config: Configuration) -> ExitCode {
|
|||
_ => {
|
||||
// Okay - Lets now create our lock and go.
|
||||
#[allow(clippy::expect_used)]
|
||||
let klock_path = match config.db_path.clone() {
|
||||
Some(val) => val.with_extension("klock"),
|
||||
None => std::env::temp_dir().join("kanidmd.klock"),
|
||||
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 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.display(), e);
|
||||
error!("ERROR: Refusing to start - unable to create kanidmd exclusive lock at {} - {:?}", klock_path, e);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
|
@ -491,7 +521,7 @@ async fn start_daemon(opt: KanidmdParser, config: Configuration) -> ExitCode {
|
|||
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.display(), e);
|
||||
error!("ERROR: Refusing to start - unable to lock kanidmd exclusive lock at {} - {:?}", klock_path, e);
|
||||
error!("Is another kanidmd process running?");
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
|
@ -499,7 +529,7 @@ async fn start_daemon(opt: KanidmdParser, config: Configuration) -> ExitCode {
|
|||
}
|
||||
}
|
||||
|
||||
kanidm_main(config, opt).await
|
||||
kanidm_main(sconfig, config, opt).await
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
|
@ -526,6 +556,10 @@ 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;
|
||||
|
@ -547,56 +581,49 @@ fn main() -> ExitCode {
|
|||
}
|
||||
};
|
||||
|
||||
let maybe_sconfig = if let Some(config_path) = maybe_config_path {
|
||||
match ServerConfigUntagged::new(config_path) {
|
||||
Ok(c) => Some(c),
|
||||
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);
|
||||
let sconfig = match ServerConfig::new(maybe_config_path) {
|
||||
Ok(c) => Some(c),
|
||||
Err(e) => {
|
||||
config_error.push(format!("Config Parse failure {:?}", e));
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
|
||||
let cli_config = CliConfig {
|
||||
output_mode: Some(opt.commands.commonopt().output_mode.to_owned().into()),
|
||||
};
|
||||
|
||||
let is_server = matches!(&opt.commands, KanidmdOpt::Server(_));
|
||||
|
||||
let config = Configuration::build()
|
||||
.add_env_config(envconfig)
|
||||
.add_opt_toml_config(maybe_sconfig)
|
||||
// We always set threads to 1 unless it's the main server.
|
||||
.add_cli_config(cli_config)
|
||||
.is_server_mode(is_server)
|
||||
.finish();
|
||||
|
||||
let Some(config) = config else {
|
||||
eprintln!(
|
||||
"ERROR: Unable to build server configuration from provided configuration inputs."
|
||||
);
|
||||
return ExitCode::FAILURE;
|
||||
};
|
||||
|
||||
// ===========================================================================
|
||||
// Config ready
|
||||
|
||||
// Get information on the windows username
|
||||
#[cfg(target_family = "windows")]
|
||||
get_user_details_windows();
|
||||
|
||||
if !config_error.is_empty() {
|
||||
println!("There were errors on startup, which prevent the server from starting:");
|
||||
for e in config_error {
|
||||
println!(" - {}", e);
|
||||
}
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
|
||||
let sconfig = match sconfig {
|
||||
Some(val) => val,
|
||||
None => {
|
||||
println!("Somehow you got an empty ServerConfig after error checking? Cannot start!");
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
|
||||
// ===========================================================================
|
||||
// Config ready
|
||||
|
||||
// We always set threads to 1 unless it's the main server.
|
||||
if matches!(&opt.commands, KanidmdOpt::Server(_)) {
|
||||
// If not updated, will default to maximum
|
||||
if let Some(threads) = sconfig.thread_count {
|
||||
config.update_threads_count(threads);
|
||||
}
|
||||
} else {
|
||||
config.update_threads_count(1);
|
||||
};
|
||||
|
||||
// Start the runtime
|
||||
|
||||
let maybe_rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(config.threads)
|
||||
.enable_all()
|
||||
|
@ -616,12 +643,16 @@ fn main() -> ExitCode {
|
|||
}
|
||||
};
|
||||
|
||||
rt.block_on(start_daemon(opt, config))
|
||||
rt.block_on(start_daemon(opt, config, sconfig))
|
||||
}
|
||||
|
||||
/// Build and execute the main server. The ServerConfig are the configuration options
|
||||
/// that we are processing into the config for the main server.
|
||||
async fn kanidm_main(config: Configuration, opt: KanidmdParser) -> ExitCode {
|
||||
async fn kanidm_main(
|
||||
sconfig: ServerConfig,
|
||||
mut config: Configuration,
|
||||
opt: KanidmdParser,
|
||||
) -> ExitCode {
|
||||
match &opt.commands {
|
||||
KanidmdOpt::Server(_sopt) | KanidmdOpt::ConfigTest(_sopt) => {
|
||||
let config_test = matches!(&opt.commands, KanidmdOpt::ConfigTest(_));
|
||||
|
@ -631,90 +662,88 @@ async fn kanidm_main(config: Configuration, opt: KanidmdParser) -> ExitCode {
|
|||
info!("Running in server mode ...");
|
||||
};
|
||||
|
||||
// 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 '{}' - {:?}",
|
||||
tls_config.chain.display(),
|
||||
e
|
||||
);
|
||||
let diag =
|
||||
kanidm_lib_file_permissions::diagnose_path(&tls_config.chain);
|
||||
info!(%diag);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
if !kanidm_lib_file_permissions::readonly(&i_meta) {
|
||||
warn!("permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ...", tls_config.chain.display());
|
||||
}
|
||||
}
|
||||
// configuration options that only relate to server mode
|
||||
config.update_config_for_server_mode(&sconfig);
|
||||
|
||||
{
|
||||
let i_meta = match metadata(&tls_config.key) {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Unable to read metadata for TLS key file '{}' - {:?}",
|
||||
tls_config.key.display(),
|
||||
e
|
||||
);
|
||||
let diag = kanidm_lib_file_permissions::diagnose_path(&tls_config.key);
|
||||
info!(%diag);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
if !kanidm_lib_file_permissions::readonly(&i_meta) {
|
||||
warn!("permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ...", tls_config.key.display());
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
if i_meta.mode() & 0o007 != 0 {
|
||||
warn!("WARNING: {} has 'everyone' permission bits in the mode. This could be a security risk ...", tls_config.key.display());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ca_dir) = tls_config.client_ca.as_ref() {
|
||||
// check that the TLS client CA config option is what we expect
|
||||
let ca_dir_path = PathBuf::from(&ca_dir);
|
||||
if !ca_dir_path.exists() {
|
||||
if let Some(i_str) = &(sconfig.tls_chain) {
|
||||
let i_path = PathBuf::from(i_str.as_str());
|
||||
let i_meta = match metadata(&i_path) {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"TLS CA folder {} does not exist, server startup will FAIL!",
|
||||
ca_dir.display()
|
||||
"Unable to read metadata for TLS chain file '{}' - {:?}",
|
||||
&i_path.to_str().unwrap_or("invalid file path"),
|
||||
e
|
||||
);
|
||||
let diag = kanidm_lib_file_permissions::diagnose_path(&ca_dir_path);
|
||||
let diag = kanidm_lib_file_permissions::diagnose_path(&i_path);
|
||||
info!(%diag);
|
||||
}
|
||||
|
||||
let i_meta = match metadata(&ca_dir_path) {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Unable to read metadata for '{}' - {:?}",
|
||||
ca_dir.display(),
|
||||
e
|
||||
);
|
||||
let diag = kanidm_lib_file_permissions::diagnose_path(&ca_dir_path);
|
||||
info!(%diag);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
if !i_meta.is_dir() {
|
||||
error!(
|
||||
"ERROR: Refusing to run - TLS Client CA folder {} may not be a directory",
|
||||
ca_dir.display()
|
||||
);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
if kanidm_lib_file_permissions::readonly(&i_meta) {
|
||||
warn!("WARNING: TLS Client CA folder permissions on {} indicate it may not be RW. This could cause the server start up to fail!", ca_dir.display());
|
||||
};
|
||||
if !kanidm_lib_file_permissions::readonly(&i_meta) {
|
||||
warn!("permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ...", i_str);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(i_str) = &(sconfig.tls_key) {
|
||||
let i_path = PathBuf::from(i_str.as_str());
|
||||
|
||||
let i_meta = match metadata(&i_path) {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Unable to read metadata for TLS key file '{}' - {:?}",
|
||||
&i_path.to_str().unwrap_or("invalid file path"),
|
||||
e
|
||||
);
|
||||
let diag = kanidm_lib_file_permissions::diagnose_path(&i_path);
|
||||
info!(%diag);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
if i_meta.mode() & 0o007 != 0 {
|
||||
warn!("WARNING: TLS Client CA folder {} has 'everyone' permission bits in the mode. This could be a security risk ...", ca_dir.display());
|
||||
};
|
||||
if !kanidm_lib_file_permissions::readonly(&i_meta) {
|
||||
warn!("permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ...", i_str);
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
if i_meta.mode() & 0o007 != 0 {
|
||||
warn!("WARNING: {} has 'everyone' permission bits in the mode. This could be a security risk ...", i_str);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ca_dir) = &(sconfig.tls_client_ca) {
|
||||
// check that the TLS client CA config option is what we expect
|
||||
let ca_dir_path = PathBuf::from(&ca_dir);
|
||||
if !ca_dir_path.exists() {
|
||||
error!(
|
||||
"TLS CA folder {} does not exist, server startup will FAIL!",
|
||||
ca_dir
|
||||
);
|
||||
let diag = kanidm_lib_file_permissions::diagnose_path(&ca_dir_path);
|
||||
info!(%diag);
|
||||
}
|
||||
|
||||
let i_meta = match metadata(&ca_dir_path) {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
error!("Unable to read metadata for '{}' - {:?}", ca_dir, e);
|
||||
let diag = kanidm_lib_file_permissions::diagnose_path(&ca_dir_path);
|
||||
info!(%diag);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
if !i_meta.is_dir() {
|
||||
error!(
|
||||
"ERROR: Refusing to run - TLS Client CA folder {} may not be a directory",
|
||||
ca_dir
|
||||
);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
if kanidm_lib_file_permissions::readonly(&i_meta) {
|
||||
warn!("WARNING: TLS Client CA folder permissions on {} indicate it may not be RW. This could cause the server start up to fail!", ca_dir);
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
if i_meta.mode() & 0o007 != 0 {
|
||||
warn!("WARNING: TLS Client CA folder {} has 'everyone' permission bits in the mode. This could be a security risk ...", ca_dir);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -724,6 +753,14 @@ async fn kanidm_main(config: Configuration, opt: KanidmdParser) -> ExitCode {
|
|||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
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 🦀")],
|
||||
|
@ -737,80 +774,86 @@ async fn kanidm_main(config: Configuration, opt: KanidmdParser) -> ExitCode {
|
|||
{
|
||||
let mut listener = sctx.subscribe();
|
||||
tokio::select! {
|
||||
Ok(()) = tokio::signal::ctrl_c() => {
|
||||
break
|
||||
}
|
||||
Some(()) = async move {
|
||||
let sigterm = tokio::signal::unix::SignalKind::terminate();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
tokio::signal::unix::signal(sigterm).unwrap().recv().await
|
||||
} => {
|
||||
break
|
||||
}
|
||||
Some(()) = async move {
|
||||
let sigterm = tokio::signal::unix::SignalKind::alarm();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
tokio::signal::unix::signal(sigterm).unwrap().recv().await
|
||||
} => {
|
||||
// Ignore
|
||||
}
|
||||
Some(()) = async move {
|
||||
let sigterm = tokio::signal::unix::SignalKind::hangup();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
tokio::signal::unix::signal(sigterm).unwrap().recv().await
|
||||
} => {
|
||||
// Reload TLS certificates
|
||||
// systemd has a special reload handler for this.
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if let Ok(monotonic_usec) = sd_notify::NotifyState::monotonic_usec_now() {
|
||||
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.");
|
||||
};
|
||||
}
|
||||
Ok(()) = tokio::signal::ctrl_c() => {
|
||||
break
|
||||
}
|
||||
Some(()) = async move {
|
||||
let sigterm = tokio::signal::unix::SignalKind::terminate();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
tokio::signal::unix::signal(sigterm).unwrap().recv().await
|
||||
} => {
|
||||
break
|
||||
}
|
||||
Some(()) = async move {
|
||||
let sigterm = tokio::signal::unix::SignalKind::alarm();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
tokio::signal::unix::signal(sigterm).unwrap().recv().await
|
||||
} => {
|
||||
// Ignore
|
||||
}
|
||||
Some(()) = async move {
|
||||
let sigterm = tokio::signal::unix::SignalKind::hangup();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
tokio::signal::unix::signal(sigterm).unwrap().recv().await
|
||||
} => {
|
||||
// Reload TLS certificates
|
||||
// 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]);
|
||||
} else {
|
||||
error!("CRITICAL!!! Unable to access clock monotonic time. SYSTEMD WILL KILL US.");
|
||||
};
|
||||
let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Status("Reloading ...")]);
|
||||
}
|
||||
|
||||
sctx.tls_acceptor_reload().await;
|
||||
sctx.tls_acceptor_reload().await;
|
||||
|
||||
// Systemd freaks out if you send the ready state too fast after the
|
||||
// reload state and can kill Kanidmd as a result.
|
||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
// Systemd freaks out if you send the ready state too fast after the
|
||||
// reload state and can kill Kanidmd as a result.
|
||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if let Ok(monotonic_usec) = sd_notify::NotifyState::monotonic_usec_now() {
|
||||
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.");
|
||||
};
|
||||
}
|
||||
#[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]);
|
||||
} 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");
|
||||
}
|
||||
Some(()) = async move {
|
||||
let sigterm = tokio::signal::unix::SignalKind::user_defined1();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
tokio::signal::unix::signal(sigterm).unwrap().recv().await
|
||||
} => {
|
||||
// Ignore
|
||||
}
|
||||
Some(()) = async move {
|
||||
let sigterm = tokio::signal::unix::SignalKind::user_defined2();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
tokio::signal::unix::signal(sigterm).unwrap().recv().await
|
||||
} => {
|
||||
// Ignore
|
||||
}
|
||||
// we got a message on thr broadcast from somewhere else
|
||||
Ok(msg) = async move {
|
||||
listener.recv().await
|
||||
} => {
|
||||
debug!("Main loop received message: {:?}", msg);
|
||||
break
|
||||
}
|
||||
}
|
||||
info!("Reload complete");
|
||||
}
|
||||
Some(()) = async move {
|
||||
let sigterm = tokio::signal::unix::SignalKind::user_defined1();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
tokio::signal::unix::signal(sigterm).unwrap().recv().await
|
||||
} => {
|
||||
// Ignore
|
||||
}
|
||||
Some(()) = async move {
|
||||
let sigterm = tokio::signal::unix::SignalKind::user_defined2();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
tokio::signal::unix::signal(sigterm).unwrap().recv().await
|
||||
} => {
|
||||
// Ignore
|
||||
}
|
||||
// we got a message on thr broadcast from somewhere else
|
||||
Ok(msg) = async move {
|
||||
listener.recv().await
|
||||
} => {
|
||||
debug!("Main loop received message: {:?}", msg);
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(target_family = "windows")]
|
||||
{
|
||||
|
@ -837,19 +880,34 @@ async fn kanidm_main(config: Configuration, opt: KanidmdParser) -> ExitCode {
|
|||
}
|
||||
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 ...");
|
||||
backup_server_core(&config, &bopt.path);
|
||||
let p = match bopt.path.to_str() {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
error!("Invalid backup path");
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
backup_server_core(&config, p);
|
||||
}
|
||||
KanidmdOpt::Database {
|
||||
commands: DbCommands::Restore(ropt),
|
||||
} => {
|
||||
info!("Running in restore mode ...");
|
||||
restore_server_core(&config, &ropt.path).await;
|
||||
let p = match ropt.path.to_str() {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
error!("Invalid restore path");
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
restore_server_core(&config, p).await;
|
||||
}
|
||||
KanidmdOpt::Database {
|
||||
commands: DbCommands::Verify(_vopt),
|
||||
|
@ -1030,6 +1088,8 @@ async fn kanidm_main(config: Configuration, opt: KanidmdParser) -> ExitCode {
|
|||
vacuum_server_core(&config);
|
||||
}
|
||||
KanidmdOpt::HealthCheck(sopt) => {
|
||||
config.update_config_for_server_mode(&sconfig);
|
||||
|
||||
debug!("{sopt:?}");
|
||||
|
||||
let healthcheck_url = match &sopt.check_origin {
|
||||
|
@ -1050,15 +1110,12 @@ async fn kanidm_main(config: Configuration, opt: KanidmdParser) -> ExitCode {
|
|||
.danger_accept_invalid_hostnames(!sopt.verify_tls)
|
||||
.https_only(true);
|
||||
|
||||
client = match &config.tls_config {
|
||||
client = match &sconfig.tls_chain {
|
||||
None => client,
|
||||
Some(tls_config) => {
|
||||
debug!(
|
||||
"Trying to load {} to build a CA cert path",
|
||||
tls_config.chain.display()
|
||||
);
|
||||
Some(ca_cert) => {
|
||||
debug!("Trying to load {} to build a CA cert path", ca_cert);
|
||||
// if the ca_cert file exists, then we'll use it
|
||||
let ca_cert_path = tls_config.chain.clone();
|
||||
let ca_cert_path = PathBuf::from(ca_cert);
|
||||
match ca_cert_path.exists() {
|
||||
true => {
|
||||
let mut cert_buf = Vec::new();
|
||||
|
@ -1091,10 +1148,7 @@ async fn kanidm_main(config: Configuration, opt: KanidmdParser) -> ExitCode {
|
|||
client
|
||||
}
|
||||
false => {
|
||||
warn!(
|
||||
"Couldn't find ca cert {} but carrying on...",
|
||||
tls_config.chain.display()
|
||||
);
|
||||
warn!("Couldn't find ca cert {} but carrying on...", ca_cert);
|
||||
client
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ fn parse_attributes(
|
|||
});
|
||||
|
||||
if !args_are_allowed {
|
||||
let msg = "Invalid test config attribute. The following are allowed";
|
||||
let msg = "Invalid test config attribute. The following are allow";
|
||||
return Err(syn::Error::new_spanned(
|
||||
input.sig.fn_token,
|
||||
format!("{}: {}", msg, ALLOWED_ATTRIBUTES.join(", ")),
|
||||
|
|
|
@ -1,21 +1,27 @@
|
|||
use std::collections::{BTreeMap, BTreeSet, VecDeque};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::time::Duration;
|
||||
|
||||
use super::keystorage::{KeyHandle, KeyHandleId};
|
||||
|
||||
// use 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 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;
|
||||
|
||||
// use uuid::Uuid;
|
||||
|
||||
const DBV_ID2ENTRY: &str = "id2entry";
|
||||
const DBV_INDEXV: &str = "indexv";
|
||||
|
@ -199,7 +205,7 @@ pub(crate) trait IdlSqliteTransaction {
|
|||
let mut stmt = self
|
||||
.get_conn()?
|
||||
.prepare(&format!(
|
||||
"SELECT rowid from {}.sqlite_master where type=\"table\" AND name = :tname LIMIT 1",
|
||||
"SELECT rowid from {}.sqlite_master where name = :tname LIMIT 1",
|
||||
self.get_db_name()
|
||||
))
|
||||
.map_err(sqlite_error)?;
|
||||
|
@ -1706,7 +1712,7 @@ impl IdlSqliteWriteTransaction {
|
|||
|
||||
impl IdlSqlite {
|
||||
pub fn new(cfg: &BackendConfig, vacuum: bool) -> Result<Self, OperationError> {
|
||||
if cfg.path.as_os_str().is_empty() {
|
||||
if cfg.path.is_empty() {
|
||||
debug_assert_eq!(cfg.pool_size, 1);
|
||||
}
|
||||
// If provided, set the page size to match the tuning we want. By default we use 4096. The VACUUM
|
||||
|
@ -1728,7 +1734,8 @@ impl IdlSqlite {
|
|||
|
||||
// Initial setup routines.
|
||||
{
|
||||
let vconn = Connection::open_with_flags(&cfg.path, flags).map_err(sqlite_error)?;
|
||||
let vconn =
|
||||
Connection::open_with_flags(cfg.path.as_str(), flags).map_err(sqlite_error)?;
|
||||
|
||||
vconn
|
||||
.execute_batch(
|
||||
|
@ -1757,7 +1764,8 @@ impl IdlSqlite {
|
|||
);
|
||||
*/
|
||||
|
||||
let vconn = Connection::open_with_flags(&cfg.path, flags).map_err(sqlite_error)?;
|
||||
let vconn =
|
||||
Connection::open_with_flags(cfg.path.as_str(), flags).map_err(sqlite_error)?;
|
||||
|
||||
vconn
|
||||
.execute_batch("PRAGMA wal_checkpoint(TRUNCATE);")
|
||||
|
@ -1778,7 +1786,8 @@ impl IdlSqlite {
|
|||
OperationError::SqliteError
|
||||
})?;
|
||||
|
||||
let vconn = Connection::open_with_flags(&cfg.path, flags).map_err(sqlite_error)?;
|
||||
let vconn =
|
||||
Connection::open_with_flags(cfg.path.as_str(), flags).map_err(sqlite_error)?;
|
||||
|
||||
vconn
|
||||
.pragma_update(None, "page_size", cfg.fstype as u32)
|
||||
|
@ -1812,7 +1821,7 @@ impl IdlSqlite {
|
|||
.map(|i| {
|
||||
trace!("Opening Connection {}", i);
|
||||
let conn =
|
||||
Connection::open_with_flags(&cfg.path, flags).map_err(sqlite_error);
|
||||
Connection::open_with_flags(cfg.path.as_str(), flags).map_err(sqlite_error);
|
||||
match conn {
|
||||
Ok(conn) => {
|
||||
// We need to set the cachesize at this point as well.
|
||||
|
|
|
@ -4,6 +4,20 @@
|
|||
//! is to persist content safely to disk, load that content, and execute queries
|
||||
//! 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;
|
||||
|
@ -17,19 +31,6 @@ 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;
|
||||
|
@ -131,7 +132,7 @@ impl IdxMeta {
|
|||
|
||||
#[derive(Clone)]
|
||||
pub struct BackendConfig {
|
||||
path: PathBuf,
|
||||
path: String,
|
||||
pool_size: u32,
|
||||
db_name: &'static str,
|
||||
fstype: FsType,
|
||||
|
@ -140,16 +141,10 @@ pub struct BackendConfig {
|
|||
}
|
||||
|
||||
impl BackendConfig {
|
||||
pub fn new(
|
||||
path: Option<&Path>,
|
||||
pool_size: u32,
|
||||
fstype: FsType,
|
||||
arcsize: Option<usize>,
|
||||
) -> Self {
|
||||
pub fn new(path: &str, pool_size: u32, fstype: FsType, arcsize: Option<usize>) -> Self {
|
||||
BackendConfig {
|
||||
pool_size,
|
||||
// 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(),
|
||||
path: path.to_string(),
|
||||
db_name: "main",
|
||||
fstype,
|
||||
arcsize,
|
||||
|
@ -159,7 +154,7 @@ impl BackendConfig {
|
|||
pub(crate) fn new_test(db_name: &'static str) -> Self {
|
||||
BackendConfig {
|
||||
pool_size: 1,
|
||||
path: PathBuf::from(""),
|
||||
path: "".to_string(),
|
||||
db_name,
|
||||
fstype: FsType::Generic,
|
||||
arcsize: Some(2048),
|
||||
|
@ -554,11 +549,10 @@ pub trait BackendTransaction {
|
|||
}
|
||||
(_, fp) => {
|
||||
plan.push(fp);
|
||||
let setplan = FilterPlan::InclusionInvalid(plan);
|
||||
error!(
|
||||
?setplan,
|
||||
filter_error!(
|
||||
"Inclusion is unable to proceed - all terms must be fully indexed!"
|
||||
);
|
||||
let setplan = FilterPlan::InclusionInvalid(plan);
|
||||
return Ok((IdList::Partial(IDLBitRange::new()), setplan));
|
||||
}
|
||||
}
|
||||
|
@ -941,7 +935,7 @@ pub trait BackendTransaction {
|
|||
self.get_ruv().verify(&entries, results);
|
||||
}
|
||||
|
||||
fn backup(&mut self, dst_path: &Path) -> Result<(), OperationError> {
|
||||
fn backup(&mut self, dst_path: &str) -> Result<(), OperationError> {
|
||||
let repl_meta = self.get_ruv().to_db_backup_ruv();
|
||||
|
||||
// load all entries into RAM, may need to change this later
|
||||
|
@ -1433,16 +1427,20 @@ impl<'a> BackendWriteTransaction<'a> {
|
|||
if self.is_idx_slopeyness_generated()? {
|
||||
trace!("Indexing slopes available");
|
||||
} else {
|
||||
warn!("No indexing slopes available. You should consider reindexing to generate these");
|
||||
admin_warn!(
|
||||
"No indexing slopes available. You should consider reindexing to generate these"
|
||||
);
|
||||
};
|
||||
|
||||
// Setup idxkeys here. By default we set these all to "max slope" aka
|
||||
// all indexes are "equal" but also worse case unless analysed. If they
|
||||
// have been analysed, we can set the slope factor into here.
|
||||
let mut idxkeys = idxkeys
|
||||
let idxkeys: Result<Map<_, _>, _> = idxkeys
|
||||
.into_iter()
|
||||
.map(|k| self.get_idx_slope(&k).map(|slope| (k, slope)))
|
||||
.collect::<Result<Map<_, _>, _>>()?;
|
||||
.collect();
|
||||
|
||||
let mut idxkeys = idxkeys?;
|
||||
|
||||
std::mem::swap(&mut self.idxmeta_wr.deref_mut().idxkeys, &mut idxkeys);
|
||||
Ok(())
|
||||
|
@ -1813,7 +1811,7 @@ impl<'a> BackendWriteTransaction<'a> {
|
|||
Ok(slope)
|
||||
}
|
||||
|
||||
pub fn restore(&mut self, src_path: &Path) -> Result<(), OperationError> {
|
||||
pub fn restore(&mut self, src_path: &str) -> Result<(), OperationError> {
|
||||
let serialized_string = fs::read_to_string(src_path).map_err(|e| {
|
||||
admin_error!("fs::read_to_string {:?}", e);
|
||||
OperationError::FsError
|
||||
|
@ -2126,7 +2124,7 @@ impl Backend {
|
|||
debug!(db_tickets = ?cfg.pool_size, profile = %env!("KANIDM_PROFILE_NAME"), cpu_flags = %env!("KANIDM_CPU_FLAGS"));
|
||||
|
||||
// If in memory, reduce pool to 1
|
||||
if cfg.path.as_os_str().is_empty() {
|
||||
if cfg.path.is_empty() {
|
||||
cfg.pool_size = 1;
|
||||
}
|
||||
|
||||
|
@ -2212,6 +2210,13 @@ 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::{
|
||||
|
@ -2221,12 +2226,6 @@ 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();
|
||||
|
@ -2601,9 +2600,11 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_be_backup_restore() {
|
||||
let db_backup_file_name =
|
||||
Path::new(option_env!("OUT_DIR").unwrap_or("/tmp")).join(".backup_test.json");
|
||||
eprintln!(" ⚠️ {}", db_backup_file_name.display());
|
||||
let db_backup_file_name = format!(
|
||||
"{}/.backup_test.json",
|
||||
option_env!("OUT_DIR").unwrap_or("/tmp")
|
||||
);
|
||||
eprintln!(" ⚠️ {db_backup_file_name}");
|
||||
run_test!(|be: &mut BackendWriteTransaction| {
|
||||
// Important! Need db metadata setup!
|
||||
be.reset_db_s_uuid().unwrap();
|
||||
|
@ -2658,9 +2659,11 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_be_backup_restore_tampered() {
|
||||
let db_backup_file_name =
|
||||
Path::new(option_env!("OUT_DIR").unwrap_or("/tmp")).join(".backup2_test.json");
|
||||
eprintln!(" ⚠️ {}", db_backup_file_name.display());
|
||||
let db_backup_file_name = format!(
|
||||
"{}/.backup2_test.json",
|
||||
option_env!("OUT_DIR").unwrap_or("/tmp")
|
||||
);
|
||||
eprintln!(" ⚠️ {db_backup_file_name}");
|
||||
run_test!(|be: &mut BackendWriteTransaction| {
|
||||
// Important! Need db metadata setup!
|
||||
be.reset_db_s_uuid().unwrap();
|
||||
|
|
|
@ -136,6 +136,8 @@ pub const UUID_SCHEMA_ATTR_PRIMARY_CREDENTIAL: Uuid = uuid!("00000000-0000-0000-
|
|||
pub const UUID_SCHEMA_CLASS_PERSON: Uuid = uuid!("00000000-0000-0000-0000-ffff00000044");
|
||||
pub const UUID_SCHEMA_CLASS_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");
|
||||
|
@ -327,13 +329,6 @@ 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,97 +492,6 @@ impl Entry<EntryInit, EntryNew> {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<SchemaAttribute> for EntryInitNew {
|
||||
fn from(value: SchemaAttribute) -> Self {
|
||||
EntryInitNew::from(&value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SchemaAttribute> for EntryInitNew {
|
||||
fn from(s: &SchemaAttribute) -> Self {
|
||||
// Build the Map of the attributes
|
||||
let mut attrs = Eattrs::new();
|
||||
attrs.insert(Attribute::AttributeName, vs_iutf8![s.name.as_str()]);
|
||||
attrs.insert(Attribute::Description, vs_utf8![s.description.to_owned()]);
|
||||
attrs.insert(Attribute::Uuid, vs_uuid![s.uuid]);
|
||||
attrs.insert(Attribute::MultiValue, vs_bool![s.multivalue]);
|
||||
attrs.insert(Attribute::Phantom, vs_bool![s.phantom]);
|
||||
attrs.insert(Attribute::SyncAllowed, vs_bool![s.sync_allowed]);
|
||||
attrs.insert(Attribute::Replicated, vs_bool![s.replicated.into()]);
|
||||
attrs.insert(Attribute::Unique, vs_bool![s.unique]);
|
||||
attrs.insert(Attribute::Indexed, vs_bool![s.indexed]);
|
||||
attrs.insert(Attribute::Syntax, vs_syntax![s.syntax]);
|
||||
attrs.insert(
|
||||
Attribute::Class,
|
||||
vs_iutf8![
|
||||
EntryClass::Object.into(),
|
||||
EntryClass::System.into(),
|
||||
EntryClass::AttributeType.into()
|
||||
],
|
||||
);
|
||||
|
||||
// Insert stuff.
|
||||
|
||||
Entry {
|
||||
valid: EntryInit,
|
||||
state: EntryNew,
|
||||
attrs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SchemaClass> for EntryInitNew {
|
||||
fn from(value: SchemaClass) -> Self {
|
||||
EntryInitNew::from(&value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SchemaClass> for EntryInitNew {
|
||||
fn from(s: &SchemaClass) -> Self {
|
||||
let mut attrs = Eattrs::new();
|
||||
attrs.insert(Attribute::ClassName, vs_iutf8![s.name.as_str()]);
|
||||
attrs.insert(Attribute::Description, vs_utf8![s.description.to_owned()]);
|
||||
attrs.insert(Attribute::SyncAllowed, vs_bool![s.sync_allowed]);
|
||||
attrs.insert(Attribute::Uuid, vs_uuid![s.uuid]);
|
||||
attrs.insert(
|
||||
Attribute::Class,
|
||||
vs_iutf8![
|
||||
EntryClass::Object.into(),
|
||||
EntryClass::System.into(),
|
||||
EntryClass::ClassType.into()
|
||||
],
|
||||
);
|
||||
|
||||
let vs_systemmay = ValueSetIutf8::from_iter(s.systemmay.iter().map(|sm| sm.as_str()));
|
||||
if let Some(vs) = vs_systemmay {
|
||||
attrs.insert(Attribute::SystemMay, vs);
|
||||
}
|
||||
|
||||
let vs_systemmust = ValueSetIutf8::from_iter(s.systemmust.iter().map(|sm| sm.as_str()));
|
||||
if let Some(vs) = vs_systemmust {
|
||||
attrs.insert(Attribute::SystemMust, vs);
|
||||
}
|
||||
|
||||
let vs_systemexcludes =
|
||||
ValueSetIutf8::from_iter(s.systemexcludes.iter().map(|sm| sm.as_str()));
|
||||
if let Some(vs) = vs_systemexcludes {
|
||||
attrs.insert(Attribute::SystemExcludes, vs);
|
||||
}
|
||||
|
||||
let vs_systemsupplements =
|
||||
ValueSetIutf8::from_iter(s.systemsupplements.iter().map(|sm| sm.as_str()));
|
||||
if let Some(vs) = vs_systemsupplements {
|
||||
attrs.insert(Attribute::SystemSupplements, vs);
|
||||
}
|
||||
|
||||
Entry {
|
||||
valid: EntryInit,
|
||||
state: EntryNew,
|
||||
attrs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Entry<EntryRefresh, EntryNew> {
|
||||
pub fn from_repl_entry_v1(repl_entry: ReplEntryV1) -> Result<Self, OperationError> {
|
||||
// From the entry, we have to rebuild the ecstate and the attrs.
|
||||
|
@ -2040,7 +1949,7 @@ impl<STATE> Entry<EntryValid, STATE> {
|
|||
};
|
||||
|
||||
if !valid_supplements {
|
||||
warn!(
|
||||
admin_warn!(
|
||||
"Validation error, the following possible supplement classes are missing - {:?}",
|
||||
supplements_classes
|
||||
);
|
||||
|
@ -2724,6 +2633,21 @@ 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> {
|
||||
|
@ -3336,6 +3260,97 @@ impl<VALID, STATE> PartialEq for Entry<VALID, STATE> {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<&SchemaAttribute> for Entry<EntryInit, EntryNew> {
|
||||
fn from(s: &SchemaAttribute) -> Self {
|
||||
// Convert an Attribute to an entry ... make it good!
|
||||
let uuid_v = vs_uuid![s.uuid];
|
||||
let name_v = vs_iutf8![s.name.as_str()];
|
||||
let desc_v = vs_utf8![s.description.to_owned()];
|
||||
|
||||
let multivalue_v = vs_bool![s.multivalue];
|
||||
let sync_allowed_v = vs_bool![s.sync_allowed];
|
||||
let replicated_v = vs_bool![s.replicated];
|
||||
let phantom_v = vs_bool![s.phantom];
|
||||
let unique_v = vs_bool![s.unique];
|
||||
|
||||
let index_v = ValueSetIndex::from_iter(s.index.iter().copied());
|
||||
|
||||
let syntax_v = vs_syntax![s.syntax];
|
||||
|
||||
// Build the Map of the attributes relevant
|
||||
// let mut attrs: Map<AttrString, Set<Value>> = Map::with_capacity(8);
|
||||
let mut attrs: Map<Attribute, ValueSet> = Map::new();
|
||||
attrs.insert(Attribute::AttributeName, name_v);
|
||||
attrs.insert(Attribute::Description, desc_v);
|
||||
attrs.insert(Attribute::Uuid, uuid_v);
|
||||
attrs.insert(Attribute::MultiValue, multivalue_v);
|
||||
attrs.insert(Attribute::Phantom, phantom_v);
|
||||
attrs.insert(Attribute::SyncAllowed, sync_allowed_v);
|
||||
attrs.insert(Attribute::Replicated, replicated_v);
|
||||
attrs.insert(Attribute::Unique, unique_v);
|
||||
if let Some(vs) = index_v {
|
||||
attrs.insert(Attribute::Index, vs);
|
||||
}
|
||||
attrs.insert(Attribute::Syntax, syntax_v);
|
||||
attrs.insert(
|
||||
Attribute::Class,
|
||||
vs_iutf8![
|
||||
EntryClass::Object.into(),
|
||||
EntryClass::System.into(),
|
||||
EntryClass::AttributeType.into()
|
||||
],
|
||||
);
|
||||
|
||||
// Insert stuff.
|
||||
|
||||
Entry {
|
||||
valid: EntryInit,
|
||||
state: EntryNew,
|
||||
attrs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SchemaClass> for Entry<EntryInit, EntryNew> {
|
||||
fn from(s: &SchemaClass) -> Self {
|
||||
let uuid_v = vs_uuid![s.uuid];
|
||||
let name_v = vs_iutf8![s.name.as_str()];
|
||||
let desc_v = vs_utf8![s.description.to_owned()];
|
||||
let sync_allowed_v = vs_bool![s.sync_allowed];
|
||||
|
||||
let mut attrs: Map<Attribute, ValueSet> = Map::new();
|
||||
attrs.insert(Attribute::ClassName, name_v);
|
||||
attrs.insert(Attribute::Description, desc_v);
|
||||
attrs.insert(Attribute::SyncAllowed, sync_allowed_v);
|
||||
attrs.insert(Attribute::Uuid, uuid_v);
|
||||
attrs.insert(
|
||||
Attribute::Class,
|
||||
vs_iutf8![
|
||||
EntryClass::Object.into(),
|
||||
EntryClass::System.into(),
|
||||
EntryClass::ClassType.into()
|
||||
],
|
||||
);
|
||||
|
||||
let vs_systemmay = ValueSetIutf8::from_iter(s.systemmay.iter().map(|sm| sm.as_str()));
|
||||
if let Some(vs) = vs_systemmay {
|
||||
attrs.insert(Attribute::SystemMay, vs);
|
||||
}
|
||||
|
||||
let vs_systemmust = ValueSetIutf8::from_iter(s.systemmust.iter().map(|sm| sm.as_str()));
|
||||
|
||||
if let Some(vs) = vs_systemmust {
|
||||
attrs.insert(Attribute::SystemMust, vs);
|
||||
}
|
||||
|
||||
Entry {
|
||||
valid: EntryInit,
|
||||
state: EntryNew,
|
||||
attrs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::*;
|
||||
|
|
|
@ -527,8 +527,7 @@ 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.
|
||||
|
||||
// Don't cache anything unless we have valid indexing metadata.
|
||||
let cacheable = idxmeta.is_some() && FilterResolved::resolve_cacheable(&self.state.inner);
|
||||
let cacheable = FilterResolved::resolve_cacheable(&self.state.inner);
|
||||
|
||||
let cache_key = if cacheable {
|
||||
// do we have a cache?
|
||||
|
@ -537,7 +536,6 @@ 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.
|
||||
|
@ -576,7 +574,6 @@ 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::Deny => false,
|
||||
Access::Denied => false,
|
||||
Access::Grant => true,
|
||||
Access::Allow(attrs) => attrs.contains(&Attribute::PrimaryCredential),
|
||||
};
|
||||
|
||||
let eperm_mod_primary_cred = match &eperm.modify_pres {
|
||||
Access::Deny => false,
|
||||
Access::Denied => false,
|
||||
Access::Grant => true,
|
||||
Access::Allow(attrs) => attrs.contains(&Attribute::PrimaryCredential),
|
||||
};
|
||||
|
||||
let eperm_rem_primary_cred = match &eperm.modify_rem {
|
||||
Access::Deny => false,
|
||||
Access::Denied => 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::Deny => false,
|
||||
Access::Denied => false,
|
||||
Access::Grant => true,
|
||||
Access::Allow(attrs) => attrs.contains(&Attribute::PassKeys),
|
||||
};
|
||||
|
||||
let eperm_mod_passkeys = match &eperm.modify_pres {
|
||||
Access::Deny => false,
|
||||
Access::Denied => false,
|
||||
Access::Grant => true,
|
||||
Access::Allow(attrs) => attrs.contains(&Attribute::PassKeys),
|
||||
};
|
||||
|
||||
let eperm_rem_passkeys = match &eperm.modify_rem {
|
||||
Access::Deny => false,
|
||||
Access::Denied => 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::Deny => false,
|
||||
Access::Denied => false,
|
||||
Access::Grant => true,
|
||||
Access::Allow(attrs) => attrs.contains(&Attribute::AttestedPasskeys),
|
||||
};
|
||||
|
||||
let eperm_mod_attested_passkeys = match &eperm.modify_pres {
|
||||
Access::Deny => false,
|
||||
Access::Denied => false,
|
||||
Access::Grant => true,
|
||||
Access::Allow(attrs) => attrs.contains(&Attribute::AttestedPasskeys),
|
||||
};
|
||||
|
||||
let eperm_rem_attested_passkeys = match &eperm.modify_rem {
|
||||
Access::Deny => false,
|
||||
Access::Denied => 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::Deny => false,
|
||||
Access::Denied => false,
|
||||
Access::Grant => true,
|
||||
Access::Allow(attrs) => attrs.contains(&Attribute::UnixPassword),
|
||||
};
|
||||
|
||||
let eperm_mod_unixcred = match &eperm.modify_pres {
|
||||
Access::Deny => false,
|
||||
Access::Denied => false,
|
||||
Access::Grant => true,
|
||||
Access::Allow(attrs) => attrs.contains(&Attribute::UnixPassword),
|
||||
};
|
||||
|
||||
let eperm_rem_unixcred = match &eperm.modify_rem {
|
||||
Access::Deny => false,
|
||||
Access::Denied => 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::Deny => false,
|
||||
Access::Denied => false,
|
||||
Access::Grant => true,
|
||||
Access::Allow(attrs) => attrs.contains(&Attribute::SshPublicKey),
|
||||
};
|
||||
|
||||
let eperm_mod_sshpubkey = match &eperm.modify_pres {
|
||||
Access::Deny => false,
|
||||
Access::Denied => false,
|
||||
Access::Grant => true,
|
||||
Access::Allow(attrs) => attrs.contains(&Attribute::SshPublicKey),
|
||||
};
|
||||
|
||||
let eperm_rem_sshpubkey = match &eperm.modify_rem {
|
||||
Access::Deny => false,
|
||||
Access::Denied => false,
|
||||
Access::Grant => true,
|
||||
Access::Allow(attrs) => attrs.contains(&Attribute::SshPublicKey),
|
||||
};
|
||||
|
@ -726,7 +726,7 @@ impl IdmServerProxyWriteTransaction<'_> {
|
|||
})?;
|
||||
|
||||
match &eperm.search {
|
||||
Access::Deny => false,
|
||||
Access::Denied => 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, ValueSetIutf8, ValueSetRefer, ValueSetSyntax,
|
||||
ValueSetT, ValueSetUtf8, ValueSetUuid,
|
||||
ValueSet, ValueSetBool, ValueSetCid, ValueSetIndex, ValueSetIutf8, ValueSetRefer,
|
||||
ValueSetSyntax, ValueSetT, ValueSetUtf8, ValueSetUuid,
|
||||
};
|
||||
|
||||
pub(crate) use kanidm_proto::scim_v1::{
|
||||
|
|
|
@ -620,6 +620,22 @@ macro_rules! vs_syntax {
|
|||
});
|
||||
}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
#[macro_export]
|
||||
macro_rules! vs_index {
|
||||
() => (
|
||||
compile_error!("ValueSetIndex needs at least 1 element")
|
||||
);
|
||||
($e:expr) => ({
|
||||
ValueSetIndex::new($e)
|
||||
});
|
||||
($e:expr, $($item:expr),*) => ({
|
||||
let mut x = ValueSetIndex::new($e);
|
||||
$(assert!(x.push($item));)*
|
||||
x
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
#[macro_export]
|
||||
macro_rules! vs_cid {
|
||||
|
|
|
@ -72,8 +72,6 @@ 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>,
|
||||
}
|
||||
|
@ -161,19 +159,9 @@ 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));
|
||||
});
|
||||
|
@ -226,7 +214,7 @@ lazy_static! {
|
|||
ATTR_RECYCLED.to_string()
|
||||
)),
|
||||
modify_removed_attrs: vec![Attribute::Class],
|
||||
modify_remove_classes: vec![EntryClass::Recycled],
|
||||
modify_classes: vec![EntryClass::Recycled],
|
||||
..Default::default()
|
||||
};
|
||||
}
|
||||
|
@ -437,7 +425,6 @@ lazy_static! {
|
|||
EntryClass::AccessControlCreate,
|
||||
EntryClass::AccessControlDelete,
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -69,6 +69,7 @@ pub fn phase_1_schema_attrs() -> Vec<EntryInitNew> {
|
|||
SCHEMA_ATTR_SYNC_TOKEN_SESSION.clone().into(),
|
||||
SCHEMA_ATTR_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,25 +2,52 @@
|
|||
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(),
|
||||
indexed: true,
|
||||
|
||||
index: vec![IndexType::Equality, IndexType::SubString],
|
||||
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(),
|
||||
indexed: true,
|
||||
|
||||
index: vec![IndexType::Equality, IndexType::SubString],
|
||||
unique: true,
|
||||
multivalue: true,
|
||||
sync_allowed: true,
|
||||
|
@ -32,7 +59,8 @@ pub static ref SCHEMA_ATTR_EC_KEY_PRIVATE: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_EC_KEY_PRIVATE,
|
||||
name: Attribute::IdVerificationEcKey,
|
||||
description: "Account verification private key".to_string(),
|
||||
indexed: true,
|
||||
|
||||
index: vec![IndexType::Presence],
|
||||
unique: false,
|
||||
sync_allowed: false,
|
||||
syntax: SyntaxType::EcKeyPrivate,
|
||||
|
@ -54,17 +82,30 @@ 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(),
|
||||
indexed: true,
|
||||
|
||||
index: vec![IndexType::Presence],
|
||||
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(),
|
||||
indexed: true,
|
||||
|
||||
index: vec![IndexType::Equality, IndexType::SubString],
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::Utf8String,
|
||||
..Default::default()
|
||||
|
@ -74,7 +115,8 @@ 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(),
|
||||
indexed: true,
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
multivalue: true,
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::AuditLogString,
|
||||
|
@ -85,6 +127,7 @@ 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()
|
||||
|
@ -94,7 +137,8 @@ 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(),
|
||||
indexed: true,
|
||||
|
||||
index: vec![IndexType::Equality, IndexType::Presence],
|
||||
unique: true,
|
||||
syntax: SyntaxType::Utf8StringIname,
|
||||
..Default::default()
|
||||
|
@ -104,6 +148,7 @@ pub static ref SCHEMA_ATTR_LDAP_ALLOW_UNIX_PW_BIND: SchemaAttribute = SchemaAttr
|
|||
uuid: UUID_SCHEMA_ATTR_LDAP_ALLOW_UNIX_PW_BIND,
|
||||
name: Attribute::LdapAllowUnixPwBind,
|
||||
description: "Configuration to enable binds to LDAP objects using their UNIX password".to_string(),
|
||||
|
||||
unique: false,
|
||||
syntax: SyntaxType::Boolean,
|
||||
..Default::default()
|
||||
|
@ -113,6 +158,7 @@ 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()
|
||||
|
@ -122,6 +168,7 @@ 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,
|
||||
|
@ -132,7 +179,8 @@ 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(),
|
||||
indexed: true,
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
syntax: SyntaxType::Utf8String,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -141,7 +189,8 @@ 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(),
|
||||
indexed: true,
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
unique: true,
|
||||
syntax: SyntaxType::Uuid,
|
||||
..Default::default()
|
||||
|
@ -151,16 +200,27 @@ 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(),
|
||||
indexed: true,
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
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()
|
||||
|
@ -170,6 +230,7 @@ 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()
|
||||
};
|
||||
|
@ -187,7 +248,8 @@ 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(),
|
||||
indexed: true,
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
unique: true,
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::Uint32,
|
||||
|
@ -198,6 +260,7 @@ 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()
|
||||
|
@ -207,6 +270,7 @@ 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()
|
||||
};
|
||||
|
@ -215,6 +279,7 @@ 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()
|
||||
};
|
||||
|
@ -223,6 +288,7 @@ 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()
|
||||
};
|
||||
|
@ -231,6 +297,7 @@ 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()
|
||||
|
@ -240,7 +307,8 @@ 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(),
|
||||
indexed: true,
|
||||
|
||||
index: vec![IndexType::Presence],
|
||||
syntax: SyntaxType::Credential,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -249,7 +317,8 @@ 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(),
|
||||
indexed: true,
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
unique: true,
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::NsUniqueId,
|
||||
|
@ -260,6 +329,7 @@ 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()
|
||||
|
@ -269,6 +339,7 @@ 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()
|
||||
|
@ -278,6 +349,7 @@ 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()
|
||||
|
@ -287,16 +359,27 @@ 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(),
|
||||
indexed: true,
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
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()
|
||||
|
@ -306,6 +389,7 @@ 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()
|
||||
};
|
||||
|
@ -315,6 +399,7 @@ 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()
|
||||
};
|
||||
|
@ -323,7 +408,8 @@ 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(),
|
||||
indexed: true,
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
multivalue: true,
|
||||
// CHANGE ME
|
||||
syntax: SyntaxType::OauthClaimMap,
|
||||
|
@ -334,7 +420,8 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_SCOPE_MAP: SchemaAttribute = SchemaAttribut
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_SCOPE_MAP,
|
||||
name: Attribute::OAuth2RsScopeMap,
|
||||
description: "A reference to a group mapped to scopes for the associated oauth2 resource server".to_string(),
|
||||
indexed: true,
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::OauthScopeMap,
|
||||
..Default::default()
|
||||
|
@ -344,7 +431,8 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_SUP_SCOPE_MAP: SchemaAttribute = SchemaAttr
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_SUP_SCOPE_MAP,
|
||||
name: Attribute::OAuth2RsSupScopeMap,
|
||||
description: "A reference to a group mapped to scopes for the associated oauth2 resource server".to_string(),
|
||||
indexed: true,
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::OauthScopeMap,
|
||||
..Default::default()
|
||||
|
@ -354,6 +442,7 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_BASIC_SECRET: SchemaAttribute = SchemaAttri
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_BASIC_SECRET,
|
||||
name: Attribute::OAuth2RsBasicSecret,
|
||||
description: "When using oauth2 basic authentication, the secret string of the resource server".to_string(),
|
||||
|
||||
syntax: SyntaxType::SecretUtf8String,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -362,6 +451,7 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_TOKEN_KEY: SchemaAttribute = SchemaAttribut
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_TOKEN_KEY,
|
||||
name: Attribute::OAuth2RsTokenKey,
|
||||
description: "An oauth2 resource servers unique token signing key".to_string(),
|
||||
|
||||
syntax: SyntaxType::SecretUtf8String,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -370,6 +460,7 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_IMPLICIT_SCOPES: SchemaAttribute = SchemaAt
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_IMPLICIT_SCOPES,
|
||||
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()
|
||||
|
@ -379,7 +470,8 @@ 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(),
|
||||
indexed: true,
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::OauthScopeMap,
|
||||
..Default::default()
|
||||
|
@ -389,6 +481,7 @@ pub static ref SCHEMA_ATTR_OAUTH2_STRICT_REDIRECT_URI_DL7: SchemaAttribute = Sch
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_STRICT_REDIRECT_URI,
|
||||
name: Attribute::OAuth2StrictRedirectUri,
|
||||
description: "Represents if strict redirect uri enforcement is enabled.".to_string(),
|
||||
|
||||
syntax: SyntaxType::Boolean,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -398,6 +491,7 @@ pub static ref SCHEMA_ATTR_OAUTH2_DEVICE_FLOW_ENABLE_DL9: SchemaAttribute = Sche
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_DEVICE_FLOW_ENABLE,
|
||||
name: Attribute::OAuth2DeviceFlowEnable,
|
||||
description: "Represents if OAuth2 Device Flow is permitted on this client.".to_string(),
|
||||
|
||||
syntax: SyntaxType::Boolean,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -406,6 +500,7 @@ pub static ref SCHEMA_ATTR_ES256_PRIVATE_KEY_DER: SchemaAttribute = SchemaAttrib
|
|||
uuid: UUID_SCHEMA_ATTR_ES256_PRIVATE_KEY_DER,
|
||||
name: Attribute::Es256PrivateKeyDer,
|
||||
description: "An es256 private key".to_string(),
|
||||
|
||||
syntax: SyntaxType::PrivateBinary,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -414,6 +509,7 @@ pub static ref SCHEMA_ATTR_RS256_PRIVATE_KEY_DER: SchemaAttribute = SchemaAttrib
|
|||
uuid: UUID_SCHEMA_ATTR_RS256_PRIVATE_KEY_DER,
|
||||
name: Attribute::Rs256PrivateKeyDer,
|
||||
description: "An rs256 private key".to_string(),
|
||||
|
||||
syntax: SyntaxType::PrivateBinary,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -422,7 +518,8 @@ pub static ref SCHEMA_ATTR_JWS_ES256_PRIVATE_KEY: SchemaAttribute = SchemaAttrib
|
|||
uuid: UUID_SCHEMA_ATTR_JWS_ES256_PRIVATE_KEY,
|
||||
name: Attribute::JwsEs256PrivateKey,
|
||||
description: "An es256 private key for jws".to_string(),
|
||||
indexed: true,
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
unique: true,
|
||||
syntax: SyntaxType::JwsKeyEs256,
|
||||
..Default::default()
|
||||
|
@ -433,6 +530,7 @@ 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()
|
||||
};
|
||||
|
@ -441,6 +539,7 @@ pub static ref SCHEMA_ATTR_OAUTH2_ALLOW_INSECURE_CLIENT_DISABLE_PKCE: SchemaAttr
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_ALLOW_INSECURE_CLIENT_DISABLE_PKCE,
|
||||
name: Attribute::OAuth2AllowInsecureClientDisablePkce,
|
||||
description: "Allows disabling of PKCE for insecure OAuth2 clients".to_string(),
|
||||
|
||||
syntax: SyntaxType::Boolean,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -449,6 +548,7 @@ pub static ref SCHEMA_ATTR_OAUTH2_JWT_LEGACY_CRYPTO_ENABLE: SchemaAttribute = Sc
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_JWT_LEGACY_CRYPTO_ENABLE,
|
||||
name: Attribute::OAuth2JwtLegacyCryptoEnable,
|
||||
description: "Allows enabling legacy JWT cryptograhpy for clients".to_string(),
|
||||
|
||||
syntax: SyntaxType::Boolean,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -457,7 +557,8 @@ pub static ref SCHEMA_ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN: SchemaAttribute = Sch
|
|||
uuid: UUID_SCHEMA_ATTR_CREDENTIAL_UPDATE_INTENT_TOKEN,
|
||||
name: Attribute::CredentialUpdateIntentToken,
|
||||
description: "The status of a credential update intent token".to_string(),
|
||||
indexed: true,
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::IntentToken,
|
||||
..Default::default()
|
||||
|
@ -467,7 +568,8 @@ pub static ref SCHEMA_ATTR_PASSKEYS: SchemaAttribute = SchemaAttribute {
|
|||
uuid: UUID_SCHEMA_ATTR_PASSKEYS,
|
||||
name: Attribute::PassKeys,
|
||||
description: "A set of registered passkeys".to_string(),
|
||||
indexed: true,
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
multivalue: true,
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::Passkey,
|
||||
|
@ -478,7 +580,8 @@ 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(),
|
||||
indexed: true,
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
multivalue: true,
|
||||
sync_allowed: true,
|
||||
syntax: SyntaxType::AttestedPasskey,
|
||||
|
@ -489,6 +592,7 @@ 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()
|
||||
};
|
||||
|
@ -497,6 +601,7 @@ 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()
|
||||
};
|
||||
|
@ -505,7 +610,8 @@ 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(),
|
||||
indexed: true,
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
unique: true,
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::ApiToken,
|
||||
|
@ -516,7 +622,8 @@ pub static ref SCHEMA_ATTR_USER_AUTH_TOKEN_SESSION: SchemaAttribute = SchemaAttr
|
|||
uuid: UUID_SCHEMA_ATTR_USER_AUTH_TOKEN_SESSION,
|
||||
name: Attribute::UserAuthTokenSession,
|
||||
description: "A session entry related to an issued user auth token".to_string(),
|
||||
indexed: true,
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
unique: true,
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::Session,
|
||||
|
@ -527,7 +634,8 @@ 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(),
|
||||
indexed: true,
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::Oauth2Session,
|
||||
..Default::default()
|
||||
|
@ -537,7 +645,8 @@ 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(),
|
||||
indexed: true,
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
unique: true,
|
||||
syntax: SyntaxType::ApiToken,
|
||||
..Default::default()
|
||||
|
@ -547,6 +656,7 @@ 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()
|
||||
};
|
||||
|
@ -555,7 +665,8 @@ 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(),
|
||||
indexed: true,
|
||||
|
||||
index: vec![IndexType::Equality],
|
||||
multivalue: true,
|
||||
syntax: SyntaxType::UiHint,
|
||||
..Default::default()
|
||||
|
@ -565,6 +676,7 @@ 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()
|
||||
};
|
||||
|
@ -573,6 +685,7 @@ 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()
|
||||
|
@ -582,6 +695,7 @@ 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()
|
||||
|
@ -591,6 +705,7 @@ 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()
|
||||
|
@ -600,6 +715,7 @@ pub static ref SCHEMA_ATTR_LIMIT_SEARCH_MAX_FILTER_TEST_DL6: SchemaAttribute = S
|
|||
uuid: UUID_SCHEMA_ATTR_LIMIT_SEARCH_MAX_FILTER_TEST,
|
||||
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()
|
||||
|
@ -619,7 +735,6 @@ 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()
|
||||
};
|
||||
|
@ -685,7 +800,6 @@ 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()
|
||||
|
@ -695,8 +809,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()
|
||||
};
|
||||
|
@ -705,6 +819,7 @@ 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()
|
||||
|
@ -723,13 +838,57 @@ 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(),
|
||||
|
@ -802,6 +961,24 @@ 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(),
|
||||
|
@ -821,6 +998,40 @@ 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(),
|
||||
|
@ -845,6 +1056,29 @@ 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(),
|
||||
|
@ -866,6 +1100,23 @@ 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(),
|
||||
|
@ -882,6 +1133,100 @@ 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(),
|
||||
|
@ -945,6 +1290,83 @@ 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,6 +2,7 @@
|
|||
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!(
|
||||
|
@ -10,6 +11,8 @@ 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()
|
||||
|
@ -19,6 +22,8 @@ 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()
|
||||
|
@ -28,6 +33,8 @@ 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,
|
||||
|
@ -39,6 +46,8 @@ 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,
|
||||
|
@ -50,6 +59,8 @@ 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,
|
||||
|
@ -71,6 +82,8 @@ 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()
|
||||
|
@ -80,6 +93,8 @@ 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()
|
||||
|
@ -89,6 +104,8 @@ 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()
|
||||
|
@ -98,6 +115,8 @@ 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,
|
||||
|
@ -118,6 +137,8 @@ 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()
|
||||
|
@ -147,6 +168,8 @@ 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()
|
||||
};
|
||||
|
@ -155,6 +178,8 @@ 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()
|
||||
|
@ -164,6 +189,8 @@ 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()
|
||||
|
@ -210,6 +237,8 @@ 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,
|
||||
|
@ -267,6 +296,8 @@ 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()
|
||||
};
|
||||
|
@ -275,6 +306,8 @@ 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,
|
||||
|
@ -315,6 +348,8 @@ 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()
|
||||
|
@ -362,6 +397,8 @@ 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,
|
||||
|
@ -372,6 +409,8 @@ 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()
|
||||
|
@ -381,6 +420,8 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_SUP_SCOPE_MAP: SchemaAttribute = SchemaAttr
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_SUP_SCOPE_MAP,
|
||||
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()
|
||||
|
@ -418,6 +459,8 @@ 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()
|
||||
|
@ -464,6 +507,8 @@ 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()
|
||||
|
@ -501,6 +546,8 @@ 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()
|
||||
|
@ -510,6 +557,8 @@ 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,
|
||||
|
@ -520,6 +569,8 @@ 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,
|
||||
|
@ -548,6 +599,8 @@ 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,
|
||||
|
@ -558,6 +611,8 @@ 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,
|
||||
|
@ -568,6 +623,8 @@ 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()
|
||||
|
@ -577,6 +634,8 @@ 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()
|
||||
|
@ -595,6 +654,8 @@ 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,6 +2,7 @@
|
|||
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!(
|
||||
|
@ -10,6 +11,8 @@ 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()
|
||||
|
@ -19,6 +22,8 @@ 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()
|
||||
|
@ -28,6 +33,8 @@ 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,
|
||||
|
@ -39,6 +46,8 @@ 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,
|
||||
|
@ -50,6 +59,8 @@ 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,
|
||||
|
@ -71,6 +82,8 @@ 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()
|
||||
|
@ -80,6 +93,8 @@ 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()
|
||||
|
@ -89,6 +104,8 @@ 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()
|
||||
|
@ -98,6 +115,8 @@ 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,
|
||||
|
@ -118,6 +137,8 @@ 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()
|
||||
|
@ -147,6 +168,8 @@ 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()
|
||||
};
|
||||
|
@ -155,6 +178,8 @@ 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()
|
||||
|
@ -164,6 +189,8 @@ 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()
|
||||
|
@ -210,6 +237,8 @@ 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,
|
||||
|
@ -267,6 +296,8 @@ 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()
|
||||
};
|
||||
|
@ -275,6 +306,8 @@ 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,
|
||||
|
@ -315,6 +348,8 @@ 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()
|
||||
|
@ -362,6 +397,8 @@ 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,
|
||||
|
@ -372,6 +409,8 @@ 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()
|
||||
|
@ -381,6 +420,8 @@ pub static ref SCHEMA_ATTR_OAUTH2_RS_SUP_SCOPE_MAP: SchemaAttribute = SchemaAttr
|
|||
uuid: UUID_SCHEMA_ATTR_OAUTH2_RS_SUP_SCOPE_MAP,
|
||||
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()
|
||||
|
@ -418,6 +459,8 @@ 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()
|
||||
|
@ -464,6 +507,8 @@ 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()
|
||||
|
@ -501,6 +546,8 @@ 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()
|
||||
|
@ -510,6 +557,8 @@ 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,
|
||||
|
@ -520,6 +569,8 @@ 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,
|
||||
|
@ -548,6 +599,8 @@ 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,
|
||||
|
@ -558,6 +611,8 @@ 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,
|
||||
|
@ -568,6 +623,8 @@ 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()
|
||||
|
@ -577,6 +634,8 @@ 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()
|
||||
|
@ -595,6 +654,8 @@ 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,6 +695,7 @@ 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")),
|
||||
(
|
||||
|
@ -725,6 +726,7 @@ 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 parsed filter for
|
||||
// Insert it to the dyngroup cache with the compiled/resolved 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,11 +175,6 @@ 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| {
|
||||
|
@ -207,7 +202,9 @@ impl DynGroup {
|
|||
let dg_filter_valid = dg_filter
|
||||
.validate(qs.get_schema())
|
||||
.map_err(OperationError::SchemaViolation)
|
||||
.and_then(|f| f.resolve(&ident_internal, None, qs.get_resolve_filter_cache()))?;
|
||||
.and_then(|f| {
|
||||
f.resolve(&ident_internal, None, Some(qs.get_resolve_filter_cache()))
|
||||
})?;
|
||||
|
||||
// Did any of our modified entries match our dyn group filter?
|
||||
let matches: Vec<_> = entries
|
||||
|
@ -294,11 +291,6 @@ 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.
|
||||
|
@ -346,7 +338,9 @@ impl DynGroup {
|
|||
let dg_filter_valid = dg_filter
|
||||
.validate(qs.get_schema())
|
||||
.map_err(OperationError::SchemaViolation)
|
||||
.and_then(|f| f.resolve(&ident_internal, None, qs.get_resolve_filter_cache()))?;
|
||||
.and_then(|f| {
|
||||
f.resolve(&ident_internal, None, Some(qs.get_resolve_filter_cache()))
|
||||
})?;
|
||||
|
||||
let matches: Vec<_> = pre_entries
|
||||
.iter()
|
||||
|
|
|
@ -22,6 +22,7 @@ mod jwskeygen;
|
|||
mod keyobject;
|
||||
mod memberof;
|
||||
mod namehistory;
|
||||
mod protected;
|
||||
mod refint;
|
||||
mod session;
|
||||
mod spn;
|
||||
|
@ -43,7 +44,6 @@ 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 = "trace", name = "plugins::run_pre_create", skip_all)]
|
||||
#[instrument(level = "debug", 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> {
|
||||
Ok(())
|
||||
protected::Protected::pre_create(qs, cand, ce)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "plugins::run_post_create", skip_all)]
|
||||
|
@ -269,6 +269,7 @@ 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)?;
|
||||
|
@ -304,6 +305,7 @@ 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)?;
|
||||
|
@ -338,6 +340,7 @@ 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)
|
||||
}
|
||||
|
||||
|
|
690
server/lib/src/plugins/protected.rs
Normal file
690
server/lib/src/plugins/protected.rs
Normal file
|
@ -0,0 +1,690 @@
|
|||
// System protected objects. Items matching specific requirements
|
||||
// may only have certain modifications performed.
|
||||
|
||||
use hashbrown::HashSet;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::event::{CreateEvent, DeleteEvent, ModifyEvent};
|
||||
use crate::modify::Modify;
|
||||
use crate::plugins::Plugin;
|
||||
use crate::prelude::*;
|
||||
|
||||
pub struct Protected {}
|
||||
|
||||
// Here is the declaration of all the attrs that can be altered by
|
||||
// a call on a system object. We trust they are allowed because
|
||||
// schema will have checked this, and we don't allow class changes!
|
||||
|
||||
lazy_static! {
|
||||
static ref ALLOWED_ATTRS: HashSet<Attribute> = {
|
||||
let attrs = vec![
|
||||
// Allow modification of some schema class types to allow local extension
|
||||
// of schema types.
|
||||
Attribute::Must,
|
||||
Attribute::May,
|
||||
// modification of some domain info types for local configuratiomn.
|
||||
Attribute::DomainSsid,
|
||||
Attribute::DomainLdapBasedn,
|
||||
Attribute::LdapMaxQueryableAttrs,
|
||||
Attribute::LdapAllowUnixPwBind,
|
||||
Attribute::FernetPrivateKeyStr,
|
||||
Attribute::Es256PrivateKeyDer,
|
||||
Attribute::KeyActionRevoke,
|
||||
Attribute::KeyActionRotate,
|
||||
Attribute::IdVerificationEcKey,
|
||||
Attribute::BadlistPassword,
|
||||
Attribute::DeniedName,
|
||||
Attribute::DomainDisplayName,
|
||||
Attribute::Image,
|
||||
// modification of account policy values for dyngroup.
|
||||
Attribute::AuthSessionExpiry,
|
||||
Attribute::AuthPasswordMinimumLength,
|
||||
Attribute::CredentialTypeMinimum,
|
||||
Attribute::PrivilegeExpiry,
|
||||
Attribute::WebauthnAttestationCaList,
|
||||
Attribute::LimitSearchMaxResults,
|
||||
Attribute::LimitSearchMaxFilterTest,
|
||||
Attribute::AllowPrimaryCredFallback,
|
||||
];
|
||||
|
||||
let mut m = HashSet::with_capacity(attrs.len());
|
||||
m.extend(attrs);
|
||||
|
||||
m
|
||||
};
|
||||
|
||||
static ref PROTECTED_ENTRYCLASSES: Vec<EntryClass> =
|
||||
vec![
|
||||
EntryClass::System,
|
||||
EntryClass::DomainInfo,
|
||||
EntryClass::SystemInfo,
|
||||
EntryClass::SystemConfig,
|
||||
EntryClass::DynGroup,
|
||||
EntryClass::SyncObject,
|
||||
EntryClass::Tombstone,
|
||||
EntryClass::Recycled,
|
||||
];
|
||||
}
|
||||
|
||||
impl Plugin for Protected {
|
||||
fn id() -> &'static str {
|
||||
"plugin_protected"
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "protected_pre_create", skip_all)]
|
||||
fn pre_create(
|
||||
_qs: &mut QueryServerWriteTransaction,
|
||||
// List of what we will commit that is valid?
|
||||
cand: &[Entry<EntrySealed, EntryNew>],
|
||||
ce: &CreateEvent,
|
||||
) -> Result<(), OperationError> {
|
||||
if ce.ident.is_internal() {
|
||||
trace!("Internal operation, not enforcing system object protection");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
cand.iter().try_fold((), |(), cand| {
|
||||
if PROTECTED_ENTRYCLASSES
|
||||
.iter()
|
||||
.any(|c| cand.attribute_equality(Attribute::Class, &c.to_partialvalue()))
|
||||
{
|
||||
trace!("Rejecting operation during pre_create check");
|
||||
Err(OperationError::SystemProtectedObject)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "protected_pre_modify", skip_all)]
|
||||
fn pre_modify(
|
||||
_qs: &mut QueryServerWriteTransaction,
|
||||
_pre_cand: &[Arc<EntrySealedCommitted>],
|
||||
cand: &mut Vec<EntryInvalidCommitted>,
|
||||
me: &ModifyEvent,
|
||||
) -> Result<(), OperationError> {
|
||||
if me.ident.is_internal() {
|
||||
trace!("Internal operation, not enforcing system object protection");
|
||||
return Ok(());
|
||||
}
|
||||
// Prevent adding class: system, domain_info, tombstone, or recycled.
|
||||
me.modlist.iter().try_fold((), |(), m| match m {
|
||||
Modify::Present(a, v) => {
|
||||
if a == Attribute::Class.as_ref()
|
||||
&& PROTECTED_ENTRYCLASSES.iter().any(|c| v == &c.to_value())
|
||||
{
|
||||
trace!("Rejecting operation during pre_modify check");
|
||||
Err(OperationError::SystemProtectedObject)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
_ => Ok(()),
|
||||
})?;
|
||||
|
||||
// HARD block mods on tombstone or recycle. We soft block on the rest as they may
|
||||
// have some allowed attrs.
|
||||
cand.iter().try_fold((), |(), cand| {
|
||||
if cand.attribute_equality(Attribute::Class, &EntryClass::Tombstone.into())
|
||||
|| cand.attribute_equality(Attribute::Class, &EntryClass::Recycled.into())
|
||||
{
|
||||
Err(OperationError::SystemProtectedObject)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})?;
|
||||
|
||||
// if class: system, check the mods are "allowed"
|
||||
let system_pres = cand.iter().any(|c| {
|
||||
// We don't need to check for domain info here because domain_info has a class
|
||||
// system also. We just need to block it from being created.
|
||||
c.attribute_equality(Attribute::Class, &EntryClass::System.into())
|
||||
});
|
||||
|
||||
trace!("class: system -> {}", system_pres);
|
||||
// No system types being altered, return.
|
||||
if !system_pres {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Something altered is system, check if it's allowed.
|
||||
me.modlist.into_iter().try_fold((), |(), m| {
|
||||
// Already hit an error, move on.
|
||||
let a = match m {
|
||||
Modify::Present(a, _)
|
||||
| Modify::Removed(a, _)
|
||||
| Modify::Set(a, _)
|
||||
| Modify::Purged(a) => Some(a),
|
||||
Modify::Assert(_, _) => None,
|
||||
};
|
||||
if let Some(attr) = a {
|
||||
match ALLOWED_ATTRS.contains(attr) {
|
||||
true => Ok(()),
|
||||
false => {
|
||||
trace!("If you're getting this, you need to modify the ALLOWED_ATTRS list");
|
||||
Err(OperationError::SystemProtectedObject)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Was not a mod needing checking
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "protected_pre_batch_modify", skip_all)]
|
||||
fn pre_batch_modify(
|
||||
_qs: &mut QueryServerWriteTransaction,
|
||||
_pre_cand: &[Arc<EntrySealedCommitted>],
|
||||
cand: &mut Vec<EntryInvalidCommitted>,
|
||||
me: &BatchModifyEvent,
|
||||
) -> Result<(), OperationError> {
|
||||
if me.ident.is_internal() {
|
||||
trace!("Internal operation, not enforcing system object protection");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
me.modset
|
||||
.values()
|
||||
.flat_map(|ml| ml.iter())
|
||||
.try_fold((), |(), m| match m {
|
||||
Modify::Present(a, v) => {
|
||||
if a == Attribute::Class.as_ref()
|
||||
&& PROTECTED_ENTRYCLASSES.iter().any(|c| v == &c.to_value())
|
||||
{
|
||||
trace!("Rejecting operation during pre_batch_modify check");
|
||||
Err(OperationError::SystemProtectedObject)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
_ => Ok(()),
|
||||
})?;
|
||||
|
||||
// HARD block mods on tombstone or recycle. We soft block on the rest as they may
|
||||
// have some allowed attrs.
|
||||
cand.iter().try_fold((), |(), cand| {
|
||||
if cand.attribute_equality(Attribute::Class, &EntryClass::Tombstone.into())
|
||||
|| cand.attribute_equality(Attribute::Class, &EntryClass::Recycled.into())
|
||||
{
|
||||
Err(OperationError::SystemProtectedObject)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})?;
|
||||
|
||||
// if class: system, check the mods are "allowed"
|
||||
let system_pres = cand.iter().any(|c| {
|
||||
// We don't need to check for domain info here because domain_info has a class
|
||||
// system also. We just need to block it from being created.
|
||||
c.attribute_equality(Attribute::Class, &EntryClass::System.into())
|
||||
});
|
||||
|
||||
trace!("{}: system -> {}", Attribute::Class, system_pres);
|
||||
// No system types being altered, return.
|
||||
if !system_pres {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Something altered is system, check if it's allowed.
|
||||
me.modset
|
||||
.values()
|
||||
.flat_map(|ml| ml.iter())
|
||||
.try_fold((), |(), m| {
|
||||
// Already hit an error, move on.
|
||||
let a = match m {
|
||||
Modify::Present(a, _) | Modify::Removed(a, _) | Modify::Set(a, _) | Modify::Purged(a) => Some(a),
|
||||
Modify::Assert(_, _) => None,
|
||||
};
|
||||
if let Some(attr) = a {
|
||||
match ALLOWED_ATTRS.contains(attr) {
|
||||
true => Ok(()),
|
||||
false => {
|
||||
|
||||
trace!("Rejecting operation during pre_batch_modify check, if you're getting this check ALLOWED_ATTRS");
|
||||
Err(OperationError::SystemProtectedObject)
|
||||
},
|
||||
}
|
||||
} else {
|
||||
// Was not a mod needing checking
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "protected_pre_delete", skip_all)]
|
||||
fn pre_delete(
|
||||
_qs: &mut QueryServerWriteTransaction,
|
||||
// Should these be EntrySealed
|
||||
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
|
||||
de: &DeleteEvent,
|
||||
) -> Result<(), OperationError> {
|
||||
if de.ident.is_internal() {
|
||||
trace!("Internal operation, not enforcing system object protection");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
cand.iter().try_fold((), |(), cand| {
|
||||
if PROTECTED_ENTRYCLASSES
|
||||
.iter()
|
||||
.any(|c| cand.attribute_equality(Attribute::Class, &c.to_partialvalue()))
|
||||
{
|
||||
trace!("Rejecting operation during pre_delete check");
|
||||
Err(OperationError::SystemProtectedObject)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
const UUID_TEST_ACCOUNT: Uuid = uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
|
||||
const UUID_TEST_GROUP: Uuid = uuid::uuid!("81ec1640-3637-4a2f-8a52-874fa3c3c92f");
|
||||
const UUID_TEST_ACP: Uuid = uuid::uuid!("acae81d6-5ea7-4bd8-8f7f-fcec4c0dd647");
|
||||
|
||||
lazy_static! {
|
||||
pub static ref TEST_ACCOUNT: EntryInitNew = entry_init!(
|
||||
(Attribute::Class, EntryClass::Account.to_value()),
|
||||
(Attribute::Class, EntryClass::ServiceAccount.to_value()),
|
||||
(Attribute::Class, EntryClass::MemberOf.to_value()),
|
||||
(Attribute::Name, Value::new_iname("test_account_1")),
|
||||
(Attribute::DisplayName, Value::new_utf8s("test_account_1")),
|
||||
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACCOUNT)),
|
||||
(Attribute::MemberOf, Value::Refer(UUID_TEST_GROUP))
|
||||
);
|
||||
pub static ref TEST_GROUP: EntryInitNew = entry_init!(
|
||||
(Attribute::Class, EntryClass::Group.to_value()),
|
||||
(Attribute::Name, Value::new_iname("test_group_a")),
|
||||
(Attribute::Uuid, Value::Uuid(UUID_TEST_GROUP)),
|
||||
(Attribute::Member, Value::Refer(UUID_TEST_ACCOUNT))
|
||||
);
|
||||
pub static ref ALLOW_ALL: EntryInitNew = entry_init!(
|
||||
(Attribute::Class, EntryClass::Object.to_value()),
|
||||
(
|
||||
Attribute::Class,
|
||||
EntryClass::AccessControlProfile.to_value()
|
||||
),
|
||||
(
|
||||
Attribute::Class,
|
||||
EntryClass::AccessControlTargetScope.to_value()
|
||||
),
|
||||
(
|
||||
Attribute::Class,
|
||||
EntryClass::AccessControlReceiverGroup.to_value()
|
||||
),
|
||||
(Attribute::Class, EntryClass::AccessControlModify.to_value()),
|
||||
(Attribute::Class, EntryClass::AccessControlCreate.to_value()),
|
||||
(Attribute::Class, EntryClass::AccessControlDelete.to_value()),
|
||||
(Attribute::Class, EntryClass::AccessControlSearch.to_value()),
|
||||
(
|
||||
Attribute::Name,
|
||||
Value::new_iname("idm_admins_acp_allow_all_test")
|
||||
),
|
||||
(Attribute::Uuid, Value::Uuid(UUID_TEST_ACP)),
|
||||
(Attribute::AcpReceiverGroup, Value::Refer(UUID_TEST_GROUP)),
|
||||
(
|
||||
Attribute::AcpTargetScope,
|
||||
Value::new_json_filter_s("{\"pres\":\"class\"}").expect("filter")
|
||||
),
|
||||
(Attribute::AcpSearchAttr, Value::from(Attribute::Name)),
|
||||
(Attribute::AcpSearchAttr, Value::from(Attribute::Class)),
|
||||
(Attribute::AcpSearchAttr, Value::from(Attribute::Uuid)),
|
||||
(Attribute::AcpSearchAttr, Value::new_iutf8("classname")),
|
||||
(
|
||||
Attribute::AcpSearchAttr,
|
||||
Value::new_iutf8(Attribute::AttributeName.as_ref())
|
||||
),
|
||||
(Attribute::AcpModifyClass, EntryClass::System.to_value()),
|
||||
(Attribute::AcpModifyClass, Value::new_iutf8("domain_info")),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Value::from(Attribute::Class)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Value::from(Attribute::DisplayName)
|
||||
),
|
||||
(Attribute::AcpModifyRemovedAttr, Value::from(Attribute::May)),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Value::from(Attribute::Must)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Value::from(Attribute::DomainName)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Value::from(Attribute::DomainDisplayName)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Value::from(Attribute::DomainUuid)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Value::from(Attribute::DomainSsid)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Value::from(Attribute::FernetPrivateKeyStr)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Value::from(Attribute::Es256PrivateKeyDer)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyRemovedAttr,
|
||||
Value::from(Attribute::PrivateCookieKey)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Value::from(Attribute::Class)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Value::from(Attribute::DisplayName)
|
||||
),
|
||||
(Attribute::AcpModifyPresentAttr, Value::from(Attribute::May)),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Value::from(Attribute::Must)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Value::from(Attribute::DomainName)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Value::from(Attribute::DomainDisplayName)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Value::from(Attribute::DomainUuid)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Value::from(Attribute::DomainSsid)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Value::from(Attribute::FernetPrivateKeyStr)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Value::from(Attribute::Es256PrivateKeyDer)
|
||||
),
|
||||
(
|
||||
Attribute::AcpModifyPresentAttr,
|
||||
Value::from(Attribute::PrivateCookieKey)
|
||||
),
|
||||
(Attribute::AcpCreateClass, EntryClass::Object.to_value()),
|
||||
(Attribute::AcpCreateClass, EntryClass::Account.to_value()),
|
||||
(Attribute::AcpCreateClass, EntryClass::Person.to_value()),
|
||||
(Attribute::AcpCreateClass, EntryClass::System.to_value()),
|
||||
(Attribute::AcpCreateClass, EntryClass::DomainInfo.to_value()),
|
||||
(Attribute::AcpCreateAttr, Value::from(Attribute::Name)),
|
||||
(Attribute::AcpCreateAttr, EntryClass::Class.to_value(),),
|
||||
(
|
||||
Attribute::AcpCreateAttr,
|
||||
Value::from(Attribute::Description),
|
||||
),
|
||||
(
|
||||
Attribute::AcpCreateAttr,
|
||||
Value::from(Attribute::DisplayName),
|
||||
),
|
||||
(Attribute::AcpCreateAttr, Value::from(Attribute::DomainName),),
|
||||
(
|
||||
Attribute::AcpCreateAttr,
|
||||
Value::from(Attribute::DomainDisplayName)
|
||||
),
|
||||
(Attribute::AcpCreateAttr, Value::from(Attribute::DomainUuid)),
|
||||
(Attribute::AcpCreateAttr, Value::from(Attribute::DomainSsid)),
|
||||
(Attribute::AcpCreateAttr, Value::from(Attribute::Uuid)),
|
||||
(
|
||||
Attribute::AcpCreateAttr,
|
||||
Value::from(Attribute::FernetPrivateKeyStr)
|
||||
),
|
||||
(
|
||||
Attribute::AcpCreateAttr,
|
||||
Value::from(Attribute::Es256PrivateKeyDer)
|
||||
),
|
||||
(
|
||||
Attribute::AcpCreateAttr,
|
||||
Value::from(Attribute::PrivateCookieKey)
|
||||
),
|
||||
(Attribute::AcpCreateAttr, Value::from(Attribute::Version))
|
||||
);
|
||||
pub static ref PRELOAD: Vec<EntryInitNew> =
|
||||
vec![TEST_ACCOUNT.clone(), TEST_GROUP.clone(), ALLOW_ALL.clone()];
|
||||
pub static ref E_TEST_ACCOUNT: Arc<EntrySealedCommitted> =
|
||||
Arc::new(TEST_ACCOUNT.clone().into_sealed_committed());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pre_create_deny() {
|
||||
// Test creating with class: system is rejected.
|
||||
let e = entry_init!(
|
||||
(Attribute::Class, EntryClass::Account.to_value()),
|
||||
(Attribute::Class, EntryClass::Person.to_value()),
|
||||
(Attribute::Class, EntryClass::System.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testperson")),
|
||||
(
|
||||
Attribute::DisplayName,
|
||||
Value::Utf8("testperson".to_string())
|
||||
)
|
||||
);
|
||||
|
||||
let create = vec![e];
|
||||
let preload = PRELOAD.clone();
|
||||
|
||||
run_create_test!(
|
||||
Err(OperationError::SystemProtectedObject),
|
||||
preload,
|
||||
create,
|
||||
Some(E_TEST_ACCOUNT.clone()),
|
||||
|_| {}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pre_modify_system_deny() {
|
||||
// Test modify of class to a system is denied
|
||||
let e = entry_init!(
|
||||
(Attribute::Class, EntryClass::Account.to_value()),
|
||||
(Attribute::Class, EntryClass::Person.to_value()),
|
||||
(Attribute::Class, EntryClass::System.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testperson")),
|
||||
(
|
||||
Attribute::DisplayName,
|
||||
Value::Utf8("testperson".to_string())
|
||||
)
|
||||
);
|
||||
|
||||
let mut preload = PRELOAD.clone();
|
||||
preload.push(e);
|
||||
|
||||
run_modify_test!(
|
||||
Err(OperationError::SystemProtectedObject),
|
||||
preload,
|
||||
filter!(f_eq(Attribute::Name, PartialValue::new_iname("testperson"))),
|
||||
modlist!([
|
||||
m_purge(Attribute::DisplayName),
|
||||
m_pres(Attribute::DisplayName, &Value::new_utf8s("system test")),
|
||||
]),
|
||||
Some(E_TEST_ACCOUNT.clone()),
|
||||
|_| {},
|
||||
|_| {}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pre_modify_class_add_deny() {
|
||||
// Show that adding a system class is denied
|
||||
// TODO: replace this with a `SchemaClass` object
|
||||
let e = entry_init!(
|
||||
(Attribute::Class, EntryClass::Object.to_value()),
|
||||
(Attribute::Class, EntryClass::ClassType.to_value()),
|
||||
(Attribute::ClassName, Value::new_iutf8("testclass")),
|
||||
(
|
||||
Attribute::Uuid,
|
||||
Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
|
||||
),
|
||||
(
|
||||
Attribute::Description,
|
||||
Value::Utf8("class test".to_string())
|
||||
)
|
||||
);
|
||||
let mut preload = PRELOAD.clone();
|
||||
preload.push(e);
|
||||
|
||||
run_modify_test!(
|
||||
Ok(()),
|
||||
preload,
|
||||
filter!(f_eq(
|
||||
Attribute::ClassName,
|
||||
PartialValue::new_iutf8("testclass")
|
||||
)),
|
||||
modlist!([
|
||||
m_pres(Attribute::May, &Value::from(Attribute::Name)),
|
||||
m_pres(Attribute::Must, &Value::from(Attribute::Name)),
|
||||
]),
|
||||
Some(E_TEST_ACCOUNT.clone()),
|
||||
|_| {},
|
||||
|_| {}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pre_delete_deny() {
|
||||
// Test deleting with class: system is rejected.
|
||||
let e = entry_init!(
|
||||
(Attribute::Class, EntryClass::Account.to_value()),
|
||||
(Attribute::Class, EntryClass::Person.to_value()),
|
||||
(Attribute::Class, EntryClass::System.to_value()),
|
||||
(Attribute::Name, Value::new_iname("testperson")),
|
||||
(
|
||||
Attribute::DisplayName,
|
||||
Value::Utf8("testperson".to_string())
|
||||
)
|
||||
);
|
||||
|
||||
let mut preload = PRELOAD.clone();
|
||||
preload.push(e);
|
||||
|
||||
run_delete_test!(
|
||||
Err(OperationError::SystemProtectedObject),
|
||||
preload,
|
||||
filter!(f_eq(Attribute::Name, PartialValue::new_iname("testperson"))),
|
||||
Some(E_TEST_ACCOUNT.clone()),
|
||||
|_| {}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_modify_domain() {
|
||||
// Can edit *my* domain_ssid and domain_name
|
||||
// Show that adding a system class is denied
|
||||
let e = entry_init!(
|
||||
(Attribute::Class, EntryClass::DomainInfo.to_value()),
|
||||
(Attribute::Name, Value::new_iname("domain_example.net.au")),
|
||||
(Attribute::Uuid, Value::Uuid(uuid::uuid!("96fd1112-28bc-48ae-9dda-5acb4719aaba"))),
|
||||
(
|
||||
Attribute::Description,
|
||||
Value::new_utf8s("Demonstration of a remote domain's info being created for uuid generation in test_modify_domain")
|
||||
),
|
||||
(Attribute::DomainUuid, Value::Uuid(uuid::uuid!("96fd1112-28bc-48ae-9dda-5acb4719aaba"))),
|
||||
(Attribute::DomainName, Value::new_iname("example.net.au")),
|
||||
(Attribute::DomainDisplayName, Value::Utf8("example.net.au".to_string())),
|
||||
(Attribute::DomainSsid, Value::Utf8("Example_Wifi".to_string())),
|
||||
(Attribute::Version, Value::Uint32(1))
|
||||
);
|
||||
|
||||
let mut preload = PRELOAD.clone();
|
||||
preload.push(e);
|
||||
|
||||
run_modify_test!(
|
||||
Ok(()),
|
||||
preload,
|
||||
filter!(f_eq(
|
||||
Attribute::Name,
|
||||
PartialValue::new_iname("domain_example.net.au")
|
||||
)),
|
||||
modlist!([
|
||||
m_purge(Attribute::DomainSsid),
|
||||
m_pres(Attribute::DomainSsid, &Value::new_utf8s("NewExampleWifi")),
|
||||
]),
|
||||
Some(E_TEST_ACCOUNT.clone()),
|
||||
|_| {},
|
||||
|_| {}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ext_create_domain() {
|
||||
// can not add a domain_info type - note the lack of class: system
|
||||
let e = entry_init!(
|
||||
(Attribute::Class, EntryClass::DomainInfo.to_value()),
|
||||
(Attribute::Name, Value::new_iname("domain_example.net.au")),
|
||||
(Attribute::Uuid, Value::Uuid(uuid::uuid!("96fd1112-28bc-48ae-9dda-5acb4719aaba"))),
|
||||
(
|
||||
Attribute::Description,
|
||||
Value::new_utf8s("Demonstration of a remote domain's info being created for uuid generation in test_modify_domain")
|
||||
),
|
||||
(Attribute::DomainUuid, Value::Uuid(uuid::uuid!("96fd1112-28bc-48ae-9dda-5acb4719aaba"))),
|
||||
(Attribute::DomainName, Value::new_iname("example.net.au")),
|
||||
(Attribute::DomainDisplayName, Value::Utf8("example.net.au".to_string())),
|
||||
(Attribute::DomainSsid, Value::Utf8("Example_Wifi".to_string())),
|
||||
(Attribute::Version, Value::Uint32(1))
|
||||
);
|
||||
|
||||
let create = vec![e];
|
||||
let preload = PRELOAD.clone();
|
||||
|
||||
run_create_test!(
|
||||
Err(OperationError::SystemProtectedObject),
|
||||
preload,
|
||||
create,
|
||||
Some(E_TEST_ACCOUNT.clone()),
|
||||
|_| {}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_domain() {
|
||||
// On the real thing we have a class: system, but to prove the point ...
|
||||
let e = entry_init!(
|
||||
(Attribute::Class, EntryClass::DomainInfo.to_value()),
|
||||
(Attribute::Name, Value::new_iname("domain_example.net.au")),
|
||||
(Attribute::Uuid, Value::Uuid(uuid::uuid!("96fd1112-28bc-48ae-9dda-5acb4719aaba"))),
|
||||
(
|
||||
Attribute::Description,
|
||||
Value::new_utf8s("Demonstration of a remote domain's info being created for uuid generation in test_modify_domain")
|
||||
),
|
||||
(Attribute::DomainUuid, Value::Uuid(uuid::uuid!("96fd1112-28bc-48ae-9dda-5acb4719aaba"))),
|
||||
(Attribute::DomainName, Value::new_iname("example.net.au")),
|
||||
(Attribute::DomainDisplayName, Value::Utf8("example.net.au".to_string())),
|
||||
(Attribute::DomainSsid, Value::Utf8("Example_Wifi".to_string())),
|
||||
(Attribute::Version, Value::Uint32(1))
|
||||
);
|
||||
|
||||
let mut preload = PRELOAD.clone();
|
||||
preload.push(e);
|
||||
|
||||
run_delete_test!(
|
||||
Err(OperationError::SystemProtectedObject),
|
||||
preload,
|
||||
filter!(f_eq(
|
||||
Attribute::Name,
|
||||
PartialValue::new_iname("domain_example.net.au")
|
||||
)),
|
||||
Some(E_TEST_ACCOUNT.clone()),
|
||||
|_| {}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
use super::proto::*;
|
||||
use crate::plugins::Plugins;
|
||||
use crate::prelude::*;
|
||||
use crate::server::{ChangeFlag, ServerPhase};
|
||||
use crate::server::ChangeFlag;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::sync::Arc;
|
||||
|
||||
|
@ -343,7 +343,7 @@ impl QueryServerWriteTransaction<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "info", skip_all)]
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
fn consumer_apply_changes_v1(
|
||||
&mut self,
|
||||
ctx_domain_version: DomainVersion,
|
||||
|
@ -548,7 +548,7 @@ impl QueryServerWriteTransaction<'_> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "info", skip_all)]
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
fn consumer_apply_refresh_v1(
|
||||
&mut self,
|
||||
ctx_domain_version: DomainVersion,
|
||||
|
@ -583,7 +583,6 @@ 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
|
||||
|
@ -598,6 +597,7 @@ 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,12 +609,6 @@ 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)
|
||||
|
@ -627,9 +621,6 @@ 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| {
|
||||
|
@ -661,10 +652,7 @@ impl QueryServerWriteTransaction<'_> {
|
|||
| ChangeFlag::KEY_MATERIAL,
|
||||
);
|
||||
|
||||
// Domain info is now ready.
|
||||
self.set_phase(ServerPhase::DomainInfoReady);
|
||||
|
||||
// ==== That's it! We are GOOD to go! ====
|
||||
// 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)
|
||||
|
@ -684,9 +672,6 @@ 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,17 +1,16 @@
|
|||
use super::profiles::{
|
||||
AccessControlCreateResolved, AccessControlReceiverCondition, AccessControlTargetCondition,
|
||||
};
|
||||
use super::protected::PROTECTED_ENTRY_CLASSES;
|
||||
use crate::prelude::*;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
pub(super) enum CreateResult {
|
||||
Deny,
|
||||
Denied,
|
||||
Grant,
|
||||
}
|
||||
|
||||
enum IResult {
|
||||
Deny,
|
||||
Denied,
|
||||
Grant,
|
||||
Ignore,
|
||||
}
|
||||
|
@ -26,25 +25,25 @@ pub(super) fn apply_create_access<'a>(
|
|||
|
||||
// This module can never yield a grant.
|
||||
match protected_filter_entry(ident, entry) {
|
||||
IResult::Deny => denied = true,
|
||||
IResult::Denied => denied = true,
|
||||
IResult::Grant | IResult::Ignore => {}
|
||||
}
|
||||
|
||||
match create_filter_entry(ident, related_acp, entry) {
|
||||
IResult::Deny => denied = true,
|
||||
IResult::Denied => denied = true,
|
||||
IResult::Grant => grant = true,
|
||||
IResult::Ignore => {}
|
||||
}
|
||||
|
||||
if denied {
|
||||
// Something explicitly said no.
|
||||
CreateResult::Deny
|
||||
CreateResult::Denied
|
||||
} else if grant {
|
||||
// Something said yes
|
||||
CreateResult::Grant
|
||||
} else {
|
||||
// Nothing said yes.
|
||||
CreateResult::Deny
|
||||
CreateResult::Denied
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,7 +60,7 @@ fn create_filter_entry<'a>(
|
|||
}
|
||||
IdentType::Synch(_) => {
|
||||
security_critical!("Blocking sync check");
|
||||
return IResult::Deny;
|
||||
return IResult::Denied;
|
||||
}
|
||||
IdentType::User(_) => {}
|
||||
};
|
||||
|
@ -70,7 +69,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::Deny;
|
||||
return IResult::Denied;
|
||||
}
|
||||
AccessScope::ReadWrite => {
|
||||
// As you were
|
||||
|
@ -97,7 +96,7 @@ fn create_filter_entry<'a>(
|
|||
Some(s) => s.collect(),
|
||||
None => {
|
||||
admin_error!("Class set failed to build - corrupted entry?");
|
||||
return IResult::Deny;
|
||||
return IResult::Denied;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -174,22 +173,22 @@ fn protected_filter_entry(ident: &Identity, entry: &Entry<EntryInit, EntryNew>)
|
|||
}
|
||||
IdentType::Synch(_) => {
|
||||
security_access!("sync agreements may not directly create entities");
|
||||
IResult::Deny
|
||||
IResult::Denied
|
||||
}
|
||||
IdentType::User(_) => {
|
||||
// Now check things ...
|
||||
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
|
||||
|
||||
// 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 create with protected class type");
|
||||
IResult::Deny
|
||||
IResult::Denied
|
||||
} else {
|
||||
IResult::Ignore
|
||||
}
|
||||
} else {
|
||||
// Nothing to check - this entry will fail to create anyway because it has
|
||||
// no classes
|
||||
// Nothing to check.
|
||||
IResult::Ignore
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
use super::profiles::{
|
||||
AccessControlDeleteResolved, AccessControlReceiverCondition, AccessControlTargetCondition,
|
||||
};
|
||||
use super::protected::PROTECTED_ENTRY_CLASSES;
|
||||
use crate::prelude::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub(super) enum DeleteResult {
|
||||
Deny,
|
||||
Denied,
|
||||
Grant,
|
||||
}
|
||||
|
||||
enum IResult {
|
||||
Deny,
|
||||
Denied,
|
||||
Grant,
|
||||
Ignore,
|
||||
}
|
||||
|
@ -25,25 +24,25 @@ pub(super) fn apply_delete_access<'a>(
|
|||
let mut grant = false;
|
||||
|
||||
match protected_filter_entry(ident, entry) {
|
||||
IResult::Deny => denied = true,
|
||||
IResult::Denied => denied = true,
|
||||
IResult::Grant | IResult::Ignore => {}
|
||||
}
|
||||
|
||||
match delete_filter_entry(ident, related_acp, entry) {
|
||||
IResult::Deny => denied = true,
|
||||
IResult::Denied => denied = true,
|
||||
IResult::Grant => grant = true,
|
||||
IResult::Ignore => {}
|
||||
}
|
||||
|
||||
if denied {
|
||||
// Something explicitly said no.
|
||||
DeleteResult::Deny
|
||||
DeleteResult::Denied
|
||||
} else if grant {
|
||||
// Something said yes
|
||||
DeleteResult::Grant
|
||||
} else {
|
||||
// Nothing said yes.
|
||||
DeleteResult::Deny
|
||||
DeleteResult::Denied
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,7 +59,7 @@ fn delete_filter_entry<'a>(
|
|||
}
|
||||
IdentType::Synch(_) => {
|
||||
security_critical!("Blocking sync check");
|
||||
return IResult::Deny;
|
||||
return IResult::Denied;
|
||||
}
|
||||
IdentType::User(_) => {}
|
||||
};
|
||||
|
@ -69,7 +68,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::Deny;
|
||||
return IResult::Denied;
|
||||
}
|
||||
AccessScope::ReadWrite => {
|
||||
// As you were
|
||||
|
@ -153,30 +152,28 @@ fn protected_filter_entry(ident: &Identity, entry: &Arc<EntrySealedCommitted>) -
|
|||
}
|
||||
IdentType::Synch(_) => {
|
||||
security_access!("sync agreements may not directly delete entities");
|
||||
IResult::Deny
|
||||
IResult::Denied
|
||||
}
|
||||
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::Deny;
|
||||
return IResult::Denied;
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
// Checks exhausted, no more input from us
|
||||
IResult::Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,13 +50,12 @@ mod create;
|
|||
mod delete;
|
||||
mod modify;
|
||||
pub mod profiles;
|
||||
mod protected;
|
||||
mod search;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Access {
|
||||
Grant,
|
||||
Deny,
|
||||
Denied,
|
||||
Allow(BTreeSet<Attribute>),
|
||||
}
|
||||
|
||||
|
@ -64,7 +63,7 @@ impl From<&Access> for ScimAttributeEffectiveAccess {
|
|||
fn from(value: &Access) -> Self {
|
||||
match value {
|
||||
Access::Grant => Self::Grant,
|
||||
Access::Deny => Self::Deny,
|
||||
Access::Denied => Self::Denied,
|
||||
Access::Allow(set) => Self::Allow(set.clone()),
|
||||
}
|
||||
}
|
||||
|
@ -73,7 +72,7 @@ impl From<&Access> for ScimAttributeEffectiveAccess {
|
|||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum AccessClass {
|
||||
Grant,
|
||||
Deny,
|
||||
Denied,
|
||||
Allow(BTreeSet<AttrString>),
|
||||
}
|
||||
|
||||
|
@ -87,22 +86,12 @@ pub struct AccessEffectivePermission {
|
|||
pub search: Access,
|
||||
pub modify_pres: Access,
|
||||
pub modify_rem: Access,
|
||||
pub modify_pres_class: AccessClass,
|
||||
pub modify_rem_class: AccessClass,
|
||||
pub modify_class: AccessClass,
|
||||
}
|
||||
|
||||
pub enum AccessBasicResult {
|
||||
pub enum AccessResult {
|
||||
// Deny this operation unconditionally.
|
||||
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,
|
||||
Denied,
|
||||
// Unbounded allow, provided no deny state exists.
|
||||
Grant,
|
||||
// This module makes no decisions about this entry.
|
||||
|
@ -110,37 +99,24 @@ pub enum AccessSrchResult {
|
|||
// Limit the allowed attr set to this - this doesn't
|
||||
// allow anything, it constrains what might be allowed
|
||||
// by a later module.
|
||||
/*
|
||||
Constrain {
|
||||
attr: BTreeSet<Attribute>,
|
||||
},
|
||||
*/
|
||||
Allow { attr: BTreeSet<Attribute> },
|
||||
Constrain(BTreeSet<Attribute>),
|
||||
// Allow these attributes within constraints.
|
||||
Allow(BTreeSet<Attribute>),
|
||||
}
|
||||
|
||||
pub enum AccessModResult<'a> {
|
||||
#[allow(dead_code)]
|
||||
pub enum AccessResultClass<'a> {
|
||||
// Deny this operation unconditionally.
|
||||
Deny,
|
||||
// Unbounded allow, provided no deny state exists.
|
||||
// Grant,
|
||||
Denied,
|
||||
// Unbounded allow, provided no denied 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
|
||||
// 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>,
|
||||
},
|
||||
// allow anything, it constrains what might be allowed.
|
||||
Constrain(BTreeSet<&'a str>),
|
||||
// Allow these attributes within constraints.
|
||||
Allow(BTreeSet<&'a str>),
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
|
@ -327,7 +303,7 @@ pub trait AccessControlsTransaction<'a> {
|
|||
.into_iter()
|
||||
.filter(|e| {
|
||||
match apply_search_access(ident, related_acp.as_slice(), e) {
|
||||
SearchResult::Deny => false,
|
||||
SearchResult::Denied => false,
|
||||
SearchResult::Grant => true,
|
||||
SearchResult::Allow(allowed_attrs) => {
|
||||
// The allow set constrained.
|
||||
|
@ -425,7 +401,7 @@ pub trait AccessControlsTransaction<'a> {
|
|||
.into_iter()
|
||||
.filter_map(|entry| {
|
||||
match apply_search_access(&se.ident, &search_related_acp, &entry) {
|
||||
SearchResult::Deny => {
|
||||
SearchResult::Denied => {
|
||||
None
|
||||
}
|
||||
SearchResult::Grant => {
|
||||
|
@ -560,8 +536,7 @@ pub trait AccessControlsTransaction<'a> {
|
|||
// Build the set of classes that we to work on, only in terms of "addition". To remove
|
||||
// I think we have no limit, but ... william of the future may find a problem with this
|
||||
// policy.
|
||||
let mut requested_pres_classes: BTreeSet<&str> = Default::default();
|
||||
let mut requested_rem_classes: BTreeSet<&str> = Default::default();
|
||||
let mut requested_classes: BTreeSet<&str> = Default::default();
|
||||
|
||||
for modify in me.modlist.iter() {
|
||||
match modify {
|
||||
|
@ -573,33 +548,27 @@ 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_pres_classes.extend(v.to_str())
|
||||
requested_classes.extend(v.to_str())
|
||||
}
|
||||
}
|
||||
Modify::Removed(a, v) => {
|
||||
if a == Attribute::Class.as_ref() {
|
||||
requested_rem_classes.extend(v.to_str())
|
||||
requested_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());
|
||||
// flatten to remove the option down to an iterator
|
||||
requested_classes.extend(v.as_iutf8_iter().into_iter().flatten())
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
debug!(?requested_pres, "Requested present set");
|
||||
debug!(?requested_rem, "Requested remove set");
|
||||
debug!(?requested_classes, "Requested class set");
|
||||
|
||||
let sync_agmts = self.get_sync_agreements();
|
||||
|
||||
|
@ -607,16 +576,9 @@ 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::Deny => false,
|
||||
ModifyResult::Denied => false,
|
||||
ModifyResult::Grant => true,
|
||||
ModifyResult::Allow {
|
||||
pres,
|
||||
rem,
|
||||
pres_cls,
|
||||
rem_cls,
|
||||
} => {
|
||||
let mut decision = true;
|
||||
|
||||
ModifyResult::Allow { pres, rem, cls } => {
|
||||
if !requested_pres.is_subset(&pres) {
|
||||
security_error!("requested_pres is not a subset of allowed");
|
||||
security_error!(
|
||||
|
@ -624,41 +586,23 @@ pub trait AccessControlsTransaction<'a> {
|
|||
requested_pres,
|
||||
pres
|
||||
);
|
||||
decision = false
|
||||
};
|
||||
|
||||
if !requested_rem.is_subset(&rem) {
|
||||
false
|
||||
} else if !requested_rem.is_subset(&rem) {
|
||||
security_error!("requested_rem is not a subset of allowed");
|
||||
security_error!("requested_rem: {:?} !⊆ allowed: {:?}", requested_rem, rem);
|
||||
decision = false;
|
||||
};
|
||||
|
||||
if !requested_pres_classes.is_subset(&pres_cls) {
|
||||
security_error!("requested_pres_classes is not a subset of allowed");
|
||||
false
|
||||
} else if !requested_classes.is_subset(&cls) {
|
||||
security_error!("requested_classes is not a subset of allowed");
|
||||
security_error!(
|
||||
"requested_pres_classes: {:?} !⊆ allowed: {:?}",
|
||||
requested_pres_classes,
|
||||
pres_cls
|
||||
"requested_classes: {:?} !⊆ allowed: {:?}",
|
||||
requested_classes,
|
||||
cls
|
||||
);
|
||||
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 {
|
||||
false
|
||||
} else {
|
||||
debug!("passed pres, rem, classes check.");
|
||||
}
|
||||
|
||||
// Yield the result
|
||||
decision
|
||||
true
|
||||
} // if acc == false
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -724,55 +668,47 @@ pub trait AccessControlsTransaction<'a> {
|
|||
})
|
||||
.collect();
|
||||
|
||||
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 {
|
||||
// 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 {
|
||||
Modify::Present(a, v) => {
|
||||
if a == Attribute::Class.as_ref() {
|
||||
requested_pres_classes.extend(v.to_str())
|
||||
// Here we have an option<&str> which could mean there is a risk of
|
||||
// a malicious entity attempting to trick us by masking class mods
|
||||
// in non-iutf8 types. However, the server first won't respect their
|
||||
// existence, and second, we would have failed the mod at schema checking
|
||||
// earlier in the process as these were not correctly type. As a result
|
||||
// we can trust these to be correct here and not to be "None".
|
||||
v.to_str()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Modify::Removed(a, v) => {
|
||||
if a == Attribute::Class.as_ref() {
|
||||
requested_rem_classes.extend(v.to_str())
|
||||
v.to_str()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
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_pres_classes, "Requested present class set");
|
||||
debug!(?requested_rem_classes, "Requested remove class set");
|
||||
debug!(?requested_classes, "Requested 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::Deny => false,
|
||||
ModifyResult::Denied => false,
|
||||
ModifyResult::Grant => true,
|
||||
ModifyResult::Allow {
|
||||
pres,
|
||||
rem,
|
||||
pres_cls,
|
||||
rem_cls,
|
||||
} => {
|
||||
let mut decision = true;
|
||||
|
||||
ModifyResult::Allow { pres, rem, cls } => {
|
||||
if !requested_pres.is_subset(&pres) {
|
||||
security_error!("requested_pres is not a subset of allowed");
|
||||
security_error!(
|
||||
|
@ -780,41 +716,23 @@ pub trait AccessControlsTransaction<'a> {
|
|||
requested_pres,
|
||||
pres
|
||||
);
|
||||
decision = false
|
||||
};
|
||||
|
||||
if !requested_rem.is_subset(&rem) {
|
||||
false
|
||||
} else if !requested_rem.is_subset(&rem) {
|
||||
security_error!("requested_rem is not a subset of allowed");
|
||||
security_error!("requested_rem: {:?} !⊆ allowed: {:?}", requested_rem, rem);
|
||||
decision = false;
|
||||
};
|
||||
|
||||
if !requested_pres_classes.is_subset(&pres_cls) {
|
||||
security_error!("requested_pres_classes is not a subset of allowed");
|
||||
false
|
||||
} else if !requested_classes.is_subset(&cls) {
|
||||
security_error!("requested_classes is not a subset of allowed");
|
||||
security_error!(
|
||||
"requested_classes: {:?} !⊆ allowed: {:?}",
|
||||
requested_pres_classes,
|
||||
pres_cls
|
||||
requested_classes,
|
||||
cls
|
||||
);
|
||||
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
|
||||
false
|
||||
} else {
|
||||
security_access!("passed pres, rem, classes check.");
|
||||
true
|
||||
} // if acc == false
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -862,7 +780,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::Deny => false,
|
||||
CreateResult::Denied => false,
|
||||
CreateResult::Grant => true,
|
||||
}
|
||||
});
|
||||
|
@ -918,7 +836,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::Deny => false,
|
||||
DeleteResult::Denied => false,
|
||||
DeleteResult::Grant => true,
|
||||
}
|
||||
});
|
||||
|
@ -1007,7 +925,7 @@ pub trait AccessControlsTransaction<'a> {
|
|||
) -> AccessEffectivePermission {
|
||||
// == search ==
|
||||
let search_effective = match apply_search_access(ident, search_related_acp, entry) {
|
||||
SearchResult::Deny => Access::Deny,
|
||||
SearchResult::Denied => Access::Denied,
|
||||
SearchResult::Grant => Access::Grant,
|
||||
SearchResult::Allow(allowed_attrs) => {
|
||||
// Bound by requested attrs?
|
||||
|
@ -1016,30 +934,14 @@ pub trait AccessControlsTransaction<'a> {
|
|||
};
|
||||
|
||||
// == modify ==
|
||||
let (modify_pres, modify_rem, modify_pres_class, modify_rem_class) =
|
||||
let (modify_pres, modify_rem, modify_class) =
|
||||
match apply_modify_access(ident, modify_related_acp, sync_agmts, entry) {
|
||||
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,
|
||||
} => (
|
||||
ModifyResult::Denied => (Access::Denied, Access::Denied, AccessClass::Denied),
|
||||
ModifyResult::Grant => (Access::Grant, Access::Grant, AccessClass::Grant),
|
||||
ModifyResult::Allow { pres, rem, cls } => (
|
||||
Access::Allow(pres.into_iter().collect()),
|
||||
Access::Allow(rem.into_iter().collect()),
|
||||
AccessClass::Allow(pres_cls.into_iter().map(|s| s.into()).collect()),
|
||||
AccessClass::Allow(rem_cls.into_iter().map(|s| s.into()).collect()),
|
||||
AccessClass::Allow(cls.into_iter().map(|s| s.into()).collect()),
|
||||
),
|
||||
};
|
||||
|
||||
|
@ -1047,7 +949,7 @@ pub trait AccessControlsTransaction<'a> {
|
|||
let delete_status = apply_delete_access(ident, delete_related_acp, entry);
|
||||
|
||||
let delete = match delete_status {
|
||||
DeleteResult::Deny => false,
|
||||
DeleteResult::Denied => false,
|
||||
DeleteResult::Grant => true,
|
||||
};
|
||||
|
||||
|
@ -1058,8 +960,7 @@ pub trait AccessControlsTransaction<'a> {
|
|||
search: search_effective,
|
||||
modify_pres,
|
||||
modify_rem,
|
||||
modify_pres_class,
|
||||
modify_rem_class,
|
||||
modify_class,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2265,8 +2166,6 @@ 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(
|
||||
|
@ -2283,8 +2182,8 @@ mod tests {
|
|||
"member class",
|
||||
// Allow rem name and class
|
||||
"member class",
|
||||
EntryClass::Group.into(),
|
||||
EntryClass::Group.into(),
|
||||
// And the class allowed is account
|
||||
"group",
|
||||
);
|
||||
// Does not have a pres or rem class in attrs
|
||||
let acp_no_class = AccessControlModify::from_raw(
|
||||
|
@ -2302,8 +2201,7 @@ mod tests {
|
|||
// Allow rem name and class
|
||||
"name class",
|
||||
// And the class allowed is NOT an account ...
|
||||
EntryClass::Group.into(),
|
||||
EntryClass::Group.into(),
|
||||
"group",
|
||||
);
|
||||
|
||||
// Test allowed pres
|
||||
|
@ -2389,7 +2287,6 @@ 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);
|
||||
|
@ -2717,8 +2614,7 @@ mod tests {
|
|||
search: Access::Allow(btreeset![Attribute::Name]),
|
||||
modify_pres: Access::Allow(BTreeSet::new()),
|
||||
modify_rem: Access::Allow(BTreeSet::new()),
|
||||
modify_pres_class: AccessClass::Allow(BTreeSet::new()),
|
||||
modify_rem_class: AccessClass::Allow(BTreeSet::new()),
|
||||
modify_class: AccessClass::Allow(BTreeSet::new()),
|
||||
}]
|
||||
)
|
||||
}
|
||||
|
@ -2751,7 +2647,6 @@ mod tests {
|
|||
Attribute::Name.as_ref(),
|
||||
Attribute::Name.as_ref(),
|
||||
EntryClass::Object.into(),
|
||||
EntryClass::Object.into(),
|
||||
)],
|
||||
&r_set,
|
||||
vec![AccessEffectivePermission {
|
||||
|
@ -2761,8 +2656,7 @@ mod tests {
|
|||
search: Access::Allow(BTreeSet::new()),
|
||||
modify_pres: Access::Allow(btreeset![Attribute::Name]),
|
||||
modify_rem: Access::Allow(btreeset![Attribute::Name]),
|
||||
modify_pres_class: AccessClass::Allow(btreeset![EntryClass::Object.into()]),
|
||||
modify_rem_class: AccessClass::Allow(btreeset![EntryClass::Object.into()]),
|
||||
modify_class: AccessClass::Allow(btreeset![EntryClass::Object.into()]),
|
||||
}]
|
||||
)
|
||||
}
|
||||
|
@ -2902,7 +2796,6 @@ 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
|
||||
|
@ -3403,7 +3296,6 @@ mod tests {
|
|||
"name class",
|
||||
// And the class allowed is account
|
||||
EntryClass::Account.into(),
|
||||
EntryClass::Account.into(),
|
||||
);
|
||||
|
||||
// Test allowed pres
|
||||
|
@ -3532,185 +3424,4 @@ 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,25 +1,21 @@
|
|||
use crate::prelude::*;
|
||||
use hashbrown::HashMap;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use super::profiles::{
|
||||
AccessControlModify, AccessControlModifyResolved, AccessControlReceiverCondition,
|
||||
AccessControlTargetCondition,
|
||||
};
|
||||
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 super::{AccessResult, AccessResultClass};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub(super) enum ModifyResult<'a> {
|
||||
Deny,
|
||||
Denied,
|
||||
Grant,
|
||||
Allow {
|
||||
pres: BTreeSet<Attribute>,
|
||||
rem: BTreeSet<Attribute>,
|
||||
pres_cls: BTreeSet<&'a str>,
|
||||
rem_cls: BTreeSet<&'a str>,
|
||||
cls: BTreeSet<&'a str>,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -31,17 +27,12 @@ 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_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();
|
||||
let mut constrain_cls = BTreeSet::default();
|
||||
let mut allow_cls = BTreeSet::default();
|
||||
|
||||
// Some useful references.
|
||||
// - needed for checking entry manager conditions.
|
||||
|
@ -52,53 +43,28 @@ pub(super) fn apply_modify_access<'a>(
|
|||
// kind of being three operations all in one.
|
||||
|
||||
match modify_ident_test(ident) {
|
||||
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 => {}
|
||||
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),
|
||||
}
|
||||
|
||||
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) {
|
||||
AccessModResult::Deny => denied = true,
|
||||
AccessModResult::Constrain {
|
||||
mut pres_attr,
|
||||
mut rem_attr,
|
||||
..
|
||||
} => {
|
||||
constrain_rem.append(&mut rem_attr);
|
||||
constrain_pres.append(&mut pres_attr);
|
||||
AccessResult::Denied => denied = true,
|
||||
AccessResult::Constrain(mut set) => {
|
||||
constrain_rem.extend(set.iter().cloned());
|
||||
constrain_pres.append(&mut set)
|
||||
}
|
||||
// Can't grant.
|
||||
// AccessModResult::Grant |
|
||||
AccessResult::Grant |
|
||||
// Can't allow
|
||||
AccessModResult::Allow { .. } | AccessModResult::Ignore => {}
|
||||
AccessResult::Allow(_) |
|
||||
AccessResult::Ignore => {}
|
||||
}
|
||||
|
||||
// Setup the acp's here
|
||||
|
@ -156,27 +122,35 @@ pub(super) fn apply_modify_access<'a>(
|
|||
.collect();
|
||||
|
||||
match modify_pres_test(scoped_acp.as_slice()) {
|
||||
AccessModResult::Deny => denied = true,
|
||||
AccessResult::Denied => denied = true,
|
||||
// Can never return a unilateral grant.
|
||||
// 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);
|
||||
}
|
||||
AccessResult::Grant => {}
|
||||
AccessResult::Ignore => {}
|
||||
AccessResult::Constrain(mut set) => constrain_pres.append(&mut set),
|
||||
AccessResult::Allow(mut set) => allow_pres.append(&mut set),
|
||||
}
|
||||
|
||||
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::Deny
|
||||
ModifyResult::Denied
|
||||
} else if grant {
|
||||
ModifyResult::Grant
|
||||
} else {
|
||||
|
@ -194,48 +168,31 @@ pub(super) fn apply_modify_access<'a>(
|
|||
allow_rem
|
||||
};
|
||||
|
||||
let mut allowed_pres_cls = if !constrain_pres_cls.is_empty() {
|
||||
let allowed_cls = if !constrain_cls.is_empty() {
|
||||
// bit_and
|
||||
&constrain_pres_cls & &allow_pres_cls
|
||||
&constrain_cls & &allow_cls
|
||||
} else {
|
||||
allow_pres_cls
|
||||
allow_cls
|
||||
};
|
||||
|
||||
let mut allowed_rem_cls = if !constrain_rem_cls.is_empty() {
|
||||
// bit_and
|
||||
&constrain_rem_cls & &allow_rem_cls
|
||||
} else {
|
||||
allow_rem_cls
|
||||
};
|
||||
|
||||
// Deny these classes from being part of any addition or removal to an entry
|
||||
for protected_cls in PROTECTED_MOD_PRES_ENTRY_CLASSES.iter() {
|
||||
allowed_pres_cls.remove(protected_cls.as_str());
|
||||
}
|
||||
|
||||
for protected_cls in PROTECTED_MOD_REM_ENTRY_CLASSES.iter() {
|
||||
allowed_rem_cls.remove(protected_cls.as_str());
|
||||
}
|
||||
|
||||
ModifyResult::Allow {
|
||||
pres: allowed_pres,
|
||||
rem: allowed_rem,
|
||||
pres_cls: allowed_pres_cls,
|
||||
rem_cls: allowed_rem_cls,
|
||||
cls: allowed_cls,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn modify_ident_test(ident: &Identity) -> AccessBasicResult {
|
||||
fn modify_ident_test(ident: &Identity) -> AccessResult {
|
||||
match &ident.origin {
|
||||
IdentType::Internal => {
|
||||
trace!("Internal operation, bypassing access check");
|
||||
// No need to check ACS
|
||||
return AccessBasicResult::Grant;
|
||||
return AccessResult::Grant;
|
||||
}
|
||||
IdentType::Synch(_) => {
|
||||
security_critical!("Blocking sync check");
|
||||
return AccessBasicResult::Deny;
|
||||
return AccessResult::Denied;
|
||||
}
|
||||
IdentType::User(_) => {}
|
||||
};
|
||||
|
@ -244,56 +201,53 @@ fn modify_ident_test(ident: &Identity) -> AccessBasicResult {
|
|||
match ident.access_scope() {
|
||||
AccessScope::ReadOnly | AccessScope::Synchronise => {
|
||||
security_access!("denied ❌ - identity access scope is not permitted to modify");
|
||||
return AccessBasicResult::Deny;
|
||||
return AccessResult::Denied;
|
||||
}
|
||||
AccessScope::ReadWrite => {
|
||||
// As you were
|
||||
}
|
||||
};
|
||||
|
||||
AccessBasicResult::Ignore
|
||||
AccessResult::Ignore
|
||||
}
|
||||
|
||||
fn modify_pres_test<'a>(scoped_acp: &[&'a AccessControlModify]) -> AccessModResult<'a> {
|
||||
let pres_attr: BTreeSet<Attribute> = scoped_acp
|
||||
fn modify_pres_test(scoped_acp: &[&AccessControlModify]) -> AccessResult {
|
||||
let allowed_pres: BTreeSet<Attribute> = scoped_acp
|
||||
.iter()
|
||||
.flat_map(|acp| acp.presattrs.iter().cloned())
|
||||
.collect();
|
||||
AccessResult::Allow(allowed_pres)
|
||||
}
|
||||
|
||||
let rem_attr: BTreeSet<Attribute> = scoped_acp
|
||||
fn modify_rem_test(scoped_acp: &[&AccessControlModify]) -> AccessResult {
|
||||
let allowed_rem: BTreeSet<Attribute> = scoped_acp
|
||||
.iter()
|
||||
.flat_map(|acp| acp.remattrs.iter().cloned())
|
||||
.collect();
|
||||
|
||||
let pres_class: BTreeSet<&'a str> = scoped_acp
|
||||
.iter()
|
||||
.flat_map(|acp| acp.pres_classes.iter().map(|s| s.as_str()))
|
||||
.collect();
|
||||
|
||||
let rem_class: BTreeSet<&'a str> = scoped_acp
|
||||
.iter()
|
||||
.flat_map(|acp| acp.rem_classes.iter().map(|s| s.as_str()))
|
||||
.collect();
|
||||
|
||||
AccessModResult::Allow {
|
||||
pres_attr,
|
||||
rem_attr,
|
||||
pres_class,
|
||||
rem_class,
|
||||
}
|
||||
AccessResult::Allow(allowed_rem)
|
||||
}
|
||||
|
||||
fn modify_sync_constrain<'a>(
|
||||
// TODO: Should this be reverted to the Str borrow method? Or do we try to change
|
||||
// to EntryClass?
|
||||
fn modify_cls_test<'a>(scoped_acp: &[&'a AccessControlModify]) -> AccessResultClass<'a> {
|
||||
let allowed_classes: BTreeSet<&'a str> = scoped_acp
|
||||
.iter()
|
||||
.flat_map(|acp| acp.classes.iter().map(|s| s.as_str()))
|
||||
.collect();
|
||||
AccessResultClass::Allow(allowed_classes)
|
||||
}
|
||||
|
||||
fn modify_sync_constrain(
|
||||
ident: &Identity,
|
||||
entry: &Arc<EntrySealedCommitted>,
|
||||
sync_agreements: &HashMap<Uuid, BTreeSet<Attribute>>,
|
||||
) -> AccessModResult<'a> {
|
||||
) -> AccessResult {
|
||||
match &ident.origin {
|
||||
IdentType::Internal => AccessModResult::Ignore,
|
||||
IdentType::Internal => AccessResult::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.
|
||||
AccessModResult::Ignore
|
||||
AccessResult::Ignore
|
||||
}
|
||||
IdentType::User(_) => {
|
||||
// We need to meet these conditions.
|
||||
|
@ -305,7 +259,7 @@ fn modify_sync_constrain<'a>(
|
|||
.unwrap_or(false);
|
||||
|
||||
if !is_sync {
|
||||
return AccessModResult::Ignore;
|
||||
return AccessResult::Ignore;
|
||||
}
|
||||
|
||||
if let Some(sync_uuid) = entry.get_ava_single_refer(Attribute::SyncParentUuid) {
|
||||
|
@ -320,115 +274,11 @@ fn modify_sync_constrain<'a>(
|
|||
set.extend(sync_yield_authority.iter().cloned())
|
||||
}
|
||||
|
||||
AccessModResult::Constrain {
|
||||
pres_attr: set.clone(),
|
||||
rem_attr: set,
|
||||
pres_cls: None,
|
||||
rem_cls: None,
|
||||
}
|
||||
AccessResult::Constrain(set)
|
||||
} else {
|
||||
warn!(entry = ?entry.get_uuid(), "sync_parent_uuid not found on sync object, preventing all access");
|
||||
AccessModResult::Deny
|
||||
AccessResult::Denied
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify if the modification runs into limits that are defined by our protection rules.
|
||||
fn modify_protected_attrs<'a>(
|
||||
ident: &Identity,
|
||||
entry: &Arc<EntrySealedCommitted>,
|
||||
) -> AccessModResult<'a> {
|
||||
match &ident.origin {
|
||||
IdentType::Internal | IdentType::Synch(_) => {
|
||||
// We don't constraint or influence these.
|
||||
AccessModResult::Ignore
|
||||
}
|
||||
IdentType::User(_) => {
|
||||
if let Some(classes) = entry.get_ava_as_iutf8(Attribute::Class) {
|
||||
if classes.is_disjoint(&PROTECTED_MOD_ENTRY_CLASSES) {
|
||||
// Not protected, go ahead
|
||||
AccessModResult::Ignore
|
||||
} else {
|
||||
// Okay, the entry is protected, apply the full ruleset.
|
||||
modify_protected_entry_attrs(classes)
|
||||
}
|
||||
} else {
|
||||
// Nothing to check - this entry will fail to modify anyway because it has
|
||||
// no classes
|
||||
AccessModResult::Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn modify_protected_entry_attrs<'a>(classes: &BTreeSet<String>) -> AccessModResult<'a> {
|
||||
// This is where the majority of the logic is - this contains the modification
|
||||
// rules as they apply.
|
||||
|
||||
// First check for the hard-deny rules.
|
||||
if !classes.is_disjoint(&LOCKED_ENTRY_CLASSES) {
|
||||
// Hard deny attribute modifications to these types.
|
||||
return AccessModResult::Deny;
|
||||
}
|
||||
|
||||
let mut constrain_attrs = BTreeSet::default();
|
||||
|
||||
// Allows removal of the recycled class specifically on recycled entries.
|
||||
if classes.contains(EntryClass::Recycled.into()) {
|
||||
constrain_attrs.extend([Attribute::Class]);
|
||||
}
|
||||
|
||||
if classes.contains(EntryClass::ClassType.into()) {
|
||||
constrain_attrs.extend([Attribute::May, Attribute::Must]);
|
||||
}
|
||||
|
||||
if classes.contains(EntryClass::SystemConfig.into()) {
|
||||
constrain_attrs.extend([Attribute::BadlistPassword]);
|
||||
}
|
||||
|
||||
// Allow domain settings.
|
||||
if classes.contains(EntryClass::DomainInfo.into()) {
|
||||
constrain_attrs.extend([
|
||||
Attribute::DomainSsid,
|
||||
Attribute::DomainLdapBasedn,
|
||||
Attribute::LdapMaxQueryableAttrs,
|
||||
Attribute::LdapAllowUnixPwBind,
|
||||
Attribute::FernetPrivateKeyStr,
|
||||
Attribute::Es256PrivateKeyDer,
|
||||
Attribute::KeyActionRevoke,
|
||||
Attribute::KeyActionRotate,
|
||||
Attribute::IdVerificationEcKey,
|
||||
Attribute::DeniedName,
|
||||
Attribute::DomainDisplayName,
|
||||
Attribute::Image,
|
||||
]);
|
||||
}
|
||||
|
||||
// Allow account policy related attributes to be changed on dyngroup
|
||||
if classes.contains(EntryClass::DynGroup.into()) {
|
||||
constrain_attrs.extend([
|
||||
Attribute::AuthSessionExpiry,
|
||||
Attribute::AuthPasswordMinimumLength,
|
||||
Attribute::CredentialTypeMinimum,
|
||||
Attribute::PrivilegeExpiry,
|
||||
Attribute::WebauthnAttestationCaList,
|
||||
Attribute::LimitSearchMaxResults,
|
||||
Attribute::LimitSearchMaxFilterTest,
|
||||
Attribute::AllowPrimaryCredFallback,
|
||||
]);
|
||||
}
|
||||
|
||||
// If we don't constrain the attributes at all, we have to deny the change
|
||||
// from proceeding.
|
||||
if constrain_attrs.is_empty() {
|
||||
AccessModResult::Deny
|
||||
} else {
|
||||
AccessModResult::Constrain {
|
||||
pres_attr: constrain_attrs.clone(),
|
||||
rem_attr: constrain_attrs,
|
||||
pres_cls: None,
|
||||
rem_cls: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -266,10 +266,9 @@ pub struct AccessControlModifyResolved<'a> {
|
|||
#[derive(Debug, Clone)]
|
||||
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 {
|
||||
|
@ -294,25 +293,14 @@ impl AccessControlModify {
|
|||
.map(|i| i.map(Attribute::from).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
let classes: Vec<AttrString> = value
|
||||
let classes = 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)?,
|
||||
pres_classes,
|
||||
rem_classes,
|
||||
classes,
|
||||
presattrs,
|
||||
remattrs,
|
||||
})
|
||||
|
@ -328,8 +316,7 @@ impl AccessControlModify {
|
|||
targetscope: Filter<FilterValid>,
|
||||
presattrs: &str,
|
||||
remattrs: &str,
|
||||
pres_classes: &str,
|
||||
rem_classes: &str,
|
||||
classes: &str,
|
||||
) -> Self {
|
||||
AccessControlModify {
|
||||
acp: AccessControlProfile {
|
||||
|
@ -338,14 +325,7 @@ impl AccessControlModify {
|
|||
receiver: AccessControlReceiver::Group(btreeset!(receiver)),
|
||||
target: AccessControlTarget::Scope(targetscope),
|
||||
},
|
||||
pres_classes: pres_classes
|
||||
.split_whitespace()
|
||||
.map(AttrString::from)
|
||||
.collect(),
|
||||
rem_classes: rem_classes
|
||||
.split_whitespace()
|
||||
.map(AttrString::from)
|
||||
.collect(),
|
||||
classes: classes.split_whitespace().map(AttrString::from).collect(),
|
||||
presattrs: presattrs.split_whitespace().map(Attribute::from).collect(),
|
||||
remattrs: remattrs.split_whitespace().map(Attribute::from).collect(),
|
||||
}
|
||||
|
@ -360,8 +340,7 @@ impl AccessControlModify {
|
|||
target: AccessControlTarget,
|
||||
presattrs: &str,
|
||||
remattrs: &str,
|
||||
pres_classes: &str,
|
||||
rem_classes: &str,
|
||||
classes: &str,
|
||||
) -> Self {
|
||||
AccessControlModify {
|
||||
acp: AccessControlProfile {
|
||||
|
@ -370,14 +349,7 @@ impl AccessControlModify {
|
|||
receiver: AccessControlReceiver::EntryManager,
|
||||
target,
|
||||
},
|
||||
pres_classes: pres_classes
|
||||
.split_whitespace()
|
||||
.map(AttrString::from)
|
||||
.collect(),
|
||||
rem_classes: rem_classes
|
||||
.split_whitespace()
|
||||
.map(AttrString::from)
|
||||
.collect(),
|
||||
classes: classes.split_whitespace().map(AttrString::from).collect(),
|
||||
presattrs: presattrs.split_whitespace().map(Attribute::from).collect(),
|
||||
remattrs: remattrs.split_whitespace().map(Attribute::from).collect(),
|
||||
}
|
||||
|
|
|
@ -1,83 +0,0 @@
|
|||
use crate::prelude::EntryClass;
|
||||
use std::collections::BTreeSet;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
/// These entry classes may not be created or deleted, and may invoke some protection rules
|
||||
/// if on an entry.
|
||||
pub static PROTECTED_ENTRY_CLASSES: LazyLock<BTreeSet<String>> = LazyLock::new(|| {
|
||||
let classes = vec![
|
||||
EntryClass::System,
|
||||
EntryClass::DomainInfo,
|
||||
EntryClass::SystemInfo,
|
||||
EntryClass::SystemConfig,
|
||||
EntryClass::DynGroup,
|
||||
EntryClass::SyncObject,
|
||||
EntryClass::Tombstone,
|
||||
EntryClass::Recycled,
|
||||
];
|
||||
|
||||
BTreeSet::from_iter(classes.into_iter().map(|ec| ec.into()))
|
||||
});
|
||||
|
||||
/// Entries with these classes are protected from modifications - not that
|
||||
/// sync object is not present here as there are separate rules for that in
|
||||
/// the modification access module.
|
||||
///
|
||||
/// Recycled is also not protected here as it needs to be able to be removed
|
||||
/// by a recycle bin admin.
|
||||
pub static PROTECTED_MOD_ENTRY_CLASSES: LazyLock<BTreeSet<String>> = LazyLock::new(|| {
|
||||
let classes = vec![
|
||||
EntryClass::System,
|
||||
EntryClass::DomainInfo,
|
||||
EntryClass::SystemInfo,
|
||||
EntryClass::SystemConfig,
|
||||
EntryClass::DynGroup,
|
||||
// EntryClass::SyncObject,
|
||||
EntryClass::Tombstone,
|
||||
EntryClass::Recycled,
|
||||
];
|
||||
|
||||
BTreeSet::from_iter(classes.into_iter().map(|ec| ec.into()))
|
||||
});
|
||||
|
||||
/// These classes may NOT be added to ANY ENTRY
|
||||
pub static PROTECTED_MOD_PRES_ENTRY_CLASSES: LazyLock<BTreeSet<String>> = LazyLock::new(|| {
|
||||
let classes = vec![
|
||||
EntryClass::System,
|
||||
EntryClass::DomainInfo,
|
||||
EntryClass::SystemInfo,
|
||||
EntryClass::SystemConfig,
|
||||
EntryClass::DynGroup,
|
||||
EntryClass::SyncObject,
|
||||
EntryClass::Tombstone,
|
||||
EntryClass::Recycled,
|
||||
];
|
||||
|
||||
BTreeSet::from_iter(classes.into_iter().map(|ec| ec.into()))
|
||||
});
|
||||
|
||||
/// These classes may NOT be removed from ANY ENTRY
|
||||
pub static PROTECTED_MOD_REM_ENTRY_CLASSES: LazyLock<BTreeSet<String>> = LazyLock::new(|| {
|
||||
let classes = vec![
|
||||
EntryClass::System,
|
||||
EntryClass::DomainInfo,
|
||||
EntryClass::SystemInfo,
|
||||
EntryClass::SystemConfig,
|
||||
EntryClass::DynGroup,
|
||||
EntryClass::SyncObject,
|
||||
EntryClass::Tombstone,
|
||||
// EntryClass::Recycled,
|
||||
];
|
||||
|
||||
BTreeSet::from_iter(classes.into_iter().map(|ec| ec.into()))
|
||||
});
|
||||
|
||||
/// Entries with these classes may not be modified under any circumstance.
|
||||
pub static LOCKED_ENTRY_CLASSES: LazyLock<BTreeSet<String>> = LazyLock::new(|| {
|
||||
let classes = vec![
|
||||
EntryClass::Tombstone,
|
||||
// EntryClass::Recycled,
|
||||
];
|
||||
|
||||
BTreeSet::from_iter(classes.into_iter().map(|ec| ec.into()))
|
||||
});
|
|
@ -4,11 +4,11 @@ use std::collections::BTreeSet;
|
|||
use super::profiles::{
|
||||
AccessControlReceiverCondition, AccessControlSearchResolved, AccessControlTargetCondition,
|
||||
};
|
||||
use super::AccessSrchResult;
|
||||
use super::AccessResult;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub(super) enum SearchResult {
|
||||
Deny,
|
||||
Denied,
|
||||
Grant,
|
||||
Allow(BTreeSet<Attribute>),
|
||||
}
|
||||
|
@ -23,32 +23,32 @@ pub(super) fn apply_search_access(
|
|||
// that.
|
||||
let mut denied = false;
|
||||
let mut grant = false;
|
||||
let constrain = BTreeSet::default();
|
||||
let mut constrain = BTreeSet::default();
|
||||
let mut allow = BTreeSet::default();
|
||||
|
||||
// The access control profile
|
||||
match search_filter_entry(ident, related_acp, entry) {
|
||||
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),
|
||||
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),
|
||||
};
|
||||
|
||||
match search_oauth2_filter_entry(ident, entry) {
|
||||
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),
|
||||
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),
|
||||
};
|
||||
|
||||
match search_sync_account_filter_entry(ident, entry) {
|
||||
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),
|
||||
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),
|
||||
};
|
||||
|
||||
// We'll add more modules later.
|
||||
|
@ -56,7 +56,7 @@ pub(super) fn apply_search_access(
|
|||
// Now finalise the decision.
|
||||
|
||||
if denied {
|
||||
SearchResult::Deny
|
||||
SearchResult::Denied
|
||||
} else if grant {
|
||||
SearchResult::Grant
|
||||
} else {
|
||||
|
@ -74,17 +74,17 @@ fn search_filter_entry(
|
|||
ident: &Identity,
|
||||
related_acp: &[AccessControlSearchResolved],
|
||||
entry: &Arc<EntrySealedCommitted>,
|
||||
) -> AccessSrchResult {
|
||||
) -> AccessResult {
|
||||
// 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 AccessSrchResult::Grant;
|
||||
return AccessResult::Grant;
|
||||
}
|
||||
IdentType::Synch(_) => {
|
||||
security_debug!(uuid = ?entry.get_display_id(), "Blocking sync check");
|
||||
return AccessSrchResult::Deny;
|
||||
return AccessResult::Denied;
|
||||
}
|
||||
IdentType::User(_) => {}
|
||||
};
|
||||
|
@ -95,7 +95,7 @@ fn search_filter_entry(
|
|||
security_debug!(
|
||||
"denied ❌ - identity access scope 'Synchronise' is not permitted to search"
|
||||
);
|
||||
return AccessSrchResult::Deny;
|
||||
return AccessResult::Denied;
|
||||
}
|
||||
AccessScope::ReadOnly | AccessScope::ReadWrite => {
|
||||
// As you were
|
||||
|
@ -161,21 +161,16 @@ fn search_filter_entry(
|
|||
.flatten()
|
||||
.collect();
|
||||
|
||||
AccessSrchResult::Allow {
|
||||
attr: allowed_attrs,
|
||||
}
|
||||
AccessResult::Allow(allowed_attrs)
|
||||
}
|
||||
|
||||
fn search_oauth2_filter_entry(
|
||||
ident: &Identity,
|
||||
entry: &Arc<EntrySealedCommitted>,
|
||||
) -> AccessSrchResult {
|
||||
fn search_oauth2_filter_entry(ident: &Identity, entry: &Arc<EntrySealedCommitted>) -> AccessResult {
|
||||
match &ident.origin {
|
||||
IdentType::Internal | IdentType::Synch(_) => AccessSrchResult::Ignore,
|
||||
IdentType::Internal | IdentType::Synch(_) => AccessResult::Ignore,
|
||||
IdentType::User(iuser) => {
|
||||
if iuser.entry.get_uuid() == UUID_ANONYMOUS {
|
||||
debug!("Anonymous can't access OAuth2 entries, ignoring");
|
||||
return AccessSrchResult::Ignore;
|
||||
return AccessResult::Ignore;
|
||||
}
|
||||
|
||||
let contains_o2_rs = entry
|
||||
|
@ -195,18 +190,16 @@ fn search_oauth2_filter_entry(
|
|||
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 AccessSrchResult::Allow {
|
||||
attr: btreeset!(
|
||||
Attribute::Class,
|
||||
Attribute::DisplayName,
|
||||
Attribute::Uuid,
|
||||
Attribute::Name,
|
||||
Attribute::OAuth2RsOriginLanding,
|
||||
Attribute::Image
|
||||
),
|
||||
};
|
||||
return AccessResult::Allow(btreeset!(
|
||||
Attribute::Class,
|
||||
Attribute::DisplayName,
|
||||
Attribute::Uuid,
|
||||
Attribute::Name,
|
||||
Attribute::OAuth2RsOriginLanding,
|
||||
Attribute::Image
|
||||
));
|
||||
}
|
||||
AccessSrchResult::Ignore
|
||||
AccessResult::Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -214,9 +207,9 @@ fn search_oauth2_filter_entry(
|
|||
fn search_sync_account_filter_entry(
|
||||
ident: &Identity,
|
||||
entry: &Arc<EntrySealedCommitted>,
|
||||
) -> AccessSrchResult {
|
||||
) -> AccessResult {
|
||||
match &ident.origin {
|
||||
IdentType::Internal | IdentType::Synch(_) => AccessSrchResult::Ignore,
|
||||
IdentType::Internal | IdentType::Synch(_) => AccessResult::Ignore,
|
||||
IdentType::User(iuser) => {
|
||||
// Is the user a synced object?
|
||||
let is_user_sync_account = iuser
|
||||
|
@ -251,18 +244,16 @@ 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 AccessSrchResult::Allow {
|
||||
attr: btreeset!(
|
||||
Attribute::Class,
|
||||
Attribute::Uuid,
|
||||
Attribute::SyncCredentialPortal
|
||||
),
|
||||
};
|
||||
return AccessResult::Allow(btreeset!(
|
||||
Attribute::Class,
|
||||
Attribute::Uuid,
|
||||
Attribute::SyncCredentialPortal
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fall through
|
||||
AccessSrchResult::Ignore
|
||||
AccessResult::Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ use crate::value::{CredentialType, EXTRACT_VAL_DN};
|
|||
use crate::valueset::uuid_to_proto_string;
|
||||
use crate::valueset::ScimValueIntermediate;
|
||||
use crate::valueset::*;
|
||||
use concread::arcache::{ARCacheBuilder, ARCacheReadTxn, ARCacheWriteTxn};
|
||||
use concread::arcache::{ARCacheBuilder, ARCacheReadTxn};
|
||||
use concread::cowcell::*;
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use kanidm_proto::internal::{DomainInfo as ProtoDomainInfo, ImageValue, UiHint};
|
||||
|
@ -205,13 +205,6 @@ 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>>),
|
||||
|
@ -267,7 +260,7 @@ pub trait QueryServerTransaction<'a> {
|
|||
|
||||
fn get_domain_image_value(&self) -> Option<ImageValue>;
|
||||
|
||||
fn get_resolve_filter_cache(&mut self) -> Option<&mut ResolveFilterCacheReadTxn<'a>>;
|
||||
fn get_resolve_filter_cache(&mut self) -> &mut ResolveFilterCacheReadTxn<'a>;
|
||||
|
||||
// Because of how borrowck in rust works, if we need to get two inner types we have to get them
|
||||
// in a single fn.
|
||||
|
@ -276,7 +269,7 @@ pub trait QueryServerTransaction<'a> {
|
|||
&mut self,
|
||||
) -> (
|
||||
&mut Self::BackendTransactionType,
|
||||
Option<&mut ResolveFilterCacheReadTxn<'a>>,
|
||||
&mut ResolveFilterCacheReadTxn<'a>,
|
||||
);
|
||||
|
||||
/// Conduct a search and apply access controls to yield a set of entries that
|
||||
|
@ -333,15 +326,11 @@ pub trait QueryServerTransaction<'a> {
|
|||
// NOTE: Filters are validated in event conversion.
|
||||
|
||||
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), resolve_filter_cache)
|
||||
.resolve(&se.ident, Some(idxmeta), Some(resolve_filter_cache))
|
||||
.map_err(|e| {
|
||||
admin_error!(?e, "search filter resolve failure");
|
||||
e
|
||||
|
@ -377,7 +366,7 @@ pub trait QueryServerTransaction<'a> {
|
|||
|
||||
let vfr = ee
|
||||
.filter
|
||||
.resolve(&ee.ident, Some(idxmeta), resolve_filter_cache)
|
||||
.resolve(&ee.ident, Some(idxmeta), Some(resolve_filter_cache))
|
||||
.map_err(|e| {
|
||||
admin_error!(?e, "Failed to resolve filter");
|
||||
e
|
||||
|
@ -1455,17 +1444,17 @@ impl<'a> QueryServerTransaction<'a> for QueryServerReadTransaction<'a> {
|
|||
&self.key_providers
|
||||
}
|
||||
|
||||
fn get_resolve_filter_cache(&mut self) -> Option<&mut ResolveFilterCacheReadTxn<'a>> {
|
||||
Some(&mut self.resolve_filter_cache)
|
||||
fn get_resolve_filter_cache(&mut self) -> &mut ResolveFilterCacheReadTxn<'a> {
|
||||
&mut self.resolve_filter_cache
|
||||
}
|
||||
|
||||
fn get_resolve_filter_cache_and_be_txn(
|
||||
&mut self,
|
||||
) -> (
|
||||
&mut BackendReadTransaction<'a>,
|
||||
Option<&mut ResolveFilterCacheReadTxn<'a>>,
|
||||
&mut ResolveFilterCacheReadTxn<'a>,
|
||||
) {
|
||||
(&mut self.be_txn, Some(&mut self.resolve_filter_cache))
|
||||
(&mut self.be_txn, &mut self.resolve_filter_cache)
|
||||
}
|
||||
|
||||
fn pw_badlist(&self) -> &HashSet<String> {
|
||||
|
@ -1689,25 +1678,17 @@ impl<'a> QueryServerTransaction<'a> for QueryServerWriteTransaction<'a> {
|
|||
&self.key_providers
|
||||
}
|
||||
|
||||
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(&mut self) -> &mut ResolveFilterCacheReadTxn<'a> {
|
||||
&mut self.resolve_filter_cache
|
||||
}
|
||||
|
||||
fn get_resolve_filter_cache_and_be_txn(
|
||||
&mut self,
|
||||
) -> (
|
||||
&mut BackendWriteTransaction<'a>,
|
||||
Option<&mut ResolveFilterCacheReadTxn<'a>>,
|
||||
&mut ResolveFilterCacheReadTxn<'a>,
|
||||
) {
|
||||
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))
|
||||
}
|
||||
(&mut self.be_txn, &mut self.resolve_filter_cache)
|
||||
}
|
||||
|
||||
fn pw_badlist(&self) -> &HashSet<String> {
|
||||
|
@ -2022,8 +2003,6 @@ 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(),
|
||||
})
|
||||
|
@ -2173,13 +2152,16 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
|||
))
|
||||
}?;
|
||||
|
||||
// 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;
|
||||
// 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.
|
||||
|
||||
// Trigger reloads on services that require post-schema reloads.
|
||||
// Mainly this is plugins.
|
||||
DynGroup::reload(self)?;
|
||||
if *self.phase >= ServerPhase::SchemaReady {
|
||||
DynGroup::reload(self)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -2602,14 +2584,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
|||
self.changed_flags.remove(ChangeFlag::OAUTH2)
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
fn set_phase(&mut self, phase: ServerPhase) {
|
||||
// Phase changes are one way
|
||||
if phase > *self.phase {
|
||||
*self.phase = phase
|
||||
|
@ -2723,8 +2698,6 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
|||
changed_flags,
|
||||
changed_uuid: _,
|
||||
resolve_filter_cache: _,
|
||||
resolve_filter_cache_clear,
|
||||
mut resolve_filter_cache_write,
|
||||
} = self;
|
||||
debug_assert!(!committed);
|
||||
|
||||
|
@ -2738,12 +2711,6 @@ 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,65 +388,6 @@ impl fmt::Display for SyntaxType {
|
|||
}
|
||||
}
|
||||
|
||||
impl SyntaxType {
|
||||
pub fn index_types(&self) -> &[IndexType] {
|
||||
match self {
|
||||
SyntaxType::Utf8String => &[IndexType::Equality, IndexType::Presence],
|
||||
// Used by classes, needs to change ...
|
||||
// Probably need an attrname syntax too
|
||||
SyntaxType::Utf8StringInsensitive => &[IndexType::Equality, IndexType::Presence],
|
||||
SyntaxType::Utf8StringIname => &[
|
||||
IndexType::Equality,
|
||||
IndexType::Presence,
|
||||
IndexType::SubString,
|
||||
],
|
||||
SyntaxType::Uuid => &[IndexType::Equality, IndexType::Presence],
|
||||
SyntaxType::Boolean => &[IndexType::Equality],
|
||||
SyntaxType::SyntaxId => &[],
|
||||
SyntaxType::IndexId => &[],
|
||||
SyntaxType::ReferenceUuid => &[IndexType::Equality, IndexType::Presence],
|
||||
SyntaxType::JsonFilter => &[],
|
||||
SyntaxType::Credential => &[IndexType::Equality],
|
||||
SyntaxType::SecretUtf8String => &[],
|
||||
SyntaxType::SshKey => &[IndexType::Equality, IndexType::Presence],
|
||||
SyntaxType::SecurityPrincipalName => &[
|
||||
IndexType::Equality,
|
||||
IndexType::Presence,
|
||||
IndexType::SubString,
|
||||
],
|
||||
SyntaxType::Uint32 => &[IndexType::Equality, IndexType::Presence],
|
||||
SyntaxType::Cid => &[],
|
||||
SyntaxType::NsUniqueId => &[IndexType::Equality, IndexType::Presence],
|
||||
SyntaxType::DateTime => &[],
|
||||
SyntaxType::EmailAddress => &[IndexType::Equality, IndexType::SubString],
|
||||
SyntaxType::Url => &[],
|
||||
SyntaxType::OauthScope => &[],
|
||||
SyntaxType::OauthScopeMap => &[IndexType::Equality],
|
||||
SyntaxType::PrivateBinary => &[],
|
||||
SyntaxType::IntentToken => &[IndexType::Equality],
|
||||
SyntaxType::Passkey => &[IndexType::Equality],
|
||||
SyntaxType::AttestedPasskey => &[IndexType::Equality],
|
||||
SyntaxType::Session => &[IndexType::Equality],
|
||||
SyntaxType::JwsKeyEs256 => &[],
|
||||
SyntaxType::JwsKeyRs256 => &[],
|
||||
SyntaxType::Oauth2Session => &[IndexType::Equality],
|
||||
SyntaxType::UiHint => &[],
|
||||
SyntaxType::TotpSecret => &[],
|
||||
SyntaxType::ApiToken => &[IndexType::Equality],
|
||||
SyntaxType::AuditLogString => &[],
|
||||
SyntaxType::EcKeyPrivate => &[],
|
||||
SyntaxType::Image => &[],
|
||||
SyntaxType::CredentialType => &[],
|
||||
SyntaxType::WebauthnAttestationCaList => &[],
|
||||
SyntaxType::OauthClaimMap => &[IndexType::Equality],
|
||||
SyntaxType::KeyInternal => &[],
|
||||
SyntaxType::HexString => &[],
|
||||
SyntaxType::Certificate => &[],
|
||||
SyntaxType::ApplicationPassword => &[IndexType::Equality],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Hash,
|
||||
Debug,
|
||||
|
|
|
@ -14,73 +14,87 @@ const ALLOWED_ATTRIBUTES: &[&str] = &[
|
|||
"role",
|
||||
"output_mode",
|
||||
"log_level",
|
||||
"ldap",
|
||||
];
|
||||
|
||||
#[derive(Default)]
|
||||
struct Flags {
|
||||
ldap: bool,
|
||||
}
|
||||
|
||||
fn parse_attributes(
|
||||
args: &TokenStream,
|
||||
fn parse_knobs(
|
||||
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(),
|
||||
)
|
||||
});
|
||||
|
||||
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()
|
||||
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()
|
||||
.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,})
|
||||
}
|
||||
}
|
||||
.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)
|
||||
};
|
||||
|
||||
// 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,})
|
||||
});
|
||||
|
||||
let ts = quote!(kanidmd_core::config::Configuration {
|
||||
// Setup the config filling the remaining fields with the default values
|
||||
let default_config_struct = quote!(kanidmd_core::config::Configuration {
|
||||
#field_modifications
|
||||
..kanidmd_core::config::Configuration::new_for_test()
|
||||
});
|
||||
|
||||
Ok((ts, flags))
|
||||
let rt = quote_spanned! {last_stmt_start_span=>
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
};
|
||||
|
||||
let header = quote! {
|
||||
#[::core::prelude::v1::test]
|
||||
};
|
||||
|
||||
let fn_name = &input.sig.ident;
|
||||
let test_driver = Ident::new(&format!("tk_{}", fn_name), input.sig.span());
|
||||
|
||||
// Effectively we are just injecting a real test function around this which we will
|
||||
// call.
|
||||
|
||||
let result = quote! {
|
||||
#input
|
||||
|
||||
#header
|
||||
fn #test_driver() {
|
||||
let body = async {
|
||||
let (rsclient, mut core_handle) = kanidmd_testkit::setup_async_test(#default_config_struct).await;
|
||||
#fn_name(rsclient).await;
|
||||
core_handle.shutdown().await;
|
||||
};
|
||||
#[allow(clippy::expect_used, clippy::diverging_sub_expression)]
|
||||
{
|
||||
return #rt
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed building the Runtime")
|
||||
.block_on(body);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
result.into()
|
||||
}
|
||||
|
||||
fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream {
|
||||
tokens.extend(TokenStream::from(error.into_compile_error()));
|
||||
tokens
|
||||
}
|
||||
|
||||
pub(crate) fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
|
@ -101,80 +115,31 @@ 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));
|
||||
}
|
||||
|
||||
// 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 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
|
||||
}
|
||||
} 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
|
||||
let args: Punctuated<ExprAssign, syn::token::Comma> =
|
||||
match Punctuated::<ExprAssign, Token![,]>::parse_terminated.parse(args.clone()) {
|
||||
Ok(it) => it,
|
||||
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(", ")),
|
||||
),
|
||||
);
|
||||
}
|
||||
parse_knobs(&input, &args)
|
||||
}
|
||||
|
|
|
@ -51,9 +51,8 @@ kanidm_build_profiles = { workspace = true }
|
|||
compact_jwt = { workspace = true }
|
||||
escargot = "0.5.13"
|
||||
# used for webdriver testing
|
||||
fantoccini = { version = "0.21.5" }
|
||||
fantoccini = { version = "0.21.4" }
|
||||
futures = { workspace = true }
|
||||
ldap3_client = { workspace = true }
|
||||
oauth2_ext = { workspace = true, default-features = false, features = [
|
||||
"reqwest",
|
||||
] }
|
||||
|
@ -64,7 +63,7 @@ tokio-openssl = { workspace = true }
|
|||
kanidm_lib_crypto = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
webauthn-authenticator-rs = { workspace = true }
|
||||
jsonschema = "0.29.1"
|
||||
jsonschema = "0.29.0"
|
||||
|
||||
[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,9 +46,14 @@ pub fn is_free_port(port: u16) -> bool {
|
|||
}
|
||||
|
||||
// Test external behaviours of the service.
|
||||
fn port_loop() -> u16 {
|
||||
|
||||
// allowed because the use of this function is behind a test gate
|
||||
#[allow(dead_code)]
|
||||
pub async fn setup_async_test(mut config: Configuration) -> (KanidmClient, CoreHandle) {
|
||||
sketching::test_init();
|
||||
|
||||
let mut counter = 0;
|
||||
loop {
|
||||
let port = loop {
|
||||
let possible_port = PORT_ALLOC.fetch_add(1, Ordering::SeqCst);
|
||||
if is_free_port(possible_port) {
|
||||
break possible_port;
|
||||
|
@ -59,21 +64,7 @@ fn port_loop() -> u16 {
|
|||
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(),
|
||||
|
@ -84,16 +75,6 @@ pub async fn setup_async_test(mut config: Configuration) -> AsyncTestEnvironment
|
|||
|
||||
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);
|
||||
|
@ -121,11 +102,7 @@ pub async fn setup_async_test(mut config: Configuration) -> AsyncTestEnvironment
|
|||
|
||||
tracing::info!("Testkit server setup complete - {}", addr);
|
||||
|
||||
AsyncTestEnvironment {
|
||||
rsclient,
|
||||
core_handle,
|
||||
ldap_url,
|
||||
}
|
||||
(rsclient, core_handle)
|
||||
}
|
||||
|
||||
/// 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,9 +13,8 @@ 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
|
||||
|
@ -28,7 +27,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
|
||||
|
@ -41,7 +40,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: &KanidmCl
|
|||
// 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: &KanidmClie
|
|||
}
|
||||
|
||||
#[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: &Kani
|
|||
|
||||
// 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: &KanidmClient) {
|
||||
async fn test_webdriver_user_login(rsclient: kanidm_client::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: &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")
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
use kanidmd_testkit::AsyncTestEnvironment;
|
||||
use ldap3_client::LdapClientBuilder;
|
||||
|
||||
#[kanidmd_testkit::test(ldap = true)]
|
||||
async fn test_ldap_basic_unix_bind(test_env: &AsyncTestEnvironment) {
|
||||
let ldap_url = test_env.ldap_url.as_ref().unwrap();
|
||||
|
||||
let mut ldap_client = LdapClientBuilder::new(ldap_url).build().await.unwrap();
|
||||
|
||||
// Bind as anonymous
|
||||
ldap_client.bind("".into(), "".into()).await.unwrap();
|
||||
|
||||
let whoami = ldap_client.whoami().await.unwrap();
|
||||
|
||||
assert_eq!(whoami, Some("u: anonymous@localhost".to_string()));
|
||||
}
|
|
@ -6,7 +6,6 @@ mod https_extractors;
|
|||
mod https_middleware;
|
||||
mod 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;
|
||||
|
|
|
@ -2,7 +2,7 @@ use kanidm_client::KanidmClient;
|
|||
|
||||
/// This literally tests that the thing exists and responds in a way we expect, probably worth testing it better...
|
||||
#[kanidmd_testkit::test]
|
||||
async fn test_v1_service_account_id_attr_attr_delete(rsclient: &KanidmClient) {
|
||||
async fn test_v1_service_account_id_attr_attr_delete(rsclient: KanidmClient) {
|
||||
// We need to do manual reqwests here.
|
||||
let client = rsclient.client();
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ use kanidm_client::KanidmClient;
|
|||
|
||||
/// This literally tests that the thing exists and responds in a way we expect, probably worth testing it better...
|
||||
#[kanidmd_testkit::test]
|
||||
async fn test_v1_system_post_attr(rsclient: &KanidmClient) {
|
||||
async fn test_v1_system_post_attr(rsclient: KanidmClient) {
|
||||
let client = rsclient.client();
|
||||
|
||||
let response = match client
|
||||
|
|
|
@ -3,8 +3,8 @@ use kanidmd_lib::constants::NAME_IDM_ADMINS;
|
|||
use kanidmd_testkit::*;
|
||||
|
||||
#[kanidmd_testkit::test]
|
||||
async fn account_id_unix_token(rsclient: &KanidmClient) {
|
||||
login_put_admin_idm_admins(rsclient).await;
|
||||
async fn account_id_unix_token(rsclient: KanidmClient) {
|
||||
login_put_admin_idm_admins(&rsclient).await;
|
||||
|
||||
create_user(&rsclient, "group_manager", "idm_group_manage_priv").await;
|
||||
// create test user without creating new groups
|
||||
|
|
14
shell.nix
14
shell.nix
|
@ -1,13 +1,14 @@
|
|||
let
|
||||
rust-overlay = (import (builtins.fetchTarball "https://github.com/oxalica/rust-overlay/archive/master.tar.gz"));
|
||||
{ pkgs ? import <nixpkgs> {} }:
|
||||
let
|
||||
overrides = (builtins.fromTOML (builtins.readFile ./rust-toolchain.toml));
|
||||
in
|
||||
{ pkgs ? import <nixpkgs> { overlays = [ rust-overlay ]; } }:
|
||||
pkgs.mkShellNoCC {
|
||||
pkgs.mkShellNoCC rec {
|
||||
# Kanidm dependencies
|
||||
buildInputs = with pkgs; [
|
||||
pkg-config
|
||||
|
||||
(rust-bin.fromRustupToolchainFile ./rust-toolchain.toml)
|
||||
cargo
|
||||
rustc
|
||||
|
||||
clang
|
||||
llvmPackages.bintools
|
||||
|
@ -18,6 +19,7 @@ pkgs.mkShellNoCC {
|
|||
linux-pam
|
||||
];
|
||||
|
||||
RUSTC_VERSION = overrides.toolchain.channel;
|
||||
# https://github.com/rust-lang/rust-bindgen#environment-variables
|
||||
LIBCLANG_PATH = pkgs.lib.makeLibraryPath [ pkgs.llvmPackages_latest.libclang.lib ];
|
||||
}
|
||||
}
|
|
@ -16,9 +16,7 @@ impl GroupOpt {
|
|||
GroupOpt::RemoveMembers(gcopt) => gcopt.copt.debug,
|
||||
GroupOpt::SetMembers(gcopt) => gcopt.copt.debug,
|
||||
GroupOpt::PurgeMembers(gcopt) => gcopt.copt.debug,
|
||||
GroupOpt::SetDescription { copt, .. }
|
||||
| GroupOpt::Rename { copt, .. }
|
||||
| GroupOpt::SetMail { copt, .. } => copt.debug,
|
||||
GroupOpt::Rename { copt, .. } | GroupOpt::SetMail { copt, .. } => copt.debug,
|
||||
GroupOpt::Posix { commands } => match commands {
|
||||
GroupPosix::Show(gcopt) => gcopt.copt.debug,
|
||||
GroupPosix::Set(gcopt) => gcopt.copt.debug,
|
||||
|
@ -180,26 +178,6 @@ impl GroupOpt {
|
|||
Ok(_) => println!("Successfully set mail for group {}", name.as_str()),
|
||||
}
|
||||
}
|
||||
GroupOpt::SetDescription {
|
||||
copt,
|
||||
name,
|
||||
description,
|
||||
} => {
|
||||
let client = copt.to_client(OpType::Write).await;
|
||||
|
||||
let result = if let Some(description) = description {
|
||||
client
|
||||
.idm_group_set_description(name.as_str(), description.as_str())
|
||||
.await
|
||||
} else {
|
||||
client.idm_group_purge_description(name.as_str()).await
|
||||
};
|
||||
|
||||
match result {
|
||||
Err(e) => handle_client_error(e, copt.output_mode),
|
||||
Ok(_) => println!("Successfully set description for group {}", name.as_str()),
|
||||
}
|
||||
}
|
||||
GroupOpt::Rename {
|
||||
copt,
|
||||
name,
|
||||
|
|
|
@ -348,14 +348,6 @@ pub enum GroupOpt {
|
|||
name: String,
|
||||
mail: Vec<String>,
|
||||
},
|
||||
/// Set the description of this group. If no description is provided, the value is cleared
|
||||
#[clap(name = "set-description")]
|
||||
SetDescription {
|
||||
#[clap(flatten)]
|
||||
copt: CommonOpt,
|
||||
name: String,
|
||||
description: Option<String>,
|
||||
},
|
||||
/// Set a new entry-managed-by for this group.
|
||||
#[clap(name = "set-entry-manager")]
|
||||
SetEntryManagedBy {
|
||||
|
|
|
@ -16,9 +16,6 @@ pub struct Config {
|
|||
|
||||
pub sync_password_as_unix_password: Option<bool>,
|
||||
|
||||
/// Maximum LDAP message size (in kilobytes)
|
||||
pub max_ber_size: Option<usize>,
|
||||
|
||||
// pub entry: Option<Vec<EntryConfig>>,
|
||||
#[serde(flatten)]
|
||||
pub entry_map: BTreeMap<Uuid, EntryConfig>,
|
||||
|
|
|
@ -306,7 +306,6 @@ async fn run_sync(
|
|||
// Preflight check.
|
||||
// * can we connect to ipa?
|
||||
let mut ipa_client = match LdapClientBuilder::new(&sync_config.ipa_uri)
|
||||
.max_ber_size(sync_config.max_ber_size)
|
||||
.add_tls_ca(&sync_config.ipa_ca)
|
||||
.build()
|
||||
.await
|
||||
|
|
|
@ -59,14 +59,6 @@ fn group_attr_gidnumber() -> String {
|
|||
Attribute::GidNumber.to_string()
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Default)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum GroupAttrSchema {
|
||||
Rfc2307,
|
||||
#[default]
|
||||
Rfc2307Bis,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Config {
|
||||
pub sync_token: String,
|
||||
|
@ -110,14 +102,12 @@ pub struct Config {
|
|||
pub group_attr_gidnumber: String,
|
||||
#[serde(default = "group_attr_member")]
|
||||
pub group_attr_member: String,
|
||||
#[serde(default)]
|
||||
pub group_attr_schema: GroupAttrSchema,
|
||||
|
||||
/// Maximum LDAP message size (in kilobytes)
|
||||
pub max_ber_size: Option<usize>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub entry_map: BTreeMap<Uuid, EntryConfig>,
|
||||
|
||||
/// Maximum LDAP message size (in kilobytes)
|
||||
pub max_ber_size: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Default, Clone)]
|
||||
|
|
|
@ -11,26 +11,16 @@
|
|||
// We allow expect since it forces good error messages at the least.
|
||||
#![allow(clippy::expect_used)]
|
||||
|
||||
use crate::config::{Config, EntryConfig, GroupAttrSchema};
|
||||
mod config;
|
||||
mod error;
|
||||
|
||||
use crate::config::{Config, EntryConfig};
|
||||
use crate::error::SyncError;
|
||||
use chrono::Utc;
|
||||
use clap::Parser;
|
||||
use cron::Schedule;
|
||||
use kanidm_client::KanidmClientBuilder;
|
||||
use kanidm_lib_file_permissions::readonly as file_permissions_readonly;
|
||||
use kanidm_proto::constants::ATTR_OBJECTCLASS;
|
||||
use kanidm_proto::scim_v1::{
|
||||
MultiValueAttr, ScimEntry, ScimSshPubKey, ScimSyncGroup, ScimSyncPerson, ScimSyncRequest,
|
||||
ScimSyncRetentionMode, ScimSyncState,
|
||||
};
|
||||
#[cfg(target_family = "unix")]
|
||||
use kanidm_utils_users::{get_current_gid, get_current_uid, get_effective_gid, get_effective_uid};
|
||||
use kanidmd_lib::prelude::Attribute;
|
||||
use ldap3_client::{
|
||||
proto::{self, LdapFilter},
|
||||
LdapClient, LdapClientBuilder, LdapSyncRepl, LdapSyncReplEntry, LdapSyncStateValue,
|
||||
};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::fs::metadata;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
@ -48,12 +38,22 @@ use tokio::net::TcpListener;
|
|||
use tokio::runtime;
|
||||
use tokio::sync::broadcast;
|
||||
use tokio::time::sleep;
|
||||
|
||||
use tracing::{debug, error, info, warn};
|
||||
use tracing_subscriber::prelude::*;
|
||||
use tracing_subscriber::{fmt, EnvFilter};
|
||||
|
||||
mod config;
|
||||
mod error;
|
||||
use kanidm_client::KanidmClientBuilder;
|
||||
use kanidm_lib_file_permissions::readonly as file_permissions_readonly;
|
||||
use kanidm_proto::scim_v1::{
|
||||
MultiValueAttr, ScimEntry, ScimSshPubKey, ScimSyncGroup, ScimSyncPerson, ScimSyncRequest,
|
||||
ScimSyncRetentionMode, ScimSyncState,
|
||||
};
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
use kanidm_utils_users::{get_current_gid, get_current_uid, get_effective_gid, get_effective_uid};
|
||||
|
||||
use ldap3_client::{proto, LdapClientBuilder, LdapSyncRepl, LdapSyncReplEntry, LdapSyncStateValue};
|
||||
|
||||
include!("./opt.rs");
|
||||
|
||||
|
@ -343,7 +343,7 @@ async fn run_sync(
|
|||
LdapSyncRepl::Success {
|
||||
cookie,
|
||||
refresh_deletes: _,
|
||||
mut entries,
|
||||
entries,
|
||||
delete_uuids,
|
||||
present_uuids,
|
||||
} => {
|
||||
|
@ -393,14 +393,6 @@ async fn run_sync(
|
|||
}
|
||||
};
|
||||
|
||||
if matches!(sync_config.group_attr_schema, GroupAttrSchema::Rfc2307) {
|
||||
// Since the schema is rfc 2307, this means that the names of members
|
||||
// in any group are uids, not dn's, so we need to resolve these now.
|
||||
resolve_member_uid_to_dn(&mut ldap_client, &mut entries, sync_config)
|
||||
.await
|
||||
.map_err(|_| SyncError::Preprocess)?;
|
||||
};
|
||||
|
||||
let entries = match process_ldap_sync_result(entries, sync_config).await {
|
||||
Ok(ssr) => ssr,
|
||||
Err(()) => {
|
||||
|
@ -452,99 +444,6 @@ async fn run_sync(
|
|||
// done!
|
||||
}
|
||||
|
||||
async fn resolve_member_uid_to_dn(
|
||||
ldap_client: &mut LdapClient,
|
||||
ldap_entries: &mut [LdapSyncReplEntry],
|
||||
sync_config: &Config,
|
||||
) -> Result<(), ()> {
|
||||
let mut lookup_cache: BTreeMap<String, String> = Default::default();
|
||||
|
||||
for sync_entry in ldap_entries.iter_mut() {
|
||||
let oc = sync_entry
|
||||
.entry
|
||||
.attrs
|
||||
.get(ATTR_OBJECTCLASS)
|
||||
.ok_or_else(|| {
|
||||
error!("Invalid entry - no object class {}", sync_entry.entry.dn);
|
||||
})?;
|
||||
|
||||
if !oc.contains(&sync_config.group_objectclass) {
|
||||
// Not a group, skip.
|
||||
continue;
|
||||
}
|
||||
|
||||
// It's a group, does it have memberUid? We pop this out here
|
||||
// because we plan to replace it.
|
||||
let members = sync_entry
|
||||
.entry
|
||||
.remove_ava(&sync_config.group_attr_member)
|
||||
.unwrap_or_default();
|
||||
|
||||
// Now, search all the members to dns.
|
||||
let mut resolved_members: BTreeSet<String> = Default::default();
|
||||
|
||||
for member_uid in members {
|
||||
if let Some(member_dn) = lookup_cache.get(&member_uid) {
|
||||
resolved_members.insert(member_dn.to_string());
|
||||
} else {
|
||||
// Not in cache, search it. We use a syncrepl request here as this
|
||||
// can bypass some query limits. Note we set the sync cookie to None.
|
||||
let filter = LdapFilter::And(vec![
|
||||
// Always put uid first as openldap can't query optimise.
|
||||
LdapFilter::Equality(
|
||||
sync_config.person_attr_user_name.clone(),
|
||||
member_uid.clone(),
|
||||
),
|
||||
LdapFilter::Equality(
|
||||
ATTR_OBJECTCLASS.into(),
|
||||
sync_config.person_objectclass.clone(),
|
||||
),
|
||||
]);
|
||||
|
||||
let mode = proto::SyncRequestMode::RefreshOnly;
|
||||
let sync_result = ldap_client
|
||||
.syncrepl(sync_config.ldap_sync_base_dn.clone(), filter, None, mode)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
debug!(?member_uid, ?sync_entry.entry_uuid);
|
||||
error!(
|
||||
?err,
|
||||
"Failed to perform syncrepl to resolve members from ldap"
|
||||
);
|
||||
})?;
|
||||
|
||||
// Get the memberDN out now.
|
||||
let member_dn = match sync_result {
|
||||
LdapSyncRepl::Success { mut entries, .. } => {
|
||||
let Some(resolved_entry) = entries.pop() else {
|
||||
warn!(?member_uid, "Unable to resolve member, no matching entries");
|
||||
continue;
|
||||
};
|
||||
|
||||
resolved_entry.entry.dn.clone()
|
||||
}
|
||||
_ => {
|
||||
error!("Invalid sync repl result state");
|
||||
return Err(());
|
||||
}
|
||||
};
|
||||
|
||||
// cache it.
|
||||
lookup_cache.insert(member_uid, member_dn.clone());
|
||||
resolved_members.insert(member_dn);
|
||||
}
|
||||
}
|
||||
|
||||
// Put the members back in resolved as DN's now.
|
||||
sync_entry
|
||||
.entry
|
||||
.attrs
|
||||
.insert(sync_config.group_attr_member.clone(), resolved_members);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn process_ldap_sync_result(
|
||||
ldap_entries: Vec<LdapSyncReplEntry>,
|
||||
sync_config: &Config,
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
|
||||
current_dir = $(shell pwd)
|
||||
|
||||
dev_install:
|
||||
@ echo "WARNING: THIS WILL BREAK EXISTING UNIXD INSTALLS"
|
||||
@ echo "ctrl-c now if this is not what you want"
|
||||
@ read
|
||||
@ echo "LAST CHANCE"
|
||||
@ sleep 5
|
||||
ln -s -f $(current_dir)/../platform/opensuse/kanidm-unixd.service /etc/systemd/system/kanidm-unixd.service
|
||||
ln -s -f $(current_dir)/../platform/opensuse/kanidm-unixd-tasks.service /etc/systemd/system/kanidm-unixd-tasks.service
|
||||
ln -s -f $(current_dir)/../target/debug/kanidm-unix /usr/sbin/kanidm-unix
|
||||
ln -s -f $(current_dir)/../target/debug/kanidm_ssh_authorizedkeys /usr/sbin/kanidm_ssh_authorizedkeys
|
||||
ln -s -f $(current_dir)/../target/debug/kanidm_unixd_tasks /usr/sbin/kanidm_unixd_tasks
|
||||
ln -s -f $(current_dir)/../target/debug/kanidm_unixd /usr/sbin/kanidm_unixd
|
||||
ln -s -f $(current_dir)/../target/debug/libpam_kanidm.so /lib64/security/pam_kanidm.so
|
||||
ln -s -f $(current_dir)/../target/debug/libnss_kanidm.so /usr/lib64/libnss_kanidm.so.2
|
||||
|
||||
|
|
@ -7,13 +7,6 @@ use std::io::Read;
|
|||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct EtcDb {
|
||||
pub users: Vec<EtcUser>,
|
||||
pub shadow: Vec<EtcShadow>,
|
||||
pub groups: Vec<EtcGroup>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
pub struct EtcUser {
|
||||
pub name: String,
|
||||
|
@ -46,7 +39,7 @@ pub fn read_etc_passwd_file<P: AsRef<Path>>(path: P) -> Result<Vec<EtcUser>, Uni
|
|||
parse_etc_passwd(contents.as_slice()).map_err(|_| UnixIntegrationError)
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Default)]
|
||||
#[derive(Debug, PartialEq, Default)]
|
||||
pub enum CryptPw {
|
||||
Sha256(String),
|
||||
Sha512(String),
|
||||
|
@ -63,16 +56,6 @@ impl fmt::Display for CryptPw {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for CryptPw {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
CryptPw::Invalid => write!(f, "x"),
|
||||
CryptPw::Sha256(_s) => write!(f, "crypt sha256"),
|
||||
CryptPw::Sha512(_s) => write!(f, "crypt sha512"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for CryptPw {
|
||||
type Err = &'static str;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::unix_passwd::{EtcDb, EtcGroup, EtcUser};
|
||||
use crate::unix_passwd::{EtcGroup, EtcUser};
|
||||
use kanidm_proto::internal::OperationError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
@ -200,12 +200,6 @@ pub struct HomeDirectoryInfo {
|
|||
pub aliases: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct TaskRequestFrame {
|
||||
pub id: u64,
|
||||
pub req: TaskRequest,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum TaskRequest {
|
||||
HomeDirectory(HomeDirectoryInfo),
|
||||
|
@ -213,9 +207,8 @@ pub enum TaskRequest {
|
|||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum TaskResponse {
|
||||
Success(u64),
|
||||
Success,
|
||||
Error(String),
|
||||
NotifyShadowChange(EtcDb),
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue