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

View file

@ -119,8 +119,8 @@ reqwest = { version = "0.11.18", default-features = false, features=["cookies",
rpassword = "^7.2.0"
rusqlite = "^0.28.0"
scim_proto = "^0.2.0"
# scim_proto = { path = "../scim/proto", version = "^0.2.0" }
scim_proto = "^0.2.1"
# scim_proto = { path = "../scim/proto", version = "^0.2.1" }
# scim_proto = { git = "https://github.com/kanidm/scim.git", version = "0.1.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
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
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,
};
// 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.
//
// 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
} else if cfg!(target_arch = "aarch64") && cfg!(target_os = "macos") {
CpuOptLevel::apple_m1
/*
} else if cfg!(target_arch = "aarch64") && cfg!(target_os = "linux") {
// Disable neon_v8 on linux - this has issues on non-apple hardware and on
// opensuse/distro builds.
// CpuOptLevel::neon_v8
CpuOptLevel::none
CpuOptLevel::neon_v8
*/
} else {
CpuOptLevel::none
}

View file

@ -4,6 +4,7 @@ use std::collections::BTreeMap;
use uuid::Uuid;
pub use scim_proto::prelude::{ScimAttr, ScimComplexAttr, ScimEntry, ScimError, ScimSimpleAttr};
pub use scim_proto::user::MultiValueAttr;
use scim_proto::*;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
@ -105,6 +106,7 @@ pub struct ScimSyncPerson {
pub password_import: Option<String>,
pub totp_import: Vec<ScimTotp>,
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
@ -121,6 +123,7 @@ impl Into<ScimEntry> for ScimSyncPerson {
password_import,
totp_import,
login_shell,
mail,
} = self;
let schemas = if gidnumber.is_some() {
@ -144,6 +147,7 @@ impl Into<ScimEntry> for ScimSyncPerson {
set_option_string!(attrs, "password_import", password_import);
set_multi_complex!(attrs, "totp_import", totp_import);
set_option_string!(attrs, "loginshell", login_shell);
set_multi_complex!(attrs, "mail", mail);
ScimEntry {
schemas,

View file

@ -1014,6 +1014,46 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
}
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) => {
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!(
@ -2914,6 +2954,11 @@ mod tests {
"gidnumber": 12345,
"loginshell": "/bin/sh",
"name": "testuser",
"mail": [
{
"value": "testuser@dev.blackhats.net.au"
}
],
"password_import": "ipaNTHash: iEb36u6PsRetBr3YMLdYbA"
},
{

View file

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

View file

@ -21,12 +21,11 @@ pub struct Config {
#[derive(Debug, Deserialize, Default, Clone)]
pub struct EntryConfig {
// uuid: Uuid,
// Default false
#[serde(default)]
pub exclude: bool,
// map_uuid: Option<Uuid>,
// map_external_id: Option<String>,
// map_name: Option<String>,
pub map_uuid: Option<Uuid>,
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_proto::scim_v1::{
ScimEntry, ScimExternalMember, ScimSyncGroup, ScimSyncPerson, ScimSyncRequest,
MultiValueAttr, ScimEntry, ScimExternalMember, ScimSyncGroup, ScimSyncPerson, ScimSyncRequest,
ScimSyncRetentionMode, ScimSyncState, ScimTotp,
};
use kanidmd_lib::utils::file_permissions_readonly;
@ -708,11 +708,19 @@ fn ipa_to_scim_entry(
mut 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(|| {
error!("Missing required attribute uid");
})?;
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");
})?
};
// ⚠️ hardcoded skip on admin here!!!
if user_name == "admin" {
@ -724,14 +732,18 @@ fn ipa_to_scim_entry(
error!("Missing required attribute cn");
})?;
let gidnumber = entry
.remove_ava_single("gidnumber")
.map(|gid| {
u32::from_str(&gid).map_err(|_| {
error!("Invalid gidnumber");
let gidnumber = if let Some(number) = entry_config.map_gidnumber.clone() {
Some(number)
} else {
entry
.remove_ava_single("gidnumber")
.map(|gid| {
u32::from_str(&gid).map_err(|_| {
error!("Invalid gidnumber");
})
})
})
.transpose()?;
.transpose()?
};
let password_import = entry
.remove_ava_single("ipanthash")
@ -741,6 +753,21 @@ fn ipa_to_scim_entry(
// pw hash formats in 389-ds we don't support!
.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() {
if password_import.is_some() {
// If there are TOTP's, convert them to something sensible.
@ -769,6 +796,7 @@ fn ipa_to_scim_entry(
password_import,
totp_import,
login_shell,
mail,
}
.into(),
))

View file

@ -29,6 +29,10 @@ fn person_attr_login_shell() -> String {
"loginshell".to_string()
}
fn person_attr_mail() -> String {
"mail".to_string()
}
fn group_objectclass() -> String {
"groupofnames".to_string()
}
@ -75,6 +79,8 @@ pub struct Config {
pub person_password_prefix: Option<String>,
#[serde(default = "person_attr_login_shell")]
pub person_attr_login_shell: String,
#[serde(default = "person_attr_mail")]
pub person_attr_mail: String,
#[serde(default = "group_objectclass")]
pub group_objectclass: String,
@ -93,12 +99,11 @@ pub struct Config {
#[derive(Debug, Deserialize, Default, Clone)]
pub struct EntryConfig {
// uuid: Uuid,
// Default false
#[serde(default)]
pub exclude: bool,
// map_uuid: Option<Uuid>,
// map_external_id: Option<String>,
// map_name: Option<String>,
pub map_uuid: Option<Uuid>,
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_proto::scim_v1::{
ScimEntry, ScimExternalMember, ScimSyncGroup, ScimSyncPerson, ScimSyncRequest,
MultiValueAttr, ScimEntry, ScimExternalMember, ScimSyncGroup, ScimSyncPerson, ScimSyncRequest,
ScimSyncRetentionMode, ScimSyncState,
};
use kanidmd_lib::utils::file_permissions_readonly;
@ -477,16 +477,24 @@ fn ldap_to_scim_entry(
mut 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(&sync_config.person_attr_user_name)
.ok_or_else(|| {
error!(
"Missing required attribute {} (person_attr_user_name)",
sync_config.person_attr_user_name
);
})?;
let user_name = if let Some(name) = entry_config.map_name.clone() {
name
} else {
entry
.remove_ava_single(&sync_config.person_attr_user_name)
.ok_or_else(|| {
error!(
"Missing required attribute {} (person_attr_user_name)",
sync_config.person_attr_user_name
);
})?
};
let display_name = entry
.remove_ava_single(&sync_config.person_attr_display_name)
@ -497,17 +505,21 @@ fn ldap_to_scim_entry(
);
})?;
let gidnumber = entry
.remove_ava_single(&sync_config.person_attr_gidnumber)
.map(|gid| {
u32::from_str(&gid).map_err(|_| {
error!(
"Invalid gidnumber - {} is not a u32 (person_attr_gidnumber)",
sync_config.person_attr_gidnumber
);
let gidnumber = if let Some(number) = entry_config.map_gidnumber.clone() {
Some(number)
} else {
entry
.remove_ava_single(&sync_config.person_attr_gidnumber)
.map(|gid| {
u32::from_str(&gid).map_err(|_| {
error!(
"Invalid gidnumber - {} is not a u32 (person_attr_gidnumber)",
sync_config.person_attr_gidnumber
);
})
})
})
.transpose()?;
.transpose()?
};
let password_import = entry.remove_ava_single(&sync_config.person_attr_password);
@ -517,6 +529,21 @@ fn ldap_to_scim_entry(
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 login_shell = entry.remove_ava_single(&sync_config.person_attr_login_shell);
@ -532,6 +559,7 @@ fn ldap_to_scim_entry(
password_import,
totp_import,
login_shell,
mail,
}
.into(),
))