1737 1739 sync - map uidnumbers mail (#1741)

This commit is contained in:
Firstyear 2023-06-16 19:15:36 +10:00 committed by GitHub
parent c65be8174a
commit 6513fae5e2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 201 additions and 67 deletions

37
Cargo.lock generated
View file

@ -95,9 +95,9 @@ dependencies = [
[[package]] [[package]]
name = "allocator-api2" name = "allocator-api2"
version = "0.2.14" version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4f263788a35611fba42eb41ff811c5d0360c58b97402570312a350736e2542e" checksum = "56fc6cf8dc8c4158eed8649f9b8b0ea1518eb62b544fe9490d66fa0b349eafe9"
[[package]] [[package]]
name = "android-tzdata" name = "android-tzdata"
@ -477,7 +477,7 @@ dependencies = [
"serde_bytes", "serde_bytes",
"serde_cbor", "serde_cbor",
"serde_json", "serde_json",
"sha2 0.10.6", "sha2 0.10.7",
"winapi", "winapi",
] ]
@ -1006,9 +1006,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.7" version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@ -3072,9 +3072,9 @@ dependencies = [
[[package]] [[package]]
name = "notify" name = "notify"
version = "6.0.0" version = "6.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d9ba6c734de18ca27c8cef5cd7058aa4ac9f63596131e4c7e41e579319032a2" checksum = "5738a2795d57ea20abec2d6d76c6081186709c0024187cd5977265eda6598b51"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
"crossbeam-channel", "crossbeam-channel",
@ -3228,7 +3228,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"serde_path_to_error", "serde_path_to_error",
"sha2 0.10.6", "sha2 0.10.7",
"thiserror", "thiserror",
"url", "url",
] ]
@ -4079,9 +4079,9 @@ dependencies = [
[[package]] [[package]]
name = "scim_proto" name = "scim_proto"
version = "0.2.0" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3fbb159b673baace5ca93f155fdf80a957631802a04950df298c6e2445e3f4e" checksum = "38e53f2c444b72dd7410aa1cdc3c0942349262e84364dc7968dc7402525ea2ca"
dependencies = [ dependencies = [
"base64urlsafedata", "base64urlsafedata",
"peg", "peg",
@ -4241,9 +4241,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.96" version = "1.0.97"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" checksum = "bdf3bf93142acad5821c99197022e170842cdbc1c30482b98750c688c640842a"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",
@ -4324,9 +4324,9 @@ dependencies = [
[[package]] [[package]]
name = "sha2" name = "sha2"
version = "0.10.6" version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"cpufeatures", "cpufeatures",
@ -5247,11 +5247,10 @@ dependencies = [
[[package]] [[package]]
name = "want" name = "want"
version = "0.3.0" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
dependencies = [ dependencies = [
"log",
"try-lock", "try-lock",
] ]
@ -5717,9 +5716,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.4.6" version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" checksum = "ca0ace3845f0d96209f0375e6d367e3eb87eb65d27d445bdc9f1843a26f39448"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]

View file

@ -119,8 +119,8 @@ reqwest = { version = "0.11.18", default-features = false, features=["cookies",
rpassword = "^7.2.0" rpassword = "^7.2.0"
rusqlite = "^0.28.0" rusqlite = "^0.28.0"
scim_proto = "^0.2.0" scim_proto = "^0.2.1"
# scim_proto = { path = "../scim/proto", version = "^0.2.0" } # scim_proto = { path = "../scim/proto", version = "^0.2.1" }
# scim_proto = { git = "https://github.com/kanidm/scim.git", version = "0.1.1" } # scim_proto = { git = "https://github.com/kanidm/scim.git", version = "0.1.1" }
selinux = "^0.4.1" selinux = "^0.4.1"

View file

@ -36,3 +36,14 @@ ipa_sync_base_dn = "dc=ipa,dc=dev,dc=kanidm,dc=com"
# my-problematic-entry # my-problematic-entry
exclude = true exclude = true
# Remap the uuid of this entry to a new uuid on Kanidm
#
# map_uuid = <uuid>
# Remap the name of this entry to a new name on Kanidm
#
# map_name = <name>
# Remap the gidnumber for groups, and uidnumber for users
#
# map_gidnumber = <number>

View file

@ -74,3 +74,17 @@ ldap_filter = "(|(objectclass=person)(objectclass=posixgroup))"
# my-problematic-entry # my-problematic-entry
exclude = true exclude = true
# Remap the uuid of this entry to a new uuid on Kanidm
#
# map_uuid = <uuid>
# Remap the name of this entry to a new name on Kanidm
#
# map_name = <name>
# Remap the gidnumber for groups, and uidnumber for users
#
# map_gidnumber = <number>

View file

@ -158,7 +158,7 @@ impl CryptoPolicy {
None => PBKDF2_MIN_NIST_COST, None => PBKDF2_MIN_NIST_COST,
}; };
// Argon2id has multiple paramaters. These all are about *exchanges* that you can // Argon2id has multiple parameters. These all are about *exchanges* that you can
// request in how the computation is performed. // request in how the computation is performed.
// //
// rfc9106 explains that there are two algorithms stacked here. Argon2i has defences // rfc9106 explains that there are two algorithms stacked here. Argon2i has defences

View file

@ -21,11 +21,12 @@ impl Default for CpuOptLevel {
CpuOptLevel::x86_64_v2 CpuOptLevel::x86_64_v2
} else if cfg!(target_arch = "aarch64") && cfg!(target_os = "macos") { } else if cfg!(target_arch = "aarch64") && cfg!(target_os = "macos") {
CpuOptLevel::apple_m1 CpuOptLevel::apple_m1
/*
} else if cfg!(target_arch = "aarch64") && cfg!(target_os = "linux") { } else if cfg!(target_arch = "aarch64") && cfg!(target_os = "linux") {
// Disable neon_v8 on linux - this has issues on non-apple hardware and on // Disable neon_v8 on linux - this has issues on non-apple hardware and on
// opensuse/distro builds. // opensuse/distro builds.
// CpuOptLevel::neon_v8 CpuOptLevel::neon_v8
CpuOptLevel::none */
} else { } else {
CpuOptLevel::none CpuOptLevel::none
} }

View file

@ -4,6 +4,7 @@ use std::collections::BTreeMap;
use uuid::Uuid; use uuid::Uuid;
pub use scim_proto::prelude::{ScimAttr, ScimComplexAttr, ScimEntry, ScimError, ScimSimpleAttr}; pub use scim_proto::prelude::{ScimAttr, ScimComplexAttr, ScimEntry, ScimError, ScimSimpleAttr};
pub use scim_proto::user::MultiValueAttr;
use scim_proto::*; use scim_proto::*;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
@ -105,6 +106,7 @@ pub struct ScimSyncPerson {
pub password_import: Option<String>, pub password_import: Option<String>,
pub totp_import: Vec<ScimTotp>, pub totp_import: Vec<ScimTotp>,
pub login_shell: Option<String>, pub login_shell: Option<String>,
pub mail: Vec<MultiValueAttr>,
} }
// Need to allow this because clippy is broken and doesn't realise scimentry is out of crate // Need to allow this because clippy is broken and doesn't realise scimentry is out of crate
@ -121,6 +123,7 @@ impl Into<ScimEntry> for ScimSyncPerson {
password_import, password_import,
totp_import, totp_import,
login_shell, login_shell,
mail,
} = self; } = self;
let schemas = if gidnumber.is_some() { let schemas = if gidnumber.is_some() {
@ -144,6 +147,7 @@ impl Into<ScimEntry> for ScimSyncPerson {
set_option_string!(attrs, "password_import", password_import); set_option_string!(attrs, "password_import", password_import);
set_multi_complex!(attrs, "totp_import", totp_import); set_multi_complex!(attrs, "totp_import", totp_import);
set_option_string!(attrs, "loginshell", login_shell); set_option_string!(attrs, "loginshell", login_shell);
set_multi_complex!(attrs, "mail", mail);
ScimEntry { ScimEntry {
schemas, schemas,

View file

@ -1014,6 +1014,46 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
} }
Ok(vs) Ok(vs)
} }
(SyntaxType::EmailAddress, true, ScimAttr::MultiComplex(values)) => {
let mut vs = Vec::with_capacity(values.len());
for complex in values.iter() {
let mail_addr = complex
.attrs
.get("value")
.ok_or_else(|| {
error!("Invalid scim complex attr - missing required key value");
OperationError::InvalidAttribute(format!(
"missing required key value - {scim_attr_name}"
))
})
.and_then(|external_id| match external_id {
ScimSimpleAttr::String(value) => Ok(value.clone()),
_ => {
error!("Invalid value attribute - must be scim simple string");
Err(OperationError::InvalidAttribute(format!(
"value must be scim simple string - {scim_attr_name}"
)))
}
})?;
let primary = if let Some(primary) = complex.attrs.get("primary") {
match primary {
ScimSimpleAttr::Bool(value) => Ok(*value),
_ => {
error!("Invalid primary attribute - must be scim simple bool");
Err(OperationError::InvalidAttribute(format!(
"primary must be scim simple bool - {scim_attr_name}"
)))
}
}?
} else {
false
};
vs.push(Value::EmailAddress(mail_addr, primary))
}
Ok(vs)
}
(syn, mv, sa) => { (syn, mv, sa) => {
error!(?syn, ?mv, ?sa, "Unsupported scim attribute conversion. This may be a syntax error in your import, or a missing feature in Kanidm."); error!(?syn, ?mv, ?sa, "Unsupported scim attribute conversion. This may be a syntax error in your import, or a missing feature in Kanidm.");
Err(OperationError::InvalidAttribute(format!( Err(OperationError::InvalidAttribute(format!(
@ -2914,6 +2954,11 @@ mod tests {
"gidnumber": 12345, "gidnumber": 12345,
"loginshell": "/bin/sh", "loginshell": "/bin/sh",
"name": "testuser", "name": "testuser",
"mail": [
{
"value": "testuser@dev.blackhats.net.au"
}
],
"password_import": "ipaNTHash: iEb36u6PsRetBr3YMLdYbA" "password_import": "ipaNTHash: iEb36u6PsRetBr3YMLdYbA"
}, },
{ {

View file

@ -159,7 +159,7 @@ impl ValueSetT for ValueSetCid {
fn to_cid_single(&self) -> Option<Cid> { fn to_cid_single(&self) -> Option<Cid> {
if self.set.len() == 1 { if self.set.len() == 1 {
self.set.iter().take(1).cloned().next() self.set.iter().take(1).next().cloned()
} else { } else {
None None
} }

View file

@ -21,12 +21,11 @@ pub struct Config {
#[derive(Debug, Deserialize, Default, Clone)] #[derive(Debug, Deserialize, Default, Clone)]
pub struct EntryConfig { pub struct EntryConfig {
// uuid: Uuid,
// Default false // Default false
#[serde(default)] #[serde(default)]
pub exclude: bool, pub exclude: bool,
// map_uuid: Option<Uuid>,
// map_external_id: Option<String>, pub map_uuid: Option<Uuid>,
// map_name: Option<String>, pub map_name: Option<String>,
pub map_gidnumber: Option<u32>,
} }

View file

@ -49,7 +49,7 @@ use uuid::Uuid;
use kanidm_client::KanidmClientBuilder; use kanidm_client::KanidmClientBuilder;
use kanidm_proto::scim_v1::{ use kanidm_proto::scim_v1::{
ScimEntry, ScimExternalMember, ScimSyncGroup, ScimSyncPerson, ScimSyncRequest, MultiValueAttr, ScimEntry, ScimExternalMember, ScimSyncGroup, ScimSyncPerson, ScimSyncRequest,
ScimSyncRetentionMode, ScimSyncState, ScimTotp, ScimSyncRetentionMode, ScimSyncState, ScimTotp,
}; };
use kanidmd_lib::utils::file_permissions_readonly; use kanidmd_lib::utils::file_permissions_readonly;
@ -708,11 +708,19 @@ fn ipa_to_scim_entry(
mut entry, mut entry,
} = sync_entry; } = sync_entry;
let id = entry_uuid; let id = if let Some(map_uuid) = &entry_config.map_uuid {
*map_uuid
} else {
entry_uuid
};
let user_name = entry.remove_ava_single("uid").ok_or_else(|| { let user_name = if let Some(name) = entry_config.map_name.clone() {
name
} else {
entry.remove_ava_single("uid").ok_or_else(|| {
error!("Missing required attribute uid"); error!("Missing required attribute uid");
})?; })?
};
// ⚠️ hardcoded skip on admin here!!! // ⚠️ hardcoded skip on admin here!!!
if user_name == "admin" { if user_name == "admin" {
@ -724,14 +732,18 @@ fn ipa_to_scim_entry(
error!("Missing required attribute cn"); error!("Missing required attribute cn");
})?; })?;
let gidnumber = entry let gidnumber = if let Some(number) = entry_config.map_gidnumber.clone() {
Some(number)
} else {
entry
.remove_ava_single("gidnumber") .remove_ava_single("gidnumber")
.map(|gid| { .map(|gid| {
u32::from_str(&gid).map_err(|_| { u32::from_str(&gid).map_err(|_| {
error!("Invalid gidnumber"); error!("Invalid gidnumber");
}) })
}) })
.transpose()?; .transpose()?
};
let password_import = entry let password_import = entry
.remove_ava_single("ipanthash") .remove_ava_single("ipanthash")
@ -741,6 +753,21 @@ fn ipa_to_scim_entry(
// pw hash formats in 389-ds we don't support! // pw hash formats in 389-ds we don't support!
.or_else(|| entry.remove_ava_single("userpassword")); .or_else(|| entry.remove_ava_single("userpassword"));
let mail: Vec<_> = entry
.remove_ava("mail")
.map(|set| {
set.into_iter()
.map(|addr| MultiValueAttr {
type_: None,
primary: None,
display: None,
ref_: None,
value: addr,
})
.collect()
})
.unwrap_or_default();
let totp_import = if !totp.is_empty() { let totp_import = if !totp.is_empty() {
if password_import.is_some() { if password_import.is_some() {
// If there are TOTP's, convert them to something sensible. // If there are TOTP's, convert them to something sensible.
@ -769,6 +796,7 @@ fn ipa_to_scim_entry(
password_import, password_import,
totp_import, totp_import,
login_shell, login_shell,
mail,
} }
.into(), .into(),
)) ))

View file

@ -29,6 +29,10 @@ fn person_attr_login_shell() -> String {
"loginshell".to_string() "loginshell".to_string()
} }
fn person_attr_mail() -> String {
"mail".to_string()
}
fn group_objectclass() -> String { fn group_objectclass() -> String {
"groupofnames".to_string() "groupofnames".to_string()
} }
@ -75,6 +79,8 @@ pub struct Config {
pub person_password_prefix: Option<String>, pub person_password_prefix: Option<String>,
#[serde(default = "person_attr_login_shell")] #[serde(default = "person_attr_login_shell")]
pub person_attr_login_shell: String, pub person_attr_login_shell: String,
#[serde(default = "person_attr_mail")]
pub person_attr_mail: String,
#[serde(default = "group_objectclass")] #[serde(default = "group_objectclass")]
pub group_objectclass: String, pub group_objectclass: String,
@ -93,12 +99,11 @@ pub struct Config {
#[derive(Debug, Deserialize, Default, Clone)] #[derive(Debug, Deserialize, Default, Clone)]
pub struct EntryConfig { pub struct EntryConfig {
// uuid: Uuid,
// Default false // Default false
#[serde(default)] #[serde(default)]
pub exclude: bool, pub exclude: bool,
// map_uuid: Option<Uuid>,
// map_external_id: Option<String>, pub map_uuid: Option<Uuid>,
// map_name: Option<String>, pub map_name: Option<String>,
pub map_gidnumber: Option<u32>,
} }

View file

@ -43,7 +43,7 @@ use tracing_subscriber::{fmt, EnvFilter};
use kanidm_client::KanidmClientBuilder; use kanidm_client::KanidmClientBuilder;
use kanidm_proto::scim_v1::{ use kanidm_proto::scim_v1::{
ScimEntry, ScimExternalMember, ScimSyncGroup, ScimSyncPerson, ScimSyncRequest, MultiValueAttr, ScimEntry, ScimExternalMember, ScimSyncGroup, ScimSyncPerson, ScimSyncRequest,
ScimSyncRetentionMode, ScimSyncState, ScimSyncRetentionMode, ScimSyncState,
}; };
use kanidmd_lib::utils::file_permissions_readonly; use kanidmd_lib::utils::file_permissions_readonly;
@ -477,16 +477,24 @@ fn ldap_to_scim_entry(
mut entry, mut entry,
} = sync_entry; } = sync_entry;
let id = entry_uuid; let id = if let Some(map_uuid) = &entry_config.map_uuid {
*map_uuid
} else {
entry_uuid
};
let user_name = entry let user_name = if let Some(name) = entry_config.map_name.clone() {
name
} else {
entry
.remove_ava_single(&sync_config.person_attr_user_name) .remove_ava_single(&sync_config.person_attr_user_name)
.ok_or_else(|| { .ok_or_else(|| {
error!( error!(
"Missing required attribute {} (person_attr_user_name)", "Missing required attribute {} (person_attr_user_name)",
sync_config.person_attr_user_name sync_config.person_attr_user_name
); );
})?; })?
};
let display_name = entry let display_name = entry
.remove_ava_single(&sync_config.person_attr_display_name) .remove_ava_single(&sync_config.person_attr_display_name)
@ -497,7 +505,10 @@ fn ldap_to_scim_entry(
); );
})?; })?;
let gidnumber = entry let gidnumber = if let Some(number) = entry_config.map_gidnumber.clone() {
Some(number)
} else {
entry
.remove_ava_single(&sync_config.person_attr_gidnumber) .remove_ava_single(&sync_config.person_attr_gidnumber)
.map(|gid| { .map(|gid| {
u32::from_str(&gid).map_err(|_| { u32::from_str(&gid).map_err(|_| {
@ -507,7 +518,8 @@ fn ldap_to_scim_entry(
); );
}) })
}) })
.transpose()?; .transpose()?
};
let password_import = entry.remove_ava_single(&sync_config.person_attr_password); let password_import = entry.remove_ava_single(&sync_config.person_attr_password);
@ -517,6 +529,21 @@ fn ldap_to_scim_entry(
password_import password_import
}; };
let mail: Vec<_> = entry
.remove_ava(&sync_config.person_attr_mail)
.map(|set| {
set.into_iter()
.map(|addr| MultiValueAttr {
type_: None,
primary: None,
display: None,
ref_: None,
value: addr,
})
.collect()
})
.unwrap_or_default();
let totp_import = Vec::default(); let totp_import = Vec::default();
let login_shell = entry.remove_ava_single(&sync_config.person_attr_login_shell); let login_shell = entry.remove_ava_single(&sync_config.person_attr_login_shell);
@ -532,6 +559,7 @@ fn ldap_to_scim_entry(
password_import, password_import,
totp_import, totp_import,
login_shell, login_shell,
mail,
} }
.into(), .into(),
)) ))