mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 12:37:00 +01:00
1737 1739 sync - map uidnumbers mail (#1741)
This commit is contained in:
parent
c65be8174a
commit
6513fae5e2
37
Cargo.lock
generated
37
Cargo.lock
generated
|
@ -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",
|
||||
]
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
))
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
))
|
||||
|
|
Loading…
Reference in a new issue