mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 20:47:01 +01:00
20221103 ipa import driver (#1180)
This commit is contained in:
parent
2e864be37f
commit
1ed4d7c1bd
122
Cargo.lock
generated
122
Cargo.lock
generated
|
@ -134,7 +134,7 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"rusticata-macros",
|
"rusticata-macros",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"time 0.3.16",
|
"time 0.3.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -596,9 +596,9 @@ checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytemuck"
|
name = "bytemuck"
|
||||||
version = "1.12.2"
|
version = "1.12.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5aec14f5d4e6e3f927cd0c81f72e5710d95ee9019fbeb4b3021193867491bfd8"
|
checksum = "aaa3a8d9a1ca92e282c96a32d6511b695d7d994d1d102ba85d279f9b2756947f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
|
@ -769,9 +769,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "compact_jwt"
|
name = "compact_jwt"
|
||||||
version = "0.2.8"
|
version = "0.2.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5656b98b1584764a52906e67caec20dfb9b0179ac2052d0d5937b083bc39a120"
|
checksum = "51f9032b96a89dd79ffc5f62523d5351ebb40680cbdfc4029393b511b9e971aa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.13.1",
|
"base64 0.13.1",
|
||||||
"base64urlsafedata",
|
"base64urlsafedata",
|
||||||
|
@ -869,7 +869,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "344adc371239ef32293cb1c4fe519592fcf21206c79c02854320afcdf3ab4917"
|
checksum = "344adc371239ef32293cb1c4fe519592fcf21206c79c02854320afcdf3ab4917"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"time 0.3.16",
|
"time 0.3.17",
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -885,7 +885,7 @@ dependencies = [
|
||||||
"publicsuffix",
|
"publicsuffix",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"time 0.3.16",
|
"time 0.3.17",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2193,9 +2193,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ipnet"
|
name = "ipnet"
|
||||||
version = "2.5.0"
|
version = "2.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b"
|
checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
|
@ -2245,6 +2245,27 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kanidm-ipa-sync"
|
||||||
|
version = "1.1.0-alpha.11-dev"
|
||||||
|
dependencies = [
|
||||||
|
"base64urlsafedata",
|
||||||
|
"clap",
|
||||||
|
"clap_complete",
|
||||||
|
"kanidm_client",
|
||||||
|
"kanidm_proto",
|
||||||
|
"kanidmd_lib",
|
||||||
|
"ldap3_client",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tokio",
|
||||||
|
"toml",
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
|
"url",
|
||||||
|
"users",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kanidm_client"
|
name = "kanidm_client"
|
||||||
version = "1.1.0-alpha.11-dev"
|
version = "1.1.0-alpha.11-dev"
|
||||||
|
@ -2269,6 +2290,7 @@ dependencies = [
|
||||||
"base32",
|
"base32",
|
||||||
"base64urlsafedata",
|
"base64urlsafedata",
|
||||||
"last-git-commit",
|
"last-git-commit",
|
||||||
|
"scim_proto",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"time 0.2.27",
|
"time 0.2.27",
|
||||||
|
@ -2518,16 +2540,36 @@ dependencies = [
|
||||||
"nom 2.2.1",
|
"nom 2.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ldap3_client"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "git+https://github.com/kanidm/ldap3.git#6b0d146d3f85a32add3bdc3639ba4146822eb861"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.13.1",
|
||||||
|
"base64urlsafedata",
|
||||||
|
"futures-util",
|
||||||
|
"ldap3_proto",
|
||||||
|
"openssl",
|
||||||
|
"serde",
|
||||||
|
"tokio",
|
||||||
|
"tokio-openssl",
|
||||||
|
"tokio-util",
|
||||||
|
"tracing",
|
||||||
|
"url",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ldap3_proto"
|
name = "ldap3_proto"
|
||||||
version = "0.2.3"
|
version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/kanidm/ldap3.git#6b0d146d3f85a32add3bdc3639ba4146822eb861"
|
||||||
checksum = "62d7f04b6dc4d5401b817596e424ecb4a0931db1418f3987a27e0ab69320665e"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"lber",
|
"lber",
|
||||||
|
"nom 7.1.1",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2768,9 +2810,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "native-tls"
|
name = "native-tls"
|
||||||
version = "0.2.10"
|
version = "0.2.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9"
|
checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -2886,9 +2928,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num_cpus"
|
name = "num_cpus"
|
||||||
version = "1.13.1"
|
version = "1.14.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
|
checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi",
|
"hermit-abi",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -2915,15 +2957,6 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num_threads"
|
|
||||||
version = "0.1.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oauth2"
|
name = "oauth2"
|
||||||
version = "4.2.3"
|
version = "4.2.3"
|
||||||
|
@ -3256,9 +3289,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.16"
|
version = "0.2.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-crate"
|
name = "proc-macro-crate"
|
||||||
|
@ -3539,9 +3572,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.6.27"
|
version = "0.6.28"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
|
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "remove_dir_all"
|
name = "remove_dir_all"
|
||||||
|
@ -3747,6 +3780,21 @@ dependencies = [
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scim_proto"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://github.com/kanidm/scim.git#f7a9241bf413ac2e40cb974876d2b0433a866c74"
|
||||||
|
dependencies = [
|
||||||
|
"base64urlsafedata",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"time 0.2.27",
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
|
"url",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scoped-tls"
|
name = "scoped-tls"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
@ -4411,16 +4459,14 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.3.16"
|
version = "0.3.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fab5c8b9980850e06d92ddbe3ab839c062c801f3927c0fb8abd6fc8e918fbca"
|
checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa 1.0.4",
|
"itoa 1.0.4",
|
||||||
"libc",
|
|
||||||
"num_threads",
|
|
||||||
"serde",
|
"serde",
|
||||||
"time-core",
|
"time-core",
|
||||||
"time-macros 0.2.5",
|
"time-macros 0.2.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -4441,9 +4487,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time-macros"
|
name = "time-macros"
|
||||||
version = "0.2.5"
|
version = "0.2.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "65bb801831d812c562ae7d2bfb531f26e66e4e1f6b17307ba4149c5064710e5b"
|
checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"time-core",
|
"time-core",
|
||||||
]
|
]
|
||||||
|
@ -5250,7 +5296,7 @@ dependencies = [
|
||||||
"oid-registry",
|
"oid-registry",
|
||||||
"rusticata-macros",
|
"rusticata-macros",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"time 0.3.16",
|
"time 0.3.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -5370,5 +5416,5 @@ dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"quick-error",
|
"quick-error",
|
||||||
"regex",
|
"regex",
|
||||||
"time 0.3.16",
|
"time 0.3.17",
|
||||||
]
|
]
|
||||||
|
|
13
Cargo.toml
13
Cargo.toml
|
@ -5,6 +5,7 @@ lto = "thin"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
|
"iam_migrations/freeipa",
|
||||||
"kanidm_client",
|
"kanidm_client",
|
||||||
"kanidm_proto",
|
"kanidm_proto",
|
||||||
"kanidm_tools",
|
"kanidm_tools",
|
||||||
|
@ -83,7 +84,17 @@ kanidm_unix_int = { path = "./kanidm_unix_int" }
|
||||||
last-git-commit = "0.2.0"
|
last-git-commit = "0.2.0"
|
||||||
# REMOVE this
|
# REMOVE this
|
||||||
lazy_static = "^1.4.0"
|
lazy_static = "^1.4.0"
|
||||||
ldap3_proto = "^0.2.3"
|
# ldap3_client = "^0.3.0"
|
||||||
|
# ldap3_proto = "^0.3.0"
|
||||||
|
|
||||||
|
# ldap3_client = { path = "../ldap3/client", version = "0.3.0" }
|
||||||
|
# ldap3_proto = { path = "../ldap3/proto", version = "0.3.0" }
|
||||||
|
# scim_proto = { path = "../scim/proto", version = "0.1.0" }
|
||||||
|
|
||||||
|
ldap3_client = { git = "https://github.com/kanidm/ldap3.git", version = "0.3.0" }
|
||||||
|
ldap3_proto = { git = "https://github.com/kanidm/ldap3.git", version = "0.3.0" }
|
||||||
|
scim_proto = { git = "https://github.com/kanidm/scim.git", version = "0.1.0" }
|
||||||
|
|
||||||
libc = "^0.2.135"
|
libc = "^0.2.135"
|
||||||
libnss = "^0.4.0"
|
libnss = "^0.4.0"
|
||||||
libsqlite3-sys = "^0.25.0"
|
libsqlite3-sys = "^0.25.0"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
dn: cn=Retro Changelog Plugin,cn=plugins,cn=config
|
dn: cn=Retro Changelog Plugin,cn=plugins,cn=config
|
||||||
changetype: modify
|
changetype: modify
|
||||||
add: nsslapd-include-suffix
|
add: nsslapd-include-suffix
|
||||||
nsslapd-include-suffix: cn=accounts,dc=dev,dc=kanidm,dc=com
|
nsslapd-include-suffix: dc=dev,dc=kanidm,dc=com
|
||||||
|
|
36
iam_migrations/freeipa/Cargo.toml
Normal file
36
iam_migrations/freeipa/Cargo.toml
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
[package]
|
||||||
|
name = "kanidm-ipa-sync"
|
||||||
|
description = "Kanidm Client Tools"
|
||||||
|
documentation = "https://kanidm.github.io/kanidm/stable/"
|
||||||
|
|
||||||
|
version.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
homepage.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
base64urlsafedata.workspace = true
|
||||||
|
clap = { workspace = true, features = ["derive", "env"] }
|
||||||
|
kanidm_client.workspace = true
|
||||||
|
kanidm_proto.workspace = true
|
||||||
|
tokio = { workspace = true, features = ["rt", "macros"] }
|
||||||
|
tracing.workspace = true
|
||||||
|
tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] }
|
||||||
|
|
||||||
|
users.workspace = true
|
||||||
|
|
||||||
|
ldap3_client.workspace = true
|
||||||
|
serde = { workspace = true, features = ["derive"] }
|
||||||
|
serde_json.workspace = true
|
||||||
|
toml.workspace = true
|
||||||
|
url = { workspace = true, features = ["serde"] }
|
||||||
|
|
||||||
|
# For file metadata, should this me moved out?
|
||||||
|
kanidmd_lib.workspace = true
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
clap = { workspace = true, features = ["derive"] }
|
||||||
|
clap_complete.workspace = true
|
12
iam_migrations/freeipa/src/config.rs
Normal file
12
iam_migrations/freeipa/src/config.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
|
pub sync_token: String,
|
||||||
|
pub ipa_uri: Url,
|
||||||
|
pub ipa_ca: String,
|
||||||
|
pub ipa_sync_dn: String,
|
||||||
|
pub ipa_sync_pw: String,
|
||||||
|
pub ipa_sync_base_dn: String,
|
||||||
|
}
|
507
iam_migrations/freeipa/src/main.rs
Normal file
507
iam_migrations/freeipa/src/main.rs
Normal file
|
@ -0,0 +1,507 @@
|
||||||
|
#![deny(warnings)]
|
||||||
|
#![warn(unused_extern_crates)]
|
||||||
|
#![deny(clippy::todo)]
|
||||||
|
#![deny(clippy::unimplemented)]
|
||||||
|
#![deny(clippy::unwrap_used)]
|
||||||
|
#![deny(clippy::panic)]
|
||||||
|
#![deny(clippy::unreachable)]
|
||||||
|
#![deny(clippy::await_holding_lock)]
|
||||||
|
#![deny(clippy::needless_pass_by_value)]
|
||||||
|
#![deny(clippy::trivially_copy_pass_by_ref)]
|
||||||
|
// We allow expect since it forces good error messages at the least.
|
||||||
|
#![allow(clippy::expect_used)]
|
||||||
|
|
||||||
|
mod config;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
use crate::config::Config;
|
||||||
|
use clap::Parser;
|
||||||
|
use std::fs::metadata;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Read;
|
||||||
|
use std::os::unix::fs::MetadataExt;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::thread;
|
||||||
|
use tokio::runtime;
|
||||||
|
use tracing::{debug, error, info, warn};
|
||||||
|
use tracing_subscriber::prelude::*;
|
||||||
|
use tracing_subscriber::{fmt, EnvFilter};
|
||||||
|
|
||||||
|
use kanidm_client::KanidmClientBuilder;
|
||||||
|
use kanidm_proto::scim_v1::{
|
||||||
|
ScimEntry, ScimExternalMember, ScimSyncGroup, ScimSyncPerson, ScimSyncRequest, ScimSyncState,
|
||||||
|
};
|
||||||
|
use kanidmd_lib::utils::file_permissions_readonly;
|
||||||
|
|
||||||
|
use users::{get_current_gid, get_current_uid, get_effective_gid, get_effective_uid};
|
||||||
|
|
||||||
|
use ldap3_client::{
|
||||||
|
proto, proto::LdapFilter, LdapClientBuilder, LdapSyncRepl, LdapSyncReplEntry,
|
||||||
|
LdapSyncStateValue,
|
||||||
|
};
|
||||||
|
|
||||||
|
include!("./opt.rs");
|
||||||
|
|
||||||
|
async fn driver_main(opt: Opt) {
|
||||||
|
debug!("Starting kanidm freeipa sync driver.");
|
||||||
|
// Parse the configs.
|
||||||
|
|
||||||
|
let mut f = match File::open(&opt.ipa_sync_config) {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Unable to open profile file [{:?}] 🥺", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut contents = String::new();
|
||||||
|
if let Err(e) = f.read_to_string(&mut contents) {
|
||||||
|
error!("unable to read profile contents {:?}", e);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let sync_config: Config = match toml::from_str(contents.as_str()) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("unable to parse config {:?}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!(?sync_config);
|
||||||
|
|
||||||
|
let cb = match KanidmClientBuilder::new().read_options_from_optional_config(&opt.client_config)
|
||||||
|
{
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => {
|
||||||
|
error!("Failed to parse {}", opt.client_config.to_string_lossy());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Do we need this?
|
||||||
|
// let cb = cb.connect_timeout(cfg.conn_timeout);
|
||||||
|
|
||||||
|
let rsclient = match cb.build() {
|
||||||
|
Ok(rsc) => rsc,
|
||||||
|
Err(_e) => {
|
||||||
|
error!("Failed to build async client");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
rsclient.set_token(sync_config.sync_token.clone()).await;
|
||||||
|
|
||||||
|
// Preflight check.
|
||||||
|
// * can we connect to ipa?
|
||||||
|
|
||||||
|
let mut ipa_client = match LdapClientBuilder::new(&sync_config.ipa_uri)
|
||||||
|
.add_tls_ca(&sync_config.ipa_ca)
|
||||||
|
.build()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(lc) => lc,
|
||||||
|
Err(e) => {
|
||||||
|
error!(?e, "Failed to connect to freeipa");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match ipa_client
|
||||||
|
.bind(
|
||||||
|
sync_config.ipa_sync_dn.clone(),
|
||||||
|
sync_config.ipa_sync_pw.clone(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(()) => {
|
||||||
|
debug!(ipa_sync_dn = ?sync_config.ipa_sync_dn, ipa_uri = %sync_config.ipa_uri);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!(?e, "Failed to bind (authenticate) to freeipa");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// * can we connect to kanidm?
|
||||||
|
// - get the current sync cookie from kanidm.
|
||||||
|
let scim_sync_status = match rsclient.scim_v1_sync_status().await {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => {
|
||||||
|
error!(?e, "Failed to access scim sync status");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!(state=?scim_sync_status);
|
||||||
|
|
||||||
|
// === Everything is connected! ===
|
||||||
|
|
||||||
|
// Based on the scim_sync_status, perform our sync repl
|
||||||
|
|
||||||
|
let mode = proto::SyncRequestMode::RefreshOnly;
|
||||||
|
|
||||||
|
let cookie = match &scim_sync_status {
|
||||||
|
ScimSyncState::Refresh => None,
|
||||||
|
ScimSyncState::Active { cookie } => Some(cookie.0.clone()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let filter = LdapFilter::Or(vec![
|
||||||
|
// LdapFilter::Equality("objectclass".to_string(), "domain".to_string()),
|
||||||
|
LdapFilter::And(vec![
|
||||||
|
LdapFilter::Equality("objectclass".to_string(), "person".to_string()),
|
||||||
|
LdapFilter::Equality("objectclass".to_string(), "ipantuserattrs".to_string()),
|
||||||
|
LdapFilter::Equality("objectclass".to_string(), "posixaccount".to_string()),
|
||||||
|
]),
|
||||||
|
LdapFilter::And(vec![
|
||||||
|
LdapFilter::Equality("objectclass".to_string(), "groupofnames".to_string()),
|
||||||
|
LdapFilter::Equality("objectclass".to_string(), "ipausergroup".to_string()),
|
||||||
|
LdapFilter::Not(Box::new(LdapFilter::Equality(
|
||||||
|
"objectclass".to_string(),
|
||||||
|
"mepmanagedentry".to_string(),
|
||||||
|
))),
|
||||||
|
// Need to exclude the admins group as it gid conflicts to admin.
|
||||||
|
LdapFilter::Not(Box::new(LdapFilter::Equality(
|
||||||
|
"cn".to_string(),
|
||||||
|
"admins".to_string(),
|
||||||
|
))),
|
||||||
|
// Kani internally has an all persons group.
|
||||||
|
LdapFilter::Not(Box::new(LdapFilter::Equality(
|
||||||
|
"cn".to_string(),
|
||||||
|
"ipausers".to_string(),
|
||||||
|
))),
|
||||||
|
]),
|
||||||
|
LdapFilter::And(vec![
|
||||||
|
LdapFilter::Equality("objectclass".to_string(), "ipatoken".to_string()),
|
||||||
|
LdapFilter::Equality("objectclass".to_string(), "ipatokentotp".to_string()),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
debug!(ipa_sync_base_dn = ?sync_config.ipa_sync_base_dn, ?cookie, ?mode, ?filter);
|
||||||
|
let sync_result = match ipa_client
|
||||||
|
.syncrepl(sync_config.ipa_sync_base_dn, filter, cookie, mode)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(results) => results,
|
||||||
|
Err(e) => {
|
||||||
|
error!(?e, "Failed to perform syncrepl from ipa");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if opt.proto_dump {
|
||||||
|
let stdout = std::io::stdout();
|
||||||
|
if let Err(e) = serde_json::to_writer_pretty(stdout, &sync_result) {
|
||||||
|
error!(?e, "Failed to serialise ldap sync response");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pre-process the entries.
|
||||||
|
// - > fn so we can test.
|
||||||
|
let scim_sync_request = match process_ipa_sync_result(scim_sync_status, sync_result).await {
|
||||||
|
Ok(ssr) => ssr,
|
||||||
|
Err(()) => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
if opt.proto_dump {
|
||||||
|
let stdout = std::io::stdout();
|
||||||
|
// write it out.
|
||||||
|
if let Err(e) = serde_json::to_writer_pretty(stdout, &scim_sync_request) {
|
||||||
|
error!(?e, "Failed to serialise scim sync request");
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
if let Err(e) = rsclient.scim_v1_sync_update(&scim_sync_request).await {
|
||||||
|
error!(
|
||||||
|
?e,
|
||||||
|
"Failed to submit scim sync update - see the kanidmd server log for more details."
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// done!
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn process_ipa_sync_result(
|
||||||
|
from_state: ScimSyncState,
|
||||||
|
sync_result: LdapSyncRepl,
|
||||||
|
) -> Result<ScimSyncRequest, ()> {
|
||||||
|
match sync_result {
|
||||||
|
LdapSyncRepl::Success {
|
||||||
|
cookie,
|
||||||
|
refresh_deletes,
|
||||||
|
entries,
|
||||||
|
delete_uuids,
|
||||||
|
present_uuids,
|
||||||
|
} => {
|
||||||
|
if refresh_deletes {
|
||||||
|
error!("Unsure how to handle refreshDeletes=True");
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !present_uuids.is_empty() {
|
||||||
|
error!("Unsure how to handle presentUuids > 0");
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let to_state = cookie
|
||||||
|
.map(|cookie| {
|
||||||
|
ScimSyncState::Active { cookie }
|
||||||
|
})
|
||||||
|
.ok_or_else(|| {
|
||||||
|
error!("Invalid state, ldap sync repl did not provide a valid state cookie in response.");
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Future - make this par-map
|
||||||
|
let entries = entries
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|e| match ipa_to_scim_entry(e) {
|
||||||
|
Ok(Some(e)) => Some(Ok(e)),
|
||||||
|
Ok(None) => None,
|
||||||
|
Err(()) => Some(Err(())),
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>();
|
||||||
|
|
||||||
|
let entries = match entries {
|
||||||
|
Ok(e) => e,
|
||||||
|
Err(()) => {
|
||||||
|
error!("Failed to process IPA entries to SCIM");
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ScimSyncRequest {
|
||||||
|
from_state,
|
||||||
|
to_state,
|
||||||
|
entries,
|
||||||
|
delete_uuids,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
LdapSyncRepl::RefreshRequired => {
|
||||||
|
let to_state = ScimSyncState::Refresh;
|
||||||
|
|
||||||
|
Ok(ScimSyncRequest {
|
||||||
|
from_state,
|
||||||
|
to_state,
|
||||||
|
entries: Vec::new(),
|
||||||
|
delete_uuids: Vec::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ipa_to_scim_entry(sync_entry: LdapSyncReplEntry) -> Result<Option<ScimEntry>, ()> {
|
||||||
|
debug!("{:#?}", sync_entry);
|
||||||
|
// Is this an entry we need to observe/look at?
|
||||||
|
|
||||||
|
// check the sync_entry state?
|
||||||
|
if sync_entry.state != LdapSyncStateValue::Add {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
|
let dn = sync_entry.entry.dn.clone();
|
||||||
|
|
||||||
|
let oc = sync_entry.entry.attrs.get("objectclass").ok_or_else(|| {
|
||||||
|
error!("Invalid entry - no object class {}", dn);
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if oc.contains("person") {
|
||||||
|
let LdapSyncReplEntry {
|
||||||
|
entry_uuid,
|
||||||
|
state: _,
|
||||||
|
mut entry,
|
||||||
|
} = sync_entry;
|
||||||
|
|
||||||
|
let id = entry_uuid;
|
||||||
|
|
||||||
|
let user_name = entry.remove_ava_single("uid").ok_or_else(|| {
|
||||||
|
error!("Missing required attribute uid");
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let display_name = entry.remove_ava_single("cn").ok_or_else(|| {
|
||||||
|
error!("Missing required attribute cn");
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let gidnumber = entry
|
||||||
|
.remove_ava_single("gidnumber")
|
||||||
|
.map(|gid| {
|
||||||
|
u32::from_str(&gid).map_err(|_| {
|
||||||
|
error!("Invalid gidnumber");
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.transpose()?;
|
||||||
|
|
||||||
|
let homedirectory = entry.remove_ava_single("homedirectory");
|
||||||
|
let password_import = entry.remove_ava_single("ipanthash");
|
||||||
|
let login_shell = entry.remove_ava_single("loginshell");
|
||||||
|
let external_id = Some(entry.dn);
|
||||||
|
|
||||||
|
Ok(Some(
|
||||||
|
ScimSyncPerson {
|
||||||
|
id,
|
||||||
|
external_id,
|
||||||
|
user_name,
|
||||||
|
display_name,
|
||||||
|
gidnumber,
|
||||||
|
homedirectory,
|
||||||
|
password_import,
|
||||||
|
login_shell,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
))
|
||||||
|
} else if oc.contains("groupofnames") {
|
||||||
|
let LdapSyncReplEntry {
|
||||||
|
entry_uuid,
|
||||||
|
state: _,
|
||||||
|
mut entry,
|
||||||
|
} = sync_entry;
|
||||||
|
|
||||||
|
let id = entry_uuid;
|
||||||
|
|
||||||
|
let name = entry.remove_ava_single("cn").ok_or_else(|| {
|
||||||
|
error!("Missing required attribute cn");
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let description = entry.remove_ava_single("description");
|
||||||
|
|
||||||
|
let gidnumber = entry
|
||||||
|
.remove_ava_single("gidnumber")
|
||||||
|
.map(|gid| {
|
||||||
|
u32::from_str(&gid).map_err(|_| {
|
||||||
|
error!("Invalid gidnumber");
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.transpose()?;
|
||||||
|
|
||||||
|
let members: Vec<_> = entry
|
||||||
|
.remove_ava("member")
|
||||||
|
.map(|set| {
|
||||||
|
set.into_iter()
|
||||||
|
.map(|external_id| ScimExternalMember { external_id })
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let external_id = Some(entry.dn);
|
||||||
|
|
||||||
|
Ok(Some(
|
||||||
|
ScimSyncGroup {
|
||||||
|
id,
|
||||||
|
external_id,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
gidnumber,
|
||||||
|
members,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
))
|
||||||
|
} else if oc.contains("ipatokentotp") {
|
||||||
|
// Skip for now, we don't supporty multiple totp yet.
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
debug!("Skipping entry {} with oc {:?}", dn, oc);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn config_security_checks(cfg_path: &Path) -> bool {
|
||||||
|
let cfg_path_str = cfg_path.to_string_lossy();
|
||||||
|
|
||||||
|
if !cfg_path.exists() {
|
||||||
|
// there's no point trying to start up if we can't read a usable config!
|
||||||
|
error!(
|
||||||
|
"Config missing from {} - cannot start up. Quitting.",
|
||||||
|
cfg_path_str
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
let cfg_meta = match metadata(&cfg_path) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Unable to read metadata for {} - {:?}", cfg_path_str, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if !file_permissions_readonly(&cfg_meta) {
|
||||||
|
warn!("permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ...",
|
||||||
|
cfg_path_str
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let cuid = get_current_uid();
|
||||||
|
let ceuid = get_effective_uid();
|
||||||
|
|
||||||
|
if cfg_meta.uid() == cuid || cfg_meta.uid() == ceuid {
|
||||||
|
warn!("WARNING: {} owned by the current uid, which may allow file permission changes. This could be a security risk ...",
|
||||||
|
cfg_path_str
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let cuid = get_current_uid();
|
||||||
|
let ceuid = get_effective_uid();
|
||||||
|
let cgid = get_current_gid();
|
||||||
|
let cegid = get_effective_gid();
|
||||||
|
|
||||||
|
let opt = Opt::parse();
|
||||||
|
|
||||||
|
let fmt_layer = fmt::layer().with_writer(std::io::stderr);
|
||||||
|
|
||||||
|
let filter_layer = if opt.debug {
|
||||||
|
match EnvFilter::try_new("kanidm_client=debug,kanidm_ipa_sync=debug,ldap3_client=debug") {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("ERROR! Unable to start tracing {:?}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match EnvFilter::try_from_default_env() {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(_) => EnvFilter::new("kanidm_client=warn,kanidm_ipa_sync=info,ldap3_client=warn"),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
tracing_subscriber::registry()
|
||||||
|
.with(filter_layer)
|
||||||
|
.with(fmt_layer)
|
||||||
|
.init();
|
||||||
|
|
||||||
|
// Startup sanity checks.
|
||||||
|
if opt.skip_root_check {
|
||||||
|
warn!("Skipping root user check, if you're running this for testing, ensure you clean up temporary files.")
|
||||||
|
// TODO: this wording is not great m'kay.
|
||||||
|
} else {
|
||||||
|
if cuid == 0 || ceuid == 0 || cgid == 0 || cegid == 0 {
|
||||||
|
error!("Refusing to run - this process must not operate as root.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if !config_security_checks(&opt.client_config) || !config_security_checks(&opt.ipa_sync_config)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let par_count = thread::available_parallelism()
|
||||||
|
.expect("Failed to determine available parallelism")
|
||||||
|
.get();
|
||||||
|
|
||||||
|
let rt = runtime::Builder::new_current_thread()
|
||||||
|
// We configure this as we use parallel workers at some points.
|
||||||
|
.max_blocking_threads(par_count)
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.expect("Failed to initialise tokio runtime!");
|
||||||
|
|
||||||
|
tracing::debug!("Using {} worker threads", par_count);
|
||||||
|
|
||||||
|
rt.block_on(async move { driver_main(opt).await });
|
||||||
|
|
||||||
|
info!("Success!");
|
||||||
|
}
|
31
iam_migrations/freeipa/src/opt.rs
Normal file
31
iam_migrations/freeipa/src/opt.rs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
|
||||||
|
|
||||||
|
use kanidm_proto::constants::DEFAULT_CLIENT_CONFIG_PATH;
|
||||||
|
pub const DEFAULT_IPA_CONFIG_PATH: &str = "/etc/kanidm/ipa-sync";
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, clap::Parser)]
|
||||||
|
#[clap(about = "Kanidm FreeIPA Sync Driver")]
|
||||||
|
pub struct Opt {
|
||||||
|
/// Enable debbuging of the sync driver
|
||||||
|
#[clap(short, long, env = "KANIDM_DEBUG")]
|
||||||
|
pub debug: bool,
|
||||||
|
/// Path to the client config file.
|
||||||
|
#[clap(parse(from_os_str), short, long, default_value_os_t = DEFAULT_CLIENT_CONFIG_PATH.into())]
|
||||||
|
pub client_config: PathBuf,
|
||||||
|
|
||||||
|
/// Path to the ipa-sync config file.
|
||||||
|
#[clap(parse(from_os_str), short, long, default_value_os_t = DEFAULT_IPA_CONFIG_PATH.into())]
|
||||||
|
pub ipa_sync_config: PathBuf,
|
||||||
|
|
||||||
|
#[clap(short, long, hide = true)]
|
||||||
|
/// Dump the ldap protocol inputs, as well as the scim outputs. This can be used
|
||||||
|
/// to create test cases for testing the parser.
|
||||||
|
///
|
||||||
|
/// No actions are taken on the kanidm instance, this is purely a dump of the
|
||||||
|
/// state in/out.
|
||||||
|
pub proto_dump: bool,
|
||||||
|
|
||||||
|
#[clap(short, long, hide = true)]
|
||||||
|
pub skip_root_check: bool,
|
||||||
|
}
|
423
iam_migrations/freeipa/src/tests.rs
Normal file
423
iam_migrations/freeipa/src/tests.rs
Normal file
|
@ -0,0 +1,423 @@
|
||||||
|
use crate::process_ipa_sync_result;
|
||||||
|
use kanidm_proto::scim_v1::ScimSyncState;
|
||||||
|
|
||||||
|
use ldap3_client::LdapSyncRepl;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_ldap_to_scim() {
|
||||||
|
let _ = tracing_subscriber::fmt::try_init();
|
||||||
|
|
||||||
|
let sync_request: LdapSyncRepl =
|
||||||
|
serde_json::from_str(TEST_LDAP_SYNC_REPL_1).expect("failed to parse ldap sync");
|
||||||
|
|
||||||
|
let scim_sync_request = process_ipa_sync_result(ScimSyncState::Refresh, sync_request)
|
||||||
|
.await
|
||||||
|
.expect("failed to process ldap sync repl");
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
scim_sync_request.from_state,
|
||||||
|
ScimSyncState::Refresh
|
||||||
|
));
|
||||||
|
|
||||||
|
// need to setup a fake ldap sync result.
|
||||||
|
|
||||||
|
// What do we expect?
|
||||||
|
}
|
||||||
|
|
||||||
|
const TEST_LDAP_SYNC_REPL_1: &str = r#"
|
||||||
|
{
|
||||||
|
"Success": {
|
||||||
|
"cookie": "aXBhLXN5bmNyZXBsLWthbmkuZGV2LmJsYWNraGF0cy5uZXQuYXU6Mzg5I2NuPWRpcmVjdG9yeSBtYW5hZ2VyOmRjPWRldixkYz1ibGFja2hhdHMsZGM9bmV0LGRjPWF1Oih8KCYob2JqZWN0Q2xhc3M9cGVyc29uKShvYmplY3RDbGFzcz1pcGFudHVzZXJhdHRycykob2JqZWN0Q2xhc3M9cG9zaXhhY2NvdW50KSkoJihvYmplY3RDbGFzcz1ncm91cG9mbmFtZXMpKG9iamVjdENsYXNzPWlwYXVzZXJncm91cCkoIShvYmplY3RDbGFzcz1tZXBtYW5hZ2VkZW50cnkpKSkoJihvYmplY3RDbGFzcz1pcGF0b2tlbikob2JqZWN0Q2xhc3M9aXBhdG9rZW50b3RwKSkpIzEwOQ",
|
||||||
|
"refresh_deletes": false,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"entry_uuid": "ac60034b-3498-11ed-a50d-919b4b1a5ec0",
|
||||||
|
"state": "Add",
|
||||||
|
"entry": {
|
||||||
|
"dn": "uid=admin,cn=users,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"attrs": {
|
||||||
|
"cn": [
|
||||||
|
"Administrator"
|
||||||
|
],
|
||||||
|
"gecos": [
|
||||||
|
"Administrator"
|
||||||
|
],
|
||||||
|
"gidnumber": [
|
||||||
|
"8200000"
|
||||||
|
],
|
||||||
|
"homedirectory": [
|
||||||
|
"/home/admin"
|
||||||
|
],
|
||||||
|
"ipanthash": [
|
||||||
|
"CVBguEizG80swI8sftaknw"
|
||||||
|
],
|
||||||
|
"ipantsecurityidentifier": [
|
||||||
|
"S-1-5-21-148961183-2750130983-218252910-500"
|
||||||
|
],
|
||||||
|
"ipauniqueid": [
|
||||||
|
"ad15f644-3498-11ed-95c3-5254006b0418"
|
||||||
|
],
|
||||||
|
"krbextradata": [
|
||||||
|
"AAL4hSJjcm9vdC9hZG1pbkBERVYuQkxBQ0tIQVRTLk5FVC5BVQA"
|
||||||
|
],
|
||||||
|
"krblastadminunlock": [
|
||||||
|
"20220915015504Z"
|
||||||
|
],
|
||||||
|
"krblastfailedauth": [
|
||||||
|
"20221108050316Z"
|
||||||
|
],
|
||||||
|
"krblastpwdchange": [
|
||||||
|
"20220915015504Z"
|
||||||
|
],
|
||||||
|
"krbloginfailedcount": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"krbpasswordexpiration": [
|
||||||
|
"20221214015504Z"
|
||||||
|
],
|
||||||
|
"krbprincipalkey": [
|
||||||
|
"MIIB1KADAgEBoQMCAQGiAwIBAaMDAgEBpIIBvDCCAbgwdKAbMBmgAwIBBKESBBBgeEMvRkhoVWphRX0iKXxCoVUwU6ADAgEUoUwESiAAuyt8szEUVLiWVjSTuUgbgCf8heFMeIhSmGTgJpwL50kddprbdeKuOYvyxepdAil/MqHs4qdqj54reDDqFW0T2bg1Iv9O1cZEMGSgGzAZoAMCAQShEgQQU2xOXT16V21hPFkzPClsJKFFMEOgAwIBE6E8BDoQALfdG+243xBQDt01+bFr46DcZnlHctoSyUQKw8I8FzvRE1LK9Ttl5qkkOHADpA7XSj1lQ2RFqBsSMHSgGzAZoAMCAQShEgQQay9XSC9tPDJJVjIwUDxFRKFVMFOgAwIBEqFMBEogADJjxICRFFzpOcsxMY3xVedF3IBd7qzsQJlSvShaeKwyhTBFI/wvVDtQq6ogWKlACUcAVk2N6p91VtRHHjxXVhKQvT0kt/KS7zBkoBswGaADAgEEoRIEEE5nNTh5SmgpZic0bDAmNUWhRTBDoAMCARGhPAQ6EAClGqBf9jZWixZo/evVMVH01NkI1VpR0fNrGyvtML78p5j6TAne5Nms/wj9BtVawuv+h+Gz1fjdfw"
|
||||||
|
],
|
||||||
|
"krbprincipalname": [
|
||||||
|
"admin@DEV.BLACKHATS.NET.AU",
|
||||||
|
"root@DEV.BLACKHATS.NET.AU"
|
||||||
|
],
|
||||||
|
"loginshell": [
|
||||||
|
"/bin/bash"
|
||||||
|
],
|
||||||
|
"memberof": [
|
||||||
|
"cn=Add Configuration Sub-Entries,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"cn=Add Replication Agreements,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"cn=Host Enrollment,cn=privileges,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"cn=Modify DNA Range,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"cn=Modify PassSync Managers Configuration,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"cn=Modify Replication Agreements,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"cn=Read DNA Range,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"cn=Read LDBM Database Configuration,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"cn=Read PassSync Managers Configuration,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"cn=Read Replication Agreements,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"cn=Read Replication Changelog Configuration,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"cn=Remove Replication Agreements,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"cn=Replication Administrators,cn=privileges,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"cn=System: Add krbPrincipalName to a Host,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"cn=System: Enroll a Host,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"cn=System: Manage Host Certificates,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"cn=System: Manage Host Enrollment Password,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"cn=System: Manage Host Keytab,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"cn=System: Manage Host Principals,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"cn=Write Replication Changelog Configuration,cn=permissions,cn=pbac,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"cn=admins,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"cn=trust admins,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au"
|
||||||
|
],
|
||||||
|
"objectclass": [
|
||||||
|
"inetuser",
|
||||||
|
"ipantuserattrs",
|
||||||
|
"ipaobject",
|
||||||
|
"ipasshgroupofpubkeys",
|
||||||
|
"ipasshuser",
|
||||||
|
"krbprincipalaux",
|
||||||
|
"krbticketpolicyaux",
|
||||||
|
"person",
|
||||||
|
"posixaccount",
|
||||||
|
"top"
|
||||||
|
],
|
||||||
|
"sn": [
|
||||||
|
"Administrator"
|
||||||
|
],
|
||||||
|
"uid": [
|
||||||
|
"admin"
|
||||||
|
],
|
||||||
|
"uidnumber": [
|
||||||
|
"8200000"
|
||||||
|
],
|
||||||
|
"userpassword": [
|
||||||
|
"{PBKDF2_SHA256}AAAIAJ3EnyWJXp/ytIk6sqf1BbLO9fzObD3q5I4y2bRFfgAFVo6CaRAaZ7KPYzU6Y340VSUV4NGRRcBjeU8q+aoTOkuzQM91jl+xlCydiB0CjeIDZ0tGy4NmQUFzfg7+exsKhNk2MfUrHcaqfZBtT7Lkfei4Rk7810TQf3NlHIRO8K3egPQ8Ox52Upw1E5QGEKQmDOjrtLtOF5gbyFtR5wc0wUJfmMhd/g65GkqFIr5vbPan3kL3ZqMhh1rrj4ISi9Ui8P7E8GDicoJDPwPf6YD9D0dx6yk72GyiuYt6p2aGJWMY897xqgB+YMgPptiDPik22ExoBAoHeJNIzKjITc2ohLLn6RkCk4GcCwMVZmcxesl/T/OMeSkNvoOM1zy7ANsGbQeaLqpViJSV0xT5PJ6NoIKMU2pIP57Q17VAlYigtCPU"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entry_uuid": "ac60034e-3498-11ed-a50d-919b4b1a5ec0",
|
||||||
|
"state": "Add",
|
||||||
|
"entry": {
|
||||||
|
"dn": "cn=editors,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"attrs": {
|
||||||
|
"cn": [
|
||||||
|
"editors"
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
"Limited admins who can edit other users"
|
||||||
|
],
|
||||||
|
"gidnumber": [
|
||||||
|
"8200002"
|
||||||
|
],
|
||||||
|
"ipantsecurityidentifier": [
|
||||||
|
"S-1-5-21-148961183-2750130983-218252910-1002"
|
||||||
|
],
|
||||||
|
"ipauniqueid": [
|
||||||
|
"ad191e00-3498-11ed-b143-5254006b0418"
|
||||||
|
],
|
||||||
|
"objectclass": [
|
||||||
|
"groupofnames",
|
||||||
|
"ipantgroupattrs",
|
||||||
|
"ipaobject",
|
||||||
|
"ipausergroup",
|
||||||
|
"nestedgroup",
|
||||||
|
"posixgroup",
|
||||||
|
"top"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entry_uuid": "0c56a965-3499-11ed-a50d-919b4b1a5ec0",
|
||||||
|
"state": "Add",
|
||||||
|
"entry": {
|
||||||
|
"dn": "cn=trust admins,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"attrs": {
|
||||||
|
"cn": [
|
||||||
|
"trust admins"
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
"Trusts administrators group"
|
||||||
|
],
|
||||||
|
"ipauniqueid": [
|
||||||
|
"0f233c48-3499-11ed-8e23-5254006b0418"
|
||||||
|
],
|
||||||
|
"member": [
|
||||||
|
"uid=admin,cn=users,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au"
|
||||||
|
],
|
||||||
|
"objectclass": [
|
||||||
|
"groupofnames",
|
||||||
|
"ipaobject",
|
||||||
|
"ipausergroup",
|
||||||
|
"nestedgroup",
|
||||||
|
"top"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entry_uuid": "babb8302-43a1-11ed-a50d-919b4b1a5ec0",
|
||||||
|
"state": "Add",
|
||||||
|
"entry": {
|
||||||
|
"dn": "uid=testuser,cn=users,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"attrs": {
|
||||||
|
"cn": [
|
||||||
|
"Test User"
|
||||||
|
],
|
||||||
|
"displayname": [
|
||||||
|
"Test User"
|
||||||
|
],
|
||||||
|
"gecos": [
|
||||||
|
"Test User"
|
||||||
|
],
|
||||||
|
"gidnumber": [
|
||||||
|
"12345"
|
||||||
|
],
|
||||||
|
"givenname": [
|
||||||
|
"Test"
|
||||||
|
],
|
||||||
|
"homedirectory": [
|
||||||
|
"/home/testuser"
|
||||||
|
],
|
||||||
|
"initials": [
|
||||||
|
"TU"
|
||||||
|
],
|
||||||
|
"ipanthash": [
|
||||||
|
"iEb36u6PsRetBr3YMLdYbA"
|
||||||
|
],
|
||||||
|
"ipantsecurityidentifier": [
|
||||||
|
"S-1-5-21-148961183-2750130983-218252910-1004"
|
||||||
|
],
|
||||||
|
"ipauniqueid": [
|
||||||
|
"d939d566-43a1-11ed-85aa-5254006b0418"
|
||||||
|
],
|
||||||
|
"ipauserauthtype": [
|
||||||
|
"password"
|
||||||
|
],
|
||||||
|
"krbcanonicalname": [
|
||||||
|
"testuser@DEV.BLACKHATS.NET.AU"
|
||||||
|
],
|
||||||
|
"krbextradata": [
|
||||||
|
"AAL732ljdGVzdHVzZXJAREVWLkJMQUNLSEFUUy5ORVQuQVUA"
|
||||||
|
],
|
||||||
|
"krblastadminunlock": [
|
||||||
|
"20221108044931Z"
|
||||||
|
],
|
||||||
|
"krblastfailedauth": [
|
||||||
|
"20221108045207Z"
|
||||||
|
],
|
||||||
|
"krblastpwdchange": [
|
||||||
|
"20221108045003Z"
|
||||||
|
],
|
||||||
|
"krbloginfailedcount": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"krbpasswordexpiration": [
|
||||||
|
"20230206045003Z"
|
||||||
|
],
|
||||||
|
"krbprincipalkey": [
|
||||||
|
"MIIB1KADAgEBoQMCAQGiAwIBBKMDAgEBpIIBvDCCAbgwdKAbMBmgAwIBBKESBBAhIyRjVSl7LkVCXCZqISkkoVUwU6ADAgEUoUwESiAAyMSJYrMnu6mUnDV3ls7arH782SiSi1+vSFosLoLogJZQHKAxUljESwhySlEn+tAEF3yEenvuigNNDtFS/cYMn4oQ1c/vH4tnMGSgGzAZoAMCAQShEgQQOGVcI0Q7YS53OCdxcmd6WaFFMEOgAwIBE6E8BDoQAG6TZ38sFh9gXirZsZcZEiFls92uUh1+Azz7DxrCpo0B8+he39ACvuwLIaxzfswHZE8/pQUFRiHeMHSgGzAZoAMCAQShEgQQOFpvIFxvRlFEVilZVkYhRaFVMFOgAwIBEqFMBEogANBXnuehcaBtCPIXvaGcUEXXkGxiHlDIBFhXeu6l6w0Nj2Cm8Fezun8ip+si3JuxZkaK7TlxccZQOpjxSRuwekeKrzTNp+vS7jBkoBswGaADAgEEoRIEEFZDXFBrSEZDO0kuX2BORyihRTBDoAMCARGhPAQ6EAChj/DZFH3h9pW31ipzT4PrtdDcR83qla52bf+bLDV6LFV6FvFqq3fBJnpiIwuD9rPBBuDut+1ncg"
|
||||||
|
],
|
||||||
|
"krbprincipalname": [
|
||||||
|
"testuser@DEV.BLACKHATS.NET.AU"
|
||||||
|
],
|
||||||
|
"loginshell": [
|
||||||
|
"/bin/sh"
|
||||||
|
],
|
||||||
|
"mail": [
|
||||||
|
"testuser@dev.blackhats.net.au"
|
||||||
|
],
|
||||||
|
"memberof": [
|
||||||
|
"cn=ipausers,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au"
|
||||||
|
],
|
||||||
|
"mepmanagedentry": [
|
||||||
|
"cn=testuser,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au"
|
||||||
|
],
|
||||||
|
"objectclass": [
|
||||||
|
"inetorgperson",
|
||||||
|
"inetuser",
|
||||||
|
"ipantuserattrs",
|
||||||
|
"ipaobject",
|
||||||
|
"ipasshgroupofpubkeys",
|
||||||
|
"ipasshuser",
|
||||||
|
"ipauserauthtypeclass",
|
||||||
|
"krbprincipalaux",
|
||||||
|
"krbticketpolicyaux",
|
||||||
|
"meporiginentry",
|
||||||
|
"organizationalperson",
|
||||||
|
"person",
|
||||||
|
"posixaccount",
|
||||||
|
"top"
|
||||||
|
],
|
||||||
|
"sn": [
|
||||||
|
"User"
|
||||||
|
],
|
||||||
|
"uid": [
|
||||||
|
"testuser"
|
||||||
|
],
|
||||||
|
"uidnumber": [
|
||||||
|
"8200004"
|
||||||
|
],
|
||||||
|
"userpassword": [
|
||||||
|
"{PBKDF2_SHA256}AAAIAOTKJTaS7zR1u0ar5vDHPzcd9FoDiQVYvpT/n19NpTQJKJfdugke9vwpYxaZk+SnR/WHi4oeKd1IyaVmAC+H5d4qUYcc74xLGoyaezCNy8HkKBz9Q/9MD/gvzUjWUTYjbnXAMjzVpAHhVtzAoPrZVoWgXWkhga+YDsqKnqG0g1UeMTgja2zYr0mrG6Y+w+VJP3nnbQ9q4vpb7MGIs8xgjse+nIWZC+mPrK4ZEjSeE9Tjj+0C6nFq1+xU6KZK8NOG8kuHyVeS87zddJApqLSb2p6X/ixobak1j8VzXFd9lxewMfY+gieoXtn47KCFsquWGlavY4ZqjHYu4+MuHDTN/s8E06O/DkLLxPPO4iSH1B6pIaVTMHxsybX7FRLTj/MOb2+oYwWZty8WJ+dRD7gDg0vdUJr/H8EzJkrdXhNyz7f+"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entry_uuid": "f4dbef82-5f20-11ed-a50d-919b4b1a5ec0",
|
||||||
|
"state": "Add",
|
||||||
|
"entry": {
|
||||||
|
"dn": "ipatokenuniqueid=380e27a4-438d-4c94-9dde-a3f6bc64ea1a,cn=otp,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"attrs": {
|
||||||
|
"ipatokenotpalgorithm": [
|
||||||
|
"sha1"
|
||||||
|
],
|
||||||
|
"ipatokenotpdigits": [
|
||||||
|
"6"
|
||||||
|
],
|
||||||
|
"ipatokenotpkey": [
|
||||||
|
"hS/2a0DXBSKMJIlIcAJbBfCCZN0b8aTpoaJcj0RAAlV7QRQ"
|
||||||
|
],
|
||||||
|
"ipatokenowner": [
|
||||||
|
"uid=testuser,cn=users,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au"
|
||||||
|
],
|
||||||
|
"ipatokentotpclockoffset": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"ipatokentotptimestep": [
|
||||||
|
"30"
|
||||||
|
],
|
||||||
|
"ipatokenuniqueid": [
|
||||||
|
"380e27a4-438d-4c94-9dde-a3f6bc64ea1a"
|
||||||
|
],
|
||||||
|
"objectclass": [
|
||||||
|
"ipatoken",
|
||||||
|
"ipatokentotp",
|
||||||
|
"top"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entry_uuid": "d547c581-5f26-11ed-a50d-919b4b1a5ec0",
|
||||||
|
"state": "Add",
|
||||||
|
"entry": {
|
||||||
|
"dn": "cn=testgroup,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"attrs": {
|
||||||
|
"cn": [
|
||||||
|
"testgroup"
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
"Test group"
|
||||||
|
],
|
||||||
|
"ipauniqueid": [
|
||||||
|
"f1b96e6c-5f26-11ed-8cd2-5254006b0418"
|
||||||
|
],
|
||||||
|
"objectclass": [
|
||||||
|
"groupofnames",
|
||||||
|
"ipaobject",
|
||||||
|
"ipausergroup",
|
||||||
|
"nestedgroup",
|
||||||
|
"top"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entry_uuid": "d547c583-5f26-11ed-a50d-919b4b1a5ec0",
|
||||||
|
"state": "Add",
|
||||||
|
"entry": {
|
||||||
|
"dn": "cn=testexternal,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"attrs": {
|
||||||
|
"cn": [
|
||||||
|
"testexternal"
|
||||||
|
],
|
||||||
|
"ipauniqueid": [
|
||||||
|
"f67fd292-5f26-11ed-a6d0-5254006b0418"
|
||||||
|
],
|
||||||
|
"objectclass": [
|
||||||
|
"groupofnames",
|
||||||
|
"ipaexternalgroup",
|
||||||
|
"ipaobject",
|
||||||
|
"ipausergroup",
|
||||||
|
"nestedgroup",
|
||||||
|
"top"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entry_uuid": "f90b0b81-5f26-11ed-a50d-919b4b1a5ec0",
|
||||||
|
"state": "Add",
|
||||||
|
"entry": {
|
||||||
|
"dn": "cn=testposix,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"attrs": {
|
||||||
|
"cn": [
|
||||||
|
"testposix"
|
||||||
|
],
|
||||||
|
"gidnumber": [
|
||||||
|
"1234567"
|
||||||
|
],
|
||||||
|
"ipauniqueid": [
|
||||||
|
"fb64973e-5f26-11ed-9cfe-5254006b0418"
|
||||||
|
],
|
||||||
|
"objectclass": [
|
||||||
|
"groupofnames",
|
||||||
|
"ipaobject",
|
||||||
|
"ipausergroup",
|
||||||
|
"nestedgroup",
|
||||||
|
"posixgroup",
|
||||||
|
"top"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"delete_uuids": [],
|
||||||
|
"present_uuids": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
|
@ -38,6 +38,7 @@ use webauthn_rs_proto::{
|
||||||
};
|
};
|
||||||
|
|
||||||
mod person;
|
mod person;
|
||||||
|
mod scim;
|
||||||
mod service_account;
|
mod service_account;
|
||||||
mod sync_account;
|
mod sync_account;
|
||||||
mod system;
|
mod system;
|
||||||
|
|
16
kanidm_client/src/scim.rs
Normal file
16
kanidm_client/src/scim.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
use crate::{ClientError, KanidmClient};
|
||||||
|
use kanidm_proto::scim_v1::{ScimSyncRequest, ScimSyncState};
|
||||||
|
|
||||||
|
impl KanidmClient {
|
||||||
|
pub async fn scim_v1_sync_status(&self) -> Result<ScimSyncState, ClientError> {
|
||||||
|
self.perform_get_request("/scim/v1/Sync").await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn scim_v1_sync_update(
|
||||||
|
&self,
|
||||||
|
scim_sync_request: &ScimSyncRequest,
|
||||||
|
) -> Result<(), ClientError> {
|
||||||
|
self.perform_post_request("/scim/v1/Sync", scim_sync_request)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,4 +31,21 @@ impl KanidmClient {
|
||||||
self.perform_post_request("/v1/sync_account", new_acct)
|
self.perform_post_request("/v1/sync_account", new_acct)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn idm_sync_account_generate_token(
|
||||||
|
&self,
|
||||||
|
id: &str,
|
||||||
|
label: &str,
|
||||||
|
) -> Result<String, ClientError> {
|
||||||
|
self.perform_post_request(
|
||||||
|
format!("/v1/sync_account/{}/_sync_token", id).as_str(),
|
||||||
|
label,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn idm_sync_account_destroy_token(&self, id: &str) -> Result<(), ClientError> {
|
||||||
|
self.perform_delete_request(format!("/v1/sync_account/{}/_sync_token", id,).as_str())
|
||||||
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ wasm = ["webauthn-rs-proto/wasm"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base32.workspace = true
|
base32.workspace = true
|
||||||
base64urlsafedata.workspace = true
|
base64urlsafedata.workspace = true
|
||||||
|
scim_proto.workspace = true
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
time = { workspace = true, features = ["serde", "std"] }
|
time = { workspace = true, features = ["serde", "std"] }
|
||||||
|
|
|
@ -1,9 +1,155 @@
|
||||||
use base64urlsafedata::Base64UrlSafeData;
|
use base64urlsafedata::Base64UrlSafeData;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
pub use scim_proto::prelude::{ScimEntry, ScimError};
|
||||||
|
use scim_proto::*;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub enum ScimSyncState {
|
pub enum ScimSyncState {
|
||||||
Initial,
|
Refresh,
|
||||||
Active { cookie: Base64UrlSafeData },
|
Active { cookie: Base64UrlSafeData },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct ScimSyncRequest {
|
||||||
|
pub from_state: ScimSyncState,
|
||||||
|
pub to_state: ScimSyncState,
|
||||||
|
|
||||||
|
// How do I want to represent different entities to kani? Split by type? All in one?
|
||||||
|
pub entries: Vec<ScimEntry>,
|
||||||
|
// Delete uuids?
|
||||||
|
pub delete_uuids: Vec<Uuid>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const SCIM_SCHEMA_SYNC_PERSON: &str = "urn:ietf:params:scim:schemas:kanidm:1.0:sync:person";
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug, Clone)]
|
||||||
|
#[serde(into = "ScimEntry")]
|
||||||
|
pub struct ScimSyncPerson {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub external_id: Option<String>,
|
||||||
|
pub user_name: String,
|
||||||
|
pub display_name: String,
|
||||||
|
pub gidnumber: Option<u32>,
|
||||||
|
pub homedirectory: Option<String>,
|
||||||
|
pub password_import: Option<String>,
|
||||||
|
pub login_shell: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
impl TryFrom<ScimEntry> for ScimSyncPerson {
|
||||||
|
type Error = ScimError;
|
||||||
|
|
||||||
|
fn try_from(_value: ScimEntry) -> Result<Self, Self::Error> {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
impl Into<ScimEntry> for ScimSyncPerson {
|
||||||
|
fn into(self) -> ScimEntry {
|
||||||
|
let ScimSyncPerson {
|
||||||
|
id,
|
||||||
|
external_id,
|
||||||
|
user_name,
|
||||||
|
display_name,
|
||||||
|
gidnumber,
|
||||||
|
homedirectory,
|
||||||
|
password_import,
|
||||||
|
login_shell,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
let schemas = vec![SCIM_SCHEMA_SYNC_PERSON.to_string()];
|
||||||
|
|
||||||
|
let mut attrs = BTreeMap::default();
|
||||||
|
|
||||||
|
set_string!(attrs, "userName", user_name);
|
||||||
|
set_string!(attrs, "displayName", display_name);
|
||||||
|
set_option_u32!(attrs, "gidNumber", gidnumber);
|
||||||
|
set_option_string!(attrs, "homeDirectory", homedirectory);
|
||||||
|
set_option_string!(attrs, "passwordImport", password_import);
|
||||||
|
set_option_string!(attrs, "loginShell", login_shell);
|
||||||
|
|
||||||
|
ScimEntry {
|
||||||
|
schemas,
|
||||||
|
id,
|
||||||
|
external_id,
|
||||||
|
meta: None,
|
||||||
|
attrs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const SCIM_SCHEMA_SYNC_GROUP: &str = "urn:ietf:params:scim:schemas:kanidm:1.0:sync:group";
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug, Clone)]
|
||||||
|
pub struct ScimExternalMember {
|
||||||
|
pub external_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<ScimComplexAttr> for ScimExternalMember {
|
||||||
|
fn into(self) -> ScimComplexAttr {
|
||||||
|
let ScimExternalMember { external_id } = self;
|
||||||
|
let mut attrs = BTreeMap::default();
|
||||||
|
|
||||||
|
attrs.insert(
|
||||||
|
"external_id".to_string(),
|
||||||
|
ScimSimpleAttr::String(external_id),
|
||||||
|
);
|
||||||
|
|
||||||
|
ScimComplexAttr { attrs }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug, Clone)]
|
||||||
|
#[serde(into = "ScimEntry")]
|
||||||
|
pub struct ScimSyncGroup {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub external_id: Option<String>,
|
||||||
|
pub name: String,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub gidnumber: Option<u32>,
|
||||||
|
pub members: Vec<ScimExternalMember>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
impl TryFrom<ScimEntry> for ScimSyncPerson {
|
||||||
|
type Error = ScimError;
|
||||||
|
|
||||||
|
fn try_from(_value: ScimEntry) -> Result<Self, Self::Error> {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
impl Into<ScimEntry> for ScimSyncGroup {
|
||||||
|
fn into(self) -> ScimEntry {
|
||||||
|
let ScimSyncGroup {
|
||||||
|
id,
|
||||||
|
external_id,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
gidnumber,
|
||||||
|
members,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
let schemas = vec![SCIM_SCHEMA_SYNC_GROUP.to_string()];
|
||||||
|
|
||||||
|
let mut attrs = BTreeMap::default();
|
||||||
|
|
||||||
|
set_string!(attrs, "name", name);
|
||||||
|
set_option_u32!(attrs, "gidNumber", gidnumber);
|
||||||
|
set_option_string!(attrs, "description", description);
|
||||||
|
set_multi_complex!(attrs, "members", members);
|
||||||
|
|
||||||
|
ScimEntry {
|
||||||
|
schemas,
|
||||||
|
id,
|
||||||
|
external_id,
|
||||||
|
meta: None,
|
||||||
|
attrs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
name = "kanidm_tools"
|
name = "kanidm_tools"
|
||||||
default-run = "kanidm"
|
default-run = "kanidm"
|
||||||
description = "Kanidm Client Tools"
|
description = "Kanidm Client Tools"
|
||||||
documentation = "https://docs.rs/kanidm_tools/latest/kanidm_tools/"
|
documentation = "https://kanidm.github.io/kanidm/stable/"
|
||||||
|
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
authors.workspace = true
|
authors.workspace = true
|
||||||
|
|
|
@ -30,6 +30,7 @@ pub mod raw;
|
||||||
pub mod recycle;
|
pub mod recycle;
|
||||||
pub mod serviceaccount;
|
pub mod serviceaccount;
|
||||||
pub mod session;
|
pub mod session;
|
||||||
|
pub mod synch;
|
||||||
|
|
||||||
impl SelfOpt {
|
impl SelfOpt {
|
||||||
pub fn debug(&self) -> bool {
|
pub fn debug(&self) -> bool {
|
||||||
|
@ -68,6 +69,7 @@ impl SystemOpt {
|
||||||
SystemOpt::PwBadlist { commands } => commands.debug(),
|
SystemOpt::PwBadlist { commands } => commands.debug(),
|
||||||
SystemOpt::Oauth2 { commands } => commands.debug(),
|
SystemOpt::Oauth2 { commands } => commands.debug(),
|
||||||
SystemOpt::Domain { commands } => commands.debug(),
|
SystemOpt::Domain { commands } => commands.debug(),
|
||||||
|
SystemOpt::Synch { commands } => commands.debug(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,6 +78,7 @@ impl SystemOpt {
|
||||||
SystemOpt::PwBadlist { commands } => commands.exec().await,
|
SystemOpt::PwBadlist { commands } => commands.exec().await,
|
||||||
SystemOpt::Oauth2 { commands } => commands.exec().await,
|
SystemOpt::Oauth2 { commands } => commands.exec().await,
|
||||||
SystemOpt::Domain { commands } => commands.exec().await,
|
SystemOpt::Domain { commands } => commands.exec().await,
|
||||||
|
SystemOpt::Synch { commands } => commands.exec().await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
68
kanidm_tools/src/cli/synch.rs
Normal file
68
kanidm_tools/src/cli/synch.rs
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
use crate::SynchOpt;
|
||||||
|
|
||||||
|
impl SynchOpt {
|
||||||
|
pub fn debug(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
SynchOpt::List(copt) => copt.debug,
|
||||||
|
SynchOpt::Get(nopt) => nopt.copt.debug,
|
||||||
|
SynchOpt::Create { copt, .. }
|
||||||
|
| SynchOpt::GenerateToken { copt, .. }
|
||||||
|
| SynchOpt::DestroyToken { copt, .. } => copt.debug,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn exec(&self) {
|
||||||
|
match self {
|
||||||
|
SynchOpt::List(copt) => {
|
||||||
|
let client = copt.to_client().await;
|
||||||
|
match client.idm_sync_account_list().await {
|
||||||
|
Ok(r) => r.iter().for_each(|ent| println!("{}", ent)),
|
||||||
|
Err(e) => error!("Error -> {:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SynchOpt::Get(nopt) => {
|
||||||
|
let client = nopt.copt.to_client().await;
|
||||||
|
match client.idm_sync_account_get(nopt.name.as_str()).await {
|
||||||
|
Ok(Some(e)) => println!("{}", e),
|
||||||
|
Ok(None) => println!("No matching entries"),
|
||||||
|
Err(e) => error!("Error -> {:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SynchOpt::Create {
|
||||||
|
account_id,
|
||||||
|
copt,
|
||||||
|
description,
|
||||||
|
} => {
|
||||||
|
let client = copt.to_client().await;
|
||||||
|
match client
|
||||||
|
.idm_sync_account_create(&account_id, description.as_deref())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(()) => println!("Success"),
|
||||||
|
Err(e) => error!("Error -> {:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SynchOpt::GenerateToken {
|
||||||
|
account_id,
|
||||||
|
label,
|
||||||
|
copt,
|
||||||
|
} => {
|
||||||
|
let client = copt.to_client().await;
|
||||||
|
match client
|
||||||
|
.idm_sync_account_generate_token(&account_id, &label)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(token) => println!("token: {}", token),
|
||||||
|
Err(e) => error!("Error -> {:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SynchOpt::DestroyToken { account_id, copt } => {
|
||||||
|
let client = copt.to_client().await;
|
||||||
|
match client.idm_sync_account_destroy_token(&account_id).await {
|
||||||
|
Ok(()) => println!("Success"),
|
||||||
|
Err(e) => error!("Error -> {:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -732,6 +732,42 @@ pub enum DomainOpt {
|
||||||
ResetTokenKey(CommonOpt),
|
ResetTokenKey(CommonOpt),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub enum SynchOpt {
|
||||||
|
#[clap(name = "list")]
|
||||||
|
/// List all configured IDM sync accounts
|
||||||
|
List(CommonOpt),
|
||||||
|
#[clap(name = "get")]
|
||||||
|
/// Display a selected IDM sync account
|
||||||
|
Get(Named),
|
||||||
|
/// Create a new IDM sync account
|
||||||
|
#[clap(name = "create")]
|
||||||
|
Create {
|
||||||
|
#[clap()]
|
||||||
|
account_id: String,
|
||||||
|
#[clap(flatten)]
|
||||||
|
copt: CommonOpt,
|
||||||
|
#[clap(name = "description")]
|
||||||
|
description: Option<String>,
|
||||||
|
},
|
||||||
|
#[clap(name = "generate-token")]
|
||||||
|
GenerateToken {
|
||||||
|
#[clap()]
|
||||||
|
account_id: String,
|
||||||
|
#[clap()]
|
||||||
|
label: String,
|
||||||
|
#[clap(flatten)]
|
||||||
|
copt: CommonOpt,
|
||||||
|
},
|
||||||
|
#[clap(name = "destroy-token")]
|
||||||
|
DestroyToken {
|
||||||
|
#[clap()]
|
||||||
|
account_id: String,
|
||||||
|
#[clap(flatten)]
|
||||||
|
copt: CommonOpt,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
pub enum SystemOpt {
|
pub enum SystemOpt {
|
||||||
#[clap(name = "pw-badlist")]
|
#[clap(name = "pw-badlist")]
|
||||||
|
@ -752,6 +788,11 @@ pub enum SystemOpt {
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
commands: DomainOpt,
|
commands: DomainOpt,
|
||||||
},
|
},
|
||||||
|
#[clap(name = "sync", hide = true)]
|
||||||
|
Synch {
|
||||||
|
#[clap(subcommand)]
|
||||||
|
commands: SynchOpt,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
|
|
|
@ -37,7 +37,7 @@ use kanidmd_lib::{
|
||||||
// ===========================================================
|
// ===========================================================
|
||||||
|
|
||||||
pub struct QueryServerReadV1 {
|
pub struct QueryServerReadV1 {
|
||||||
idms: Arc<IdmServer>,
|
pub(crate) idms: Arc<IdmServer>,
|
||||||
ldap: Arc<LdapServer>,
|
ldap: Arc<LdapServer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
use kanidmd_lib::prelude::*;
|
use kanidmd_lib::prelude::*;
|
||||||
|
|
||||||
use crate::QueryServerWriteV1;
|
use crate::{QueryServerReadV1, QueryServerWriteV1};
|
||||||
use kanidmd_lib::idm::scim::GenerateScimSyncTokenEvent;
|
use kanidmd_lib::idm::scim::{GenerateScimSyncTokenEvent, ScimSyncUpdateEvent};
|
||||||
use kanidmd_lib::idm::server::IdmServerTransaction;
|
use kanidmd_lib::idm::server::IdmServerTransaction;
|
||||||
|
|
||||||
|
use kanidm_proto::scim_v1::{ScimSyncRequest, ScimSyncState};
|
||||||
|
|
||||||
impl QueryServerWriteV1 {
|
impl QueryServerWriteV1 {
|
||||||
#[instrument(
|
#[instrument(
|
||||||
level = "info",
|
level = "info",
|
||||||
|
@ -77,4 +79,48 @@ impl QueryServerWriteV1 {
|
||||||
.sync_account_destroy_token(&ident, target, ct)
|
.sync_account_destroy_token(&ident, target, ct)
|
||||||
.and_then(|r| idms_prox_write.commit().map(|_| r))
|
.and_then(|r| idms_prox_write.commit().map(|_| r))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(
|
||||||
|
level = "info",
|
||||||
|
skip_all,
|
||||||
|
fields(uuid = ?eventid)
|
||||||
|
)]
|
||||||
|
pub async fn handle_scim_sync_apply(
|
||||||
|
&self,
|
||||||
|
bearer: Option<String>,
|
||||||
|
changes: ScimSyncRequest,
|
||||||
|
eventid: Uuid,
|
||||||
|
) -> Result<(), OperationError> {
|
||||||
|
let ct = duration_from_epoch_now();
|
||||||
|
let mut idms_prox_write = self.idms.proxy_write(ct).await;
|
||||||
|
|
||||||
|
let ident =
|
||||||
|
idms_prox_write.validate_and_parse_sync_token_to_ident(bearer.as_deref(), ct)?;
|
||||||
|
|
||||||
|
let sse = ScimSyncUpdateEvent { ident };
|
||||||
|
|
||||||
|
idms_prox_write
|
||||||
|
.scim_sync_apply(&sse, &changes, ct)
|
||||||
|
.and_then(|r| idms_prox_write.commit().map(|_| r))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QueryServerReadV1 {
|
||||||
|
#[instrument(
|
||||||
|
level = "info",
|
||||||
|
skip_all,
|
||||||
|
fields(uuid = ?eventid)
|
||||||
|
)]
|
||||||
|
pub async fn handle_scim_sync_status(
|
||||||
|
&self,
|
||||||
|
bearer: Option<String>,
|
||||||
|
eventid: Uuid,
|
||||||
|
) -> Result<ScimSyncState, OperationError> {
|
||||||
|
let ct = duration_from_epoch_now();
|
||||||
|
let idms_prox_read = self.idms.proxy_read().await;
|
||||||
|
|
||||||
|
let ident = idms_prox_read.validate_and_parse_sync_token_to_ident(bearer.as_deref(), ct)?;
|
||||||
|
|
||||||
|
idms_prox_read.scim_sync_get_state(&ident)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,7 @@ pub struct AppState {
|
||||||
pub trait RequestExtensions {
|
pub trait RequestExtensions {
|
||||||
fn get_current_uat(&self) -> Option<String>;
|
fn get_current_uat(&self) -> Option<String>;
|
||||||
|
|
||||||
fn get_auth_bearer(&self) -> Option<&str>;
|
fn get_auth_bearer(&self) -> Option<String>;
|
||||||
|
|
||||||
fn get_current_auth_session_id(&self) -> Option<Uuid>;
|
fn get_current_auth_session_id(&self) -> Option<Uuid>;
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ pub trait RequestExtensions {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequestExtensions for tide::Request<AppState> {
|
impl RequestExtensions for tide::Request<AppState> {
|
||||||
fn get_auth_bearer(&self) -> Option<&str> {
|
fn get_auth_bearer(&self) -> Option<String> {
|
||||||
// Contact the QS to get it to validate wtf is up.
|
// Contact the QS to get it to validate wtf is up.
|
||||||
// let kref = &self.state().bundy_handle;
|
// let kref = &self.state().bundy_handle;
|
||||||
// self.session().get::<UserAuthToken>("uat")
|
// self.session().get::<UserAuthToken>("uat")
|
||||||
|
@ -97,6 +97,7 @@ impl RequestExtensions for tide::Request<AppState> {
|
||||||
// Turn it to a &str, and then check the prefix
|
// Turn it to a &str, and then check the prefix
|
||||||
h.as_str().strip_prefix("Bearer ")
|
h.as_str().strip_prefix("Bearer ")
|
||||||
})
|
})
|
||||||
|
.map(str::to_string)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_current_uat(&self) -> Option<String> {
|
fn get_current_uat(&self) -> Option<String> {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use super::routemaps::{RouteMap, RouteMaps};
|
use super::routemaps::{RouteMap, RouteMaps};
|
||||||
use super::{to_tide_response, AppState, RequestExtensions};
|
use super::{to_tide_response, AppState, RequestExtensions};
|
||||||
|
use kanidm_proto::scim_v1::ScimSyncRequest;
|
||||||
use kanidm_proto::v1::Entry as ProtoEntry;
|
use kanidm_proto::v1::Entry as ProtoEntry;
|
||||||
use kanidmd_lib::prelude::*;
|
use kanidmd_lib::prelude::*;
|
||||||
|
|
||||||
|
@ -93,32 +94,36 @@ pub async fn sync_account_token_delete(req: tide::Request<AppState>) -> tide::Re
|
||||||
to_tide_response(res, hvalue)
|
to_tide_response(res, hvalue)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn scim_sync_post(_req: tide::Request<AppState>) -> tide::Result {
|
async fn scim_sync_post(mut req: tide::Request<AppState>) -> tide::Result {
|
||||||
// let (eventid, hvalue) = req.new_eventid();
|
let (eventid, hvalue) = req.new_eventid();
|
||||||
/*
|
|
||||||
let ApiTokenGenerate {
|
|
||||||
label,
|
|
||||||
expiry,
|
|
||||||
read_write,
|
|
||||||
} = req.body_json().await?;
|
|
||||||
*/
|
|
||||||
|
|
||||||
// We need to deserialise the body.
|
// Given the token, and a sync update, apply the changes if any
|
||||||
|
let bearer = req.get_auth_bearer();
|
||||||
|
|
||||||
Ok(tide::Response::new(500))
|
// Change this type later.
|
||||||
|
let changes: ScimSyncRequest = req.body_json().await?;
|
||||||
|
|
||||||
|
let res = req
|
||||||
|
.state()
|
||||||
|
.qe_w_ref
|
||||||
|
.handle_scim_sync_apply(bearer, changes, eventid)
|
||||||
|
.await;
|
||||||
|
to_tide_response(res, hvalue)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn scim_sync_get(_req: tide::Request<AppState>) -> tide::Result {
|
async fn scim_sync_get(req: tide::Request<AppState>) -> tide::Result {
|
||||||
// let (eventid, hvalue) = req.new_eventid();
|
let (eventid, hvalue) = req.new_eventid();
|
||||||
|
|
||||||
// let bearer = req.get_auth_bearer();
|
// Given the token, what is it's connected sync state?
|
||||||
|
let bearer = req.get_auth_bearer();
|
||||||
|
trace!(?bearer);
|
||||||
|
|
||||||
// Given the token
|
let res = req
|
||||||
// What is the connected sync session
|
.state()
|
||||||
// Issue it's current state (version) cookie.
|
.qe_r_ref
|
||||||
|
.handle_scim_sync_status(bearer, eventid)
|
||||||
// todo!();
|
.await;
|
||||||
Ok(tide::Response::new(500))
|
to_tide_response(res, hvalue)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn scim_sink_get(req: tide::Request<AppState>) -> tide::Result {
|
async fn scim_sink_get(req: tide::Request<AppState>) -> tide::Result {
|
||||||
|
|
|
@ -1327,7 +1327,8 @@ pub const JSON_IDM_HP_ACP_SYNC_ACCOUNT_MANAGE_PRIV_V1: &str = r#"{
|
||||||
],
|
],
|
||||||
"acp_modify_presentattr": [
|
"acp_modify_presentattr": [
|
||||||
"name",
|
"name",
|
||||||
"description"
|
"description",
|
||||||
|
"sync_token_session"
|
||||||
],
|
],
|
||||||
"acp_modify_class": [],
|
"acp_modify_class": [],
|
||||||
"acp_create_attr": [
|
"acp_create_attr": [
|
||||||
|
|
|
@ -1709,7 +1709,7 @@ impl Entry<EntryReduced, EntryCommitted> {
|
||||||
// so they are all in "ldap forms" which makes our next stage a bit easier.
|
// so they are all in "ldap forms" which makes our next stage a bit easier.
|
||||||
|
|
||||||
// Stage 1 - transform our results to a map of kani attr -> ldap value.
|
// Stage 1 - transform our results to a map of kani attr -> ldap value.
|
||||||
let attr_map: Result<Map<&str, Vec<String>>, _> = self
|
let attr_map: Result<Map<&str, Vec<Vec<u8>>>, _> = self
|
||||||
.attrs
|
.attrs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(k, vs)| {
|
.map(|(k, vs)| {
|
||||||
|
@ -1717,8 +1717,8 @@ impl Entry<EntryReduced, EntryCommitted> {
|
||||||
.map(|pvs| (k.as_str(), pvs))
|
.map(|pvs| (k.as_str(), pvs))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let attr_map = attr_map?;
|
let attr_map = attr_map?;
|
||||||
|
|
||||||
// Stage 2 - transform and get all our attr - names out that we need to return.
|
// Stage 2 - transform and get all our attr - names out that we need to return.
|
||||||
// ldap a, kani a
|
// ldap a, kani a
|
||||||
let attr_names: Vec<(&str, &str)> = if all_attrs {
|
let attr_names: Vec<(&str, &str)> = if all_attrs {
|
||||||
|
@ -1748,7 +1748,7 @@ impl Entry<EntryReduced, EntryCommitted> {
|
||||||
match ldap_a {
|
match ldap_a {
|
||||||
"entrydn" => Some(LdapPartialAttribute {
|
"entrydn" => Some(LdapPartialAttribute {
|
||||||
atype: "entrydn".to_string(),
|
atype: "entrydn".to_string(),
|
||||||
vals: vec![dn.clone()],
|
vals: vec![dn.as_bytes().to_vec()],
|
||||||
}),
|
}),
|
||||||
_ => attr_map.get(kani_a).map(|pvs| LdapPartialAttribute {
|
_ => attr_map.get(kani_a).map(|pvs| LdapPartialAttribute {
|
||||||
atype: ldap_a.to_string(),
|
atype: ldap_a.to_string(),
|
||||||
|
|
|
@ -4,6 +4,7 @@ use std::time::Duration;
|
||||||
use base64urlsafedata::Base64UrlSafeData;
|
use base64urlsafedata::Base64UrlSafeData;
|
||||||
|
|
||||||
use compact_jwt::{Jws, JwsSigner};
|
use compact_jwt::{Jws, JwsSigner};
|
||||||
|
use kanidm_proto::scim_v1::ScimSyncRequest;
|
||||||
use kanidm_proto::scim_v1::*;
|
use kanidm_proto::scim_v1::*;
|
||||||
use kanidm_proto::v1::ApiTokenPurpose;
|
use kanidm_proto::v1::ApiTokenPurpose;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -225,14 +226,38 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ScimSyncUpdateEvent {}
|
pub struct ScimSyncUpdateEvent {
|
||||||
|
pub ident: Identity,
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> IdmServerProxyWriteTransaction<'a> {
|
impl<'a> IdmServerProxyWriteTransaction<'a> {
|
||||||
pub fn scim_sync_update(
|
pub fn scim_sync_apply(
|
||||||
&mut self,
|
&mut self,
|
||||||
_sse: &ScimSyncUpdateEvent,
|
sse: &ScimSyncUpdateEvent,
|
||||||
|
_changes: &ScimSyncRequest,
|
||||||
_ct: Duration,
|
_ct: Duration,
|
||||||
) -> Result<(), OperationError> {
|
) -> Result<(), OperationError> {
|
||||||
|
let _sync_uuid = match &sse.ident.origin {
|
||||||
|
IdentType::User(_) | IdentType::Internal => {
|
||||||
|
warn!("Ident type is not synchronise");
|
||||||
|
return Err(OperationError::AccessDenied);
|
||||||
|
}
|
||||||
|
IdentType::Synch(u) => {
|
||||||
|
// Ok!
|
||||||
|
u
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match sse.ident.access_scope() {
|
||||||
|
AccessScope::IdentityOnly | AccessScope::ReadOnly | AccessScope::ReadWrite => {
|
||||||
|
warn!("Ident access scope is not synchronise");
|
||||||
|
return Err(OperationError::AccessDenied);
|
||||||
|
}
|
||||||
|
AccessScope::Synchronise => {
|
||||||
|
// As you were
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Only update entries related to this uuid
|
// Only update entries related to this uuid
|
||||||
// Make a sync_authority uuid to relate back to on creates.
|
// Make a sync_authority uuid to relate back to on creates.
|
||||||
|
|
||||||
|
@ -283,7 +308,7 @@ impl<'a> IdmServerProxyReadTransaction<'a> {
|
||||||
Some(b) => ScimSyncState::Active {
|
Some(b) => ScimSyncState::Active {
|
||||||
cookie: Base64UrlSafeData(b.to_vec()),
|
cookie: Base64UrlSafeData(b.to_vec()),
|
||||||
},
|
},
|
||||||
None => ScimSyncState::Initial,
|
None => ScimSyncState::Refresh,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -300,7 +325,7 @@ mod tests {
|
||||||
use kanidm_proto::v1::ApiTokenPurpose;
|
use kanidm_proto::v1::ApiTokenPurpose;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use super::{GenerateScimSyncTokenEvent, ScimSyncToken};
|
use super::{GenerateScimSyncTokenEvent, ScimSyncToken, ScimSyncUpdateEvent};
|
||||||
|
|
||||||
use async_std::task;
|
use async_std::task;
|
||||||
|
|
||||||
|
@ -359,7 +384,7 @@ mod tests {
|
||||||
.expect("Failed to get current sync state");
|
.expect("Failed to get current sync state");
|
||||||
trace!(?sync_state);
|
trace!(?sync_state);
|
||||||
|
|
||||||
assert!(matches!(sync_state, ScimSyncState::Initial));
|
assert!(matches!(sync_state, ScimSyncState::Refresh));
|
||||||
|
|
||||||
drop(idms_prox_read);
|
drop(idms_prox_read);
|
||||||
|
|
||||||
|
@ -497,4 +522,141 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need to delete different phases such as conflictn and end of the agreement.
|
// Need to delete different phases such as conflictn and end of the agreement.
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_idm_scim_sync_refresh_1() {
|
||||||
|
run_idm_test!(|_qs: &QueryServer,
|
||||||
|
idms: &IdmServer,
|
||||||
|
_idms_delayed: &mut IdmServerDelayed| {
|
||||||
|
let ct = Duration::from_secs(TEST_CURRENT_TIME);
|
||||||
|
|
||||||
|
let mut idms_prox_write = task::block_on(idms.proxy_write(ct));
|
||||||
|
|
||||||
|
let sync_uuid = Uuid::new_v4();
|
||||||
|
|
||||||
|
let e1 = entry_init!(
|
||||||
|
("class", Value::new_class("object")),
|
||||||
|
("class", Value::new_class("sync_account")),
|
||||||
|
("name", Value::new_iname("test_scim_sync")),
|
||||||
|
("uuid", Value::new_uuid(sync_uuid)),
|
||||||
|
("description", Value::new_utf8s("A test sync agreement"))
|
||||||
|
);
|
||||||
|
|
||||||
|
let ce = CreateEvent::new_internal(vec![e1]);
|
||||||
|
let cr = idms_prox_write.qs_write.create(&ce);
|
||||||
|
assert!(cr.is_ok());
|
||||||
|
|
||||||
|
let gte = GenerateScimSyncTokenEvent::new_internal(sync_uuid, "Sync Connector");
|
||||||
|
|
||||||
|
let sync_token = idms_prox_write
|
||||||
|
.scim_sync_generate_token(>e, ct)
|
||||||
|
.expect("failed to generate new scim sync token");
|
||||||
|
|
||||||
|
let ident = idms_prox_write
|
||||||
|
.validate_and_parse_sync_token_to_ident(Some(sync_token.as_str()), ct)
|
||||||
|
.expect("Failed to process sync token to ident");
|
||||||
|
|
||||||
|
let sse = ScimSyncUpdateEvent { ident };
|
||||||
|
|
||||||
|
let changes =
|
||||||
|
serde_json::from_str(TEST_SYNC_SCIM_IPA_1).expect("failed to parse scim sync");
|
||||||
|
|
||||||
|
let res = idms_prox_write.scim_sync_apply(&sse, &changes, ct);
|
||||||
|
|
||||||
|
// Currently in testing this is just access denied.
|
||||||
|
assert!(matches!(res, Err(OperationError::AccessDenied)));
|
||||||
|
|
||||||
|
assert!(idms_prox_write.commit().is_ok());
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const TEST_SYNC_SCIM_IPA_1: &str = r#"
|
||||||
|
{
|
||||||
|
"from_state": "Refresh",
|
||||||
|
"to_state": {
|
||||||
|
"Active": {
|
||||||
|
"cookie": "aXBhLXN5bmNyZXBsLWthbmkuZGV2LmJsYWNraGF0cy5uZXQuYXU6Mzg5I2NuPWRpcmVjdG9yeSBtYW5hZ2VyOmRjPWRldixkYz1ibGFja2hhdHMsZGM9bmV0LGRjPWF1Oih8KCYob2JqZWN0Q2xhc3M9cGVyc29uKShvYmplY3RDbGFzcz1pcGFudHVzZXJhdHRycykob2JqZWN0Q2xhc3M9cG9zaXhhY2NvdW50KSkoJihvYmplY3RDbGFzcz1ncm91cG9mbmFtZXMpKG9iamVjdENsYXNzPWlwYXVzZXJncm91cCkoIShvYmplY3RDbGFzcz1tZXBtYW5hZ2VkZW50cnkpKSghKGNuPWFkbWlucykpKCEoY249aXBhdXNlcnMpKSkoJihvYmplY3RDbGFzcz1pcGF0b2tlbikob2JqZWN0Q2xhc3M9aXBhdG9rZW50b3RwKSkpIzEwOQ"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"schemas": [
|
||||||
|
"urn:ietf:params:scim:schemas:kanidm:1.0:sync:person"
|
||||||
|
],
|
||||||
|
"id": "ac60034b-3498-11ed-a50d-919b4b1a5ec0",
|
||||||
|
"externalId": "uid=admin,cn=users,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"displayName": "Administrator",
|
||||||
|
"gidNumber": 8200000,
|
||||||
|
"homeDirectory": "/home/admin",
|
||||||
|
"loginShell": "/bin/bash",
|
||||||
|
"passwordImport": "CVBguEizG80swI8sftaknw",
|
||||||
|
"userName": "admin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"schemas": [
|
||||||
|
"urn:ietf:params:scim:schemas:kanidm:1.0:sync:group"
|
||||||
|
],
|
||||||
|
"id": "ac60034e-3498-11ed-a50d-919b4b1a5ec0",
|
||||||
|
"externalId": "cn=editors,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"description": "Limited admins who can edit other users",
|
||||||
|
"gidNumber": 8200002,
|
||||||
|
"name": "editors"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"schemas": [
|
||||||
|
"urn:ietf:params:scim:schemas:kanidm:1.0:sync:group"
|
||||||
|
],
|
||||||
|
"id": "0c56a965-3499-11ed-a50d-919b4b1a5ec0",
|
||||||
|
"externalId": "cn=trust admins,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"description": "Trusts administrators group",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"external_id": "uid=admin,cn=users,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "trust admins"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"schemas": [
|
||||||
|
"urn:ietf:params:scim:schemas:kanidm:1.0:sync:person"
|
||||||
|
],
|
||||||
|
"id": "babb8302-43a1-11ed-a50d-919b4b1a5ec0",
|
||||||
|
"externalId": "uid=testuser,cn=users,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"displayName": "Test User",
|
||||||
|
"gidNumber": 12345,
|
||||||
|
"homeDirectory": "/home/testuser",
|
||||||
|
"loginShell": "/bin/sh",
|
||||||
|
"passwordImport": "iEb36u6PsRetBr3YMLdYbA",
|
||||||
|
"userName": "testuser"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"schemas": [
|
||||||
|
"urn:ietf:params:scim:schemas:kanidm:1.0:sync:group"
|
||||||
|
],
|
||||||
|
"id": "d547c581-5f26-11ed-a50d-919b4b1a5ec0",
|
||||||
|
"externalId": "cn=testgroup,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"description": "Test group",
|
||||||
|
"name": "testgroup"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"schemas": [
|
||||||
|
"urn:ietf:params:scim:schemas:kanidm:1.0:sync:group"
|
||||||
|
],
|
||||||
|
"id": "d547c583-5f26-11ed-a50d-919b4b1a5ec0",
|
||||||
|
"externalId": "cn=testexternal,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"name": "testexternal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"schemas": [
|
||||||
|
"urn:ietf:params:scim:schemas:kanidm:1.0:sync:group"
|
||||||
|
],
|
||||||
|
"id": "f90b0b81-5f26-11ed-a50d-919b4b1a5ec0",
|
||||||
|
"externalId": "cn=testposix,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au",
|
||||||
|
"gidNumber": 1234567,
|
||||||
|
"name": "testposix"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"delete_uuids": []
|
||||||
|
}
|
||||||
|
"#;
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,31 +86,31 @@ impl LdapServer {
|
||||||
attributes: vec![
|
attributes: vec![
|
||||||
LdapPartialAttribute {
|
LdapPartialAttribute {
|
||||||
atype: "objectClass".to_string(),
|
atype: "objectClass".to_string(),
|
||||||
vals: vec!["top".to_string()],
|
vals: vec!["top".as_bytes().to_vec()],
|
||||||
},
|
},
|
||||||
LdapPartialAttribute {
|
LdapPartialAttribute {
|
||||||
atype: "vendorName".to_string(),
|
atype: "vendorName".to_string(),
|
||||||
vals: vec!["Kanidm Project".to_string()],
|
vals: vec!["Kanidm Project".as_bytes().to_vec()],
|
||||||
},
|
},
|
||||||
LdapPartialAttribute {
|
LdapPartialAttribute {
|
||||||
atype: "vendorVersion".to_string(),
|
atype: "vendorVersion".to_string(),
|
||||||
vals: vec!["kanidm_ldap_1.0.0".to_string()],
|
vals: vec!["kanidm_ldap_1.0.0".as_bytes().to_vec()],
|
||||||
},
|
},
|
||||||
LdapPartialAttribute {
|
LdapPartialAttribute {
|
||||||
atype: "supportedLDAPVersion".to_string(),
|
atype: "supportedLDAPVersion".to_string(),
|
||||||
vals: vec!["3".to_string()],
|
vals: vec!["3".as_bytes().to_vec()],
|
||||||
},
|
},
|
||||||
LdapPartialAttribute {
|
LdapPartialAttribute {
|
||||||
atype: "supportedExtension".to_string(),
|
atype: "supportedExtension".to_string(),
|
||||||
vals: vec!["1.3.6.1.4.1.4203.1.11.3".to_string()],
|
vals: vec!["1.3.6.1.4.1.4203.1.11.3".as_bytes().to_vec()],
|
||||||
},
|
},
|
||||||
LdapPartialAttribute {
|
LdapPartialAttribute {
|
||||||
atype: "supportedFeatures".to_string(),
|
atype: "supportedFeatures".to_string(),
|
||||||
vals: vec!["1.3.6.1.4.1.4203.1.5.1".to_string()],
|
vals: vec!["1.3.6.1.4.1.4203.1.5.1".as_bytes().to_vec()],
|
||||||
},
|
},
|
||||||
LdapPartialAttribute {
|
LdapPartialAttribute {
|
||||||
atype: "defaultnamingcontext".to_string(),
|
atype: "defaultnamingcontext".to_string(),
|
||||||
vals: vec![basedn.clone()],
|
vals: vec![basedn.as_bytes().to_vec()],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
@ -717,12 +717,12 @@ mod tests {
|
||||||
let mut attrs = HashSet::new();
|
let mut attrs = HashSet::new();
|
||||||
for a in $e.attributes.iter() {
|
for a in $e.attributes.iter() {
|
||||||
for v in a.vals.iter() {
|
for v in a.vals.iter() {
|
||||||
attrs.insert((a.atype.as_str(), v.as_str()));
|
attrs.insert((a.atype.as_str(), v.as_slice()));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
$(
|
$(
|
||||||
assert!(attrs.contains(&(
|
assert!(attrs.contains(&(
|
||||||
$item.0, $item.1
|
$item.0, $item.1.as_bytes()
|
||||||
)));
|
)));
|
||||||
)*
|
)*
|
||||||
|
|
||||||
|
|
|
@ -688,22 +688,25 @@ pub trait QueryServerTransaction<'a> {
|
||||||
&self,
|
&self,
|
||||||
value: &ValueSet,
|
value: &ValueSet,
|
||||||
basedn: &str,
|
basedn: &str,
|
||||||
) -> Result<Vec<String>, OperationError> {
|
) -> Result<Vec<Vec<u8>>, OperationError> {
|
||||||
if let Some(r_set) = value.as_refer_set() {
|
if let Some(r_set) = value.as_refer_set() {
|
||||||
let v: Result<Vec<_>, _> = r_set
|
let v: Result<Vec<_>, _> = r_set
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.map(|ur| {
|
.map(|ur| {
|
||||||
let rdn = self.uuid_to_rdn(ur)?;
|
let rdn = self.uuid_to_rdn(ur)?;
|
||||||
Ok(format!("{},{}", rdn, basedn))
|
Ok(format!("{},{}", rdn, basedn).into_bytes())
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
v
|
v
|
||||||
} else if let Some(k_set) = value.as_sshkey_map() {
|
} else if let Some(k_set) = value.as_sshkey_map() {
|
||||||
let v: Vec<_> = k_set.values().cloned().collect();
|
let v: Vec<_> = k_set.values().cloned().map(|s| s.into_bytes()).collect();
|
||||||
Ok(v)
|
Ok(v)
|
||||||
} else {
|
} else {
|
||||||
let v: Vec<_> = value.to_proto_string_clone_iter().collect();
|
let v: Vec<_> = value
|
||||||
|
.to_proto_string_clone_iter()
|
||||||
|
.map(|s| s.into_bytes())
|
||||||
|
.collect();
|
||||||
Ok(v)
|
Ok(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,11 +97,14 @@ impl DirectoryServer {
|
||||||
attributes: vec![
|
attributes: vec![
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "objectClass".to_string(),
|
atype: "objectClass".to_string(),
|
||||||
vals: vec!["top".to_string(), "organizationalUnit".to_string()],
|
vals: vec![
|
||||||
|
"top".as_bytes().into(),
|
||||||
|
"organizationalUnit".as_bytes().into(),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "ou".to_string(),
|
atype: "ou".to_string(),
|
||||||
vals: vec!["people".to_string()],
|
vals: vec!["people".as_bytes().into()],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
@ -121,11 +124,14 @@ impl DirectoryServer {
|
||||||
attributes: vec![
|
attributes: vec![
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "objectClass".to_string(),
|
atype: "objectClass".to_string(),
|
||||||
vals: vec!["top".to_string(), "organizationalUnit".to_string()],
|
vals: vec![
|
||||||
|
"top".as_bytes().into(),
|
||||||
|
"organizationalUnit".as_bytes().into(),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "ou".to_string(),
|
atype: "ou".to_string(),
|
||||||
vals: vec!["groups".to_string()],
|
vals: vec!["groups".as_bytes().into()],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
@ -155,40 +161,40 @@ impl DirectoryServer {
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "objectClass".to_string(),
|
atype: "objectClass".to_string(),
|
||||||
vals: vec![
|
vals: vec![
|
||||||
"top".to_string(),
|
"top".as_bytes().into(),
|
||||||
"nsPerson".to_string(),
|
"nsPerson".as_bytes().into(),
|
||||||
"nsAccount".to_string(),
|
"nsAccount".as_bytes().into(),
|
||||||
"nsOrgPerson".to_string(),
|
"nsOrgPerson".as_bytes().into(),
|
||||||
"posixAccount".to_string(),
|
"posixAccount".as_bytes().into(),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "cn".to_string(),
|
atype: "cn".to_string(),
|
||||||
vals: vec![a.uuid.to_string()],
|
vals: vec![a.uuid.as_bytes().to_vec()],
|
||||||
},
|
},
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "uid".to_string(),
|
atype: "uid".to_string(),
|
||||||
vals: vec![a.name.clone()],
|
vals: vec![a.name.as_bytes().into()],
|
||||||
},
|
},
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "displayName".to_string(),
|
atype: "displayName".to_string(),
|
||||||
vals: vec![a.display_name.clone()],
|
vals: vec![a.display_name.as_bytes().into()],
|
||||||
},
|
},
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "userPassword".to_string(),
|
atype: "userPassword".to_string(),
|
||||||
vals: vec![a.password.clone()],
|
vals: vec![a.password.as_bytes().into()],
|
||||||
},
|
},
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "homeDirectory".to_string(),
|
atype: "homeDirectory".to_string(),
|
||||||
vals: vec![format!("/home/{}", a.uuid)],
|
vals: vec![format!("/home/{}", a.uuid).as_bytes().into()],
|
||||||
},
|
},
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "uidNumber".to_string(),
|
atype: "uidNumber".to_string(),
|
||||||
vals: vec!["1000".to_string()],
|
vals: vec!["1000".as_bytes().into()],
|
||||||
},
|
},
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "gidNumber".to_string(),
|
atype: "gidNumber".to_string(),
|
||||||
vals: vec!["1000".to_string()],
|
vals: vec!["1000".as_bytes().into()],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
@ -200,11 +206,14 @@ impl DirectoryServer {
|
||||||
attributes: vec![
|
attributes: vec![
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "objectClass".to_string(),
|
atype: "objectClass".to_string(),
|
||||||
vals: vec!["top".to_string(), "groupOfNames".to_string()],
|
vals: vec![
|
||||||
|
"top".as_bytes().into(),
|
||||||
|
"groupOfNames".as_bytes().into(),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "cn".to_string(),
|
atype: "cn".to_string(),
|
||||||
vals: vec![g.uuid.to_string(), g.name.clone()],
|
vals: vec![g.uuid.as_bytes().to_vec(), g.name.as_bytes().into()],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
@ -222,7 +231,7 @@ impl DirectoryServer {
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
// List of dns
|
// List of dns
|
||||||
let vals: Vec<_> = g
|
let vals: Vec<Vec<u8>> = g
|
||||||
.members
|
.members
|
||||||
.iter()
|
.iter()
|
||||||
.map(|id| {
|
.map(|id| {
|
||||||
|
@ -230,6 +239,8 @@ impl DirectoryServer {
|
||||||
.get(id)
|
.get(id)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.get_ds_ldap_dn(&self.ldap.basedn)
|
.get_ds_ldap_dn(&self.ldap.basedn)
|
||||||
|
.as_bytes()
|
||||||
|
.into()
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -270,11 +281,11 @@ impl DirectoryServer {
|
||||||
attributes: vec![
|
attributes: vec![
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "objectClass".to_string(),
|
atype: "objectClass".to_string(),
|
||||||
vals: vec!["top".to_string(), "groupOfNames".to_string()],
|
vals: vec!["top".as_bytes().into(), "groupOfNames".as_bytes().into()],
|
||||||
},
|
},
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "cn".to_string(),
|
atype: "cn".to_string(),
|
||||||
vals: vec!["priv_account_manage".to_string()],
|
vals: vec!["priv_account_manage".as_bytes().into()],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
@ -297,11 +308,11 @@ impl DirectoryServer {
|
||||||
attributes: vec![
|
attributes: vec![
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "objectClass".to_string(),
|
atype: "objectClass".to_string(),
|
||||||
vals: vec!["top".to_string(), "groupOfNames".to_string()],
|
vals: vec!["top".as_bytes().into(), "groupOfNames".as_bytes().into()],
|
||||||
},
|
},
|
||||||
LdapAttribute {
|
LdapAttribute {
|
||||||
atype: "cn".to_string(),
|
atype: "cn".to_string(),
|
||||||
vals: vec!["priv_group_manage".to_string()],
|
vals: vec!["priv_group_manage".as_bytes().into()],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
@ -317,14 +328,14 @@ impl DirectoryServer {
|
||||||
modification: LdapPartialAttribute {
|
modification: LdapPartialAttribute {
|
||||||
atype: "aci".to_string(),
|
atype: "aci".to_string(),
|
||||||
vals: vec![
|
vals: vec![
|
||||||
r#"(targetattr="dc || description || objectClass")(targetfilter="(objectClass=domain)")(version 3.0; acl "Enable anyone domain read"; allow (read, search, compare)(userdn="ldap:///anyone");)"#.to_string(),
|
r#"(targetattr="dc || description || objectClass")(targetfilter="(objectClass=domain)")(version 3.0; acl "Enable anyone domain read"; allow (read, search, compare)(userdn="ldap:///anyone");)"#.as_bytes().into(),
|
||||||
|
|
||||||
r#"(targetattr="ou || objectClass")(targetfilter="(objectClass=organizationalUnit)")(version 3.0; acl "Enable anyone ou read"; allow (read, search, compare)(userdn="ldap:///anyone");)"#.to_string(),
|
r#"(targetattr="ou || objectClass")(targetfilter="(objectClass=organizationalUnit)")(version 3.0; acl "Enable anyone ou read"; allow (read, search, compare)(userdn="ldap:///anyone");)"#.as_bytes().into(),
|
||||||
r#"(targetattr="cn || member || gidNumber || nsUniqueId || description || objectClass")(targetfilter="(objectClass=groupOfNames)")(version 3.0; acl "Enable anyone group read"; allow (read, search, compare)(userdn="ldap:///anyone");)"#.to_string(),
|
r#"(targetattr="cn || member || gidNumber || nsUniqueId || description || objectClass")(targetfilter="(objectClass=groupOfNames)")(version 3.0; acl "Enable anyone group read"; allow (read, search, compare)(userdn="ldap:///anyone");)"#.as_bytes().into(),
|
||||||
format!(r#"(targetattr="cn || member || gidNumber || description || objectClass")(targetfilter="(objectClass=groupOfNames)")(version 3.0; acl "Enable group_admin to manage groups"; allow (write,add, delete)(groupdn="ldap:///cn=priv_group_manage,{}");)"#, self.ldap.basedn),
|
format!(r#"(targetattr="cn || member || gidNumber || description || objectClass")(targetfilter="(objectClass=groupOfNames)")(version 3.0; acl "Enable group_admin to manage groups"; allow (write,add, delete)(groupdn="ldap:///cn=priv_group_manage,{}");)"#, self.ldap.basedn).as_bytes().into(),
|
||||||
r#"(targetattr="objectClass || description || nsUniqueId || uid || displayName || loginShell || uidNumber || gidNumber || gecos || homeDirectory || cn || memberOf || mail || nsSshPublicKey || nsAccountLock || userCertificate")(targetfilter="(objectClass=posixaccount)")(version 3.0; acl "Enable anyone user read"; allow (read, search, compare)(userdn="ldap:///anyone");)"#.to_string(),
|
r#"(targetattr="objectClass || description || nsUniqueId || uid || displayName || loginShell || uidNumber || gidNumber || gecos || homeDirectory || cn || memberOf || mail || nsSshPublicKey || nsAccountLock || userCertificate")(targetfilter="(objectClass=posixaccount)")(version 3.0; acl "Enable anyone user read"; allow (read, search, compare)(userdn="ldap:///anyone");)"#.as_bytes().into(),
|
||||||
r#"(targetattr="displayName || legalName || userPassword || nsSshPublicKey")(version 3.0; acl "Enable self partial modify"; allow (write)(userdn="ldap:///self");)"#.to_string(),
|
r#"(targetattr="displayName || legalName || userPassword || nsSshPublicKey")(version 3.0; acl "Enable self partial modify"; allow (write)(userdn="ldap:///self");)"#.as_bytes().into(),
|
||||||
format!(r#"(targetattr="uid || description || displayName || loginShell || uidNumber || gidNumber || gecos || homeDirectory || cn || memberOf || mail || legalName || telephoneNumber || mobile")(targetfilter="(&(objectClass=nsPerson)(objectClass=nsAccount))")(version 3.0; acl "Enable user admin create"; allow (write, add, delete, read)(groupdn="ldap:///cn=priv_account_manage,{}");)"#, self.ldap.basedn),
|
format!(r#"(targetattr="uid || description || displayName || loginShell || uidNumber || gidNumber || gecos || homeDirectory || cn || memberOf || mail || legalName || telephoneNumber || mobile")(targetfilter="(&(objectClass=nsPerson)(objectClass=nsAccount))")(version 3.0; acl "Enable user admin create"; allow (write, add, delete, read)(groupdn="ldap:///cn=priv_account_manage,{}");)"#, self.ldap.basedn).as_bytes().into(),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -352,10 +363,20 @@ impl DirectoryServer {
|
||||||
== 0;
|
== 0;
|
||||||
|
|
||||||
if need_account {
|
if need_account {
|
||||||
priv_account.push(account.get_ds_ldap_dn(&self.ldap.basedn))
|
priv_account.push(
|
||||||
|
account
|
||||||
|
.get_ds_ldap_dn(&self.ldap.basedn)
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if need_group {
|
if need_group {
|
||||||
priv_group.push(account.get_ds_ldap_dn(&self.ldap.basedn))
|
priv_group.push(
|
||||||
|
account
|
||||||
|
.get_ds_ldap_dn(&self.ldap.basedn)
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue