20231218 ipa sync unix password (#2374)

* Add support for importing the users password as unix password
This commit is contained in:
Firstyear 2023-12-18 11:20:37 +10:00 committed by GitHub
parent 608e4b579d
commit 5c445a4704
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 230 additions and 54 deletions

View file

@ -26,6 +26,15 @@ ipa_sync_pw = "directory manager password"
# The basedn to examine. # The basedn to examine.
ipa_sync_base_dn = "dc=ipa,dc=dev,dc=kanidm,dc=com" ipa_sync_base_dn = "dc=ipa,dc=dev,dc=kanidm,dc=com"
# By default Kanidm seperates the primary account password and credentials from
# the unix credential. This allows the unix password to be isolated from the
# account password so that compromise of one doesn't compromise the other. However
# this can be surprising for new users during a migration. This boolean allows the
# user password to be set as the unix password during the migration for consistency
# and then after the migration they are "unlinked".
#
# sync_password_as_unix_password = false
# The sync tool can alter or exclude entries. These are mapped by their syncuuid # The sync tool can alter or exclude entries. These are mapped by their syncuuid
# (not their ipa-object-uuid). The syncuuid is derived from nsUniqueId in 389-ds. # (not their ipa-object-uuid). The syncuuid is derived from nsUniqueId in 389-ds.
# This is chosen oven DN because DN's can change with modrdn where nsUniqueId is # This is chosen oven DN because DN's can change with modrdn where nsUniqueId is

View file

@ -32,6 +32,15 @@ ldap_sync_base_dn = "dc=ldap,dc=dev,dc=kanidm,dc=com"
ldap_filter = "(|(objectclass=person)(objectclass=posixgroup))" ldap_filter = "(|(objectclass=person)(objectclass=posixgroup))"
# ldap_filter = "(cn=\"my value\")" # ldap_filter = "(cn=\"my value\")"
# By default Kanidm seperates the primary account password and credentials from
# the unix credential. This allows the unix password to be isolated from the
# account password so that compromise of one doesn't compromise the other. However
# this can be surprising for new users during a migration. This boolean allows the
# user password to be set as the unix password during the migration for consistency
# and then after the migration they are "unlinked".
#
# sync_password_as_unix_password = false
# The objectclass used to identify persons to import to Kanidm. # The objectclass used to identify persons to import to Kanidm.
# #
# If not set, defaults to "person" # If not set, defaults to "person"

View file

@ -172,6 +172,7 @@ pub const ATTR_UID: &str = "uid";
pub const ATTR_UIDNUMBER: &str = "uidnumber"; pub const ATTR_UIDNUMBER: &str = "uidnumber";
pub const ATTR_UNIQUE: &str = "unique"; pub const ATTR_UNIQUE: &str = "unique";
pub const ATTR_UNIX_PASSWORD: &str = "unix_password"; pub const ATTR_UNIX_PASSWORD: &str = "unix_password";
pub const ATTR_UNIX_PASSWORD_IMPORT: &str = "unix_password_import";
pub const ATTR_USER_AUTH_TOKEN_SESSION: &str = "user_auth_token_session"; pub const ATTR_USER_AUTH_TOKEN_SESSION: &str = "user_auth_token_session";
pub const ATTR_USERID: &str = "userid"; pub const ATTR_USERID: &str = "userid";
pub const ATTR_USERPASSWORD: &str = "userpassword"; pub const ATTR_USERPASSWORD: &str = "userpassword";

View file

@ -10,7 +10,8 @@ use scim_proto::*;
use crate::constants::{ use crate::constants::{
ATTR_ACCOUNT_EXPIRE, ATTR_ACCOUNT_VALID_FROM, ATTR_DESCRIPTION, ATTR_DISPLAYNAME, ATTR_ACCOUNT_EXPIRE, ATTR_ACCOUNT_VALID_FROM, ATTR_DESCRIPTION, ATTR_DISPLAYNAME,
ATTR_GIDNUMBER, ATTR_LOGINSHELL, ATTR_MAIL, ATTR_MEMBER, ATTR_NAME, ATTR_SSH_PUBLICKEY, ATTR_GIDNUMBER, ATTR_LOGINSHELL, ATTR_MAIL, ATTR_MEMBER, ATTR_NAME, ATTR_PASSWORD_IMPORT,
ATTR_SSH_PUBLICKEY, ATTR_UNIX_PASSWORD_IMPORT, ATTR_TOTP_IMPORT
}; };
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, ToSchema)] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, ToSchema)]
@ -138,6 +139,7 @@ pub struct ScimSyncPerson {
pub display_name: String, pub display_name: String,
pub gidnumber: Option<u32>, pub gidnumber: Option<u32>,
pub password_import: Option<String>, pub password_import: Option<String>,
pub unix_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>, pub mail: Vec<MultiValueAttr>,
@ -158,6 +160,7 @@ impl Into<ScimEntry> for ScimSyncPerson {
display_name, display_name,
gidnumber, gidnumber,
password_import, password_import,
unix_password_import,
totp_import, totp_import,
login_shell, login_shell,
mail, mail,
@ -184,8 +187,9 @@ impl Into<ScimEntry> for ScimSyncPerson {
set_string!(attrs, ATTR_NAME, user_name); set_string!(attrs, ATTR_NAME, user_name);
set_string!(attrs, ATTR_DISPLAYNAME, display_name); set_string!(attrs, ATTR_DISPLAYNAME, display_name);
set_option_u32!(attrs, ATTR_GIDNUMBER, gidnumber); set_option_u32!(attrs, ATTR_GIDNUMBER, gidnumber);
set_option_string!(attrs, "password_import", password_import); set_option_string!(attrs, ATTR_PASSWORD_IMPORT, password_import);
set_multi_complex!(attrs, "totp_import", totp_import); set_option_string!(attrs, ATTR_UNIX_PASSWORD_IMPORT, unix_password_import);
set_multi_complex!(attrs, ATTR_TOTP_IMPORT, totp_import);
set_option_string!(attrs, ATTR_LOGINSHELL, login_shell); set_option_string!(attrs, ATTR_LOGINSHELL, login_shell);
set_multi_complex!(attrs, ATTR_MAIL, mail); set_multi_complex!(attrs, ATTR_MAIL, mail);
set_multi_complex!(attrs, ATTR_SSH_PUBLICKEY, ssh_publickey); // with the underscore set_multi_complex!(attrs, ATTR_SSH_PUBLICKEY, ssh_publickey); // with the underscore

View file

@ -169,6 +169,7 @@ pub enum Attribute {
UidNumber, UidNumber,
Unique, Unique,
UnixPassword, UnixPassword,
UnixPasswordImport,
UserAuthTokenSession, UserAuthTokenSession,
UserId, UserId,
UserPassword, UserPassword,
@ -351,6 +352,7 @@ impl TryFrom<String> for Attribute {
ATTR_UIDNUMBER => Attribute::UidNumber, ATTR_UIDNUMBER => Attribute::UidNumber,
ATTR_UNIQUE => Attribute::Unique, ATTR_UNIQUE => Attribute::Unique,
ATTR_UNIX_PASSWORD => Attribute::UnixPassword, ATTR_UNIX_PASSWORD => Attribute::UnixPassword,
ATTR_UNIX_PASSWORD_IMPORT => Attribute::UnixPasswordImport,
ATTR_USER_AUTH_TOKEN_SESSION => Attribute::UserAuthTokenSession, ATTR_USER_AUTH_TOKEN_SESSION => Attribute::UserAuthTokenSession,
ATTR_USERID => Attribute::UserId, ATTR_USERID => Attribute::UserId,
ATTR_USERPASSWORD => Attribute::UserPassword, ATTR_USERPASSWORD => Attribute::UserPassword,
@ -511,6 +513,7 @@ impl From<Attribute> for &'static str {
Attribute::UidNumber => ATTR_UIDNUMBER, Attribute::UidNumber => ATTR_UIDNUMBER,
Attribute::Unique => ATTR_UNIQUE, Attribute::Unique => ATTR_UNIQUE,
Attribute::UnixPassword => ATTR_UNIX_PASSWORD, Attribute::UnixPassword => ATTR_UNIX_PASSWORD,
Attribute::UnixPasswordImport => ATTR_UNIX_PASSWORD_IMPORT,
Attribute::UserAuthTokenSession => ATTR_USER_AUTH_TOKEN_SESSION, Attribute::UserAuthTokenSession => ATTR_USER_AUTH_TOKEN_SESSION,
Attribute::UserId => ATTR_USERID, Attribute::UserId => ATTR_USERID,
Attribute::UserPassword => ATTR_USERPASSWORD, Attribute::UserPassword => ATTR_USERPASSWORD,

View file

@ -268,6 +268,8 @@ pub const UUID_SCHEMA_CLASS_ACCESS_CONTROL_RECEIVER_ENTRY_MANAGER: Uuid =
pub const UUID_SCHEMA_CLASS_ACCESS_CONTROL_TARGET_SCOPE: Uuid = pub const UUID_SCHEMA_CLASS_ACCESS_CONTROL_TARGET_SCOPE: Uuid =
uuid!("00000000-0000-0000-0000-ffff00000155"); uuid!("00000000-0000-0000-0000-ffff00000155");
pub const UUID_SCHEMA_ATTR_ENTRY_MANAGED_BY: Uuid = uuid!("00000000-0000-0000-0000-ffff00000156"); pub const UUID_SCHEMA_ATTR_ENTRY_MANAGED_BY: Uuid = uuid!("00000000-0000-0000-0000-ffff00000156");
pub const UUID_SCHEMA_ATTR_UNIX_PASSWORD_IMPORT: Uuid =
uuid!("00000000-0000-0000-0000-ffff00000157");
// System and domain infos // System and domain infos
// I'd like to strongly criticise william of the past for making poor choices about these allocations. // I'd like to strongly criticise william of the past for making poor choices about these allocations.

View file

@ -820,13 +820,11 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
false, false,
ScimAttr::SingleSimple(ScimSimpleAttr::String(value)), ScimAttr::SingleSimple(ScimSimpleAttr::String(value)),
) => Ok(vec![Value::new_utf8(value.clone())]), ) => Ok(vec![Value::new_utf8(value.clone())]),
( (
SyntaxType::Utf8StringInsensitive, SyntaxType::Utf8StringInsensitive,
false, false,
ScimAttr::SingleSimple(ScimSimpleAttr::String(value)), ScimAttr::SingleSimple(ScimSimpleAttr::String(value)),
) => Ok(vec![Value::new_iutf8(value)]), ) => Ok(vec![Value::new_iutf8(value)]),
( (
SyntaxType::Uint32, SyntaxType::Uint32,
false, false,
@ -848,7 +846,6 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
}) })
}) })
.map(|value| vec![Value::Uint32(value)]), .map(|value| vec![Value::Uint32(value)]),
(SyntaxType::ReferenceUuid, true, ScimAttr::MultiComplex(values)) => { (SyntaxType::ReferenceUuid, true, ScimAttr::MultiComplex(values)) => {
// In this case, because it's a reference uuid only, despite the multicomplex structure, it's a list of // In this case, because it's a reference uuid only, despite the multicomplex structure, it's a list of
// "external_id" to external_ids. These *might* also be uuids. So we need to use sync_external_id_to_uuid // "external_id" to external_ids. These *might* also be uuids. So we need to use sync_external_id_to_uuid
@ -3191,6 +3188,7 @@ mod tests {
"value": "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBENubZikrb8hu+HeVRdZ0pp/VAk2qv4JDbuJhvD0yNdWDL2e3cBbERiDeNPkWx58Q4rVnxkbV1fa8E2waRtT91wAAAAEc3NoOg== testuser@fidokey" "value": "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBENubZikrb8hu+HeVRdZ0pp/VAk2qv4JDbuJhvD0yNdWDL2e3cBbERiDeNPkWx58Q4rVnxkbV1fa8E2waRtT91wAAAAEc3NoOg== testuser@fidokey"
} }
], ],
"unix_password_import": "ipaNTHash: iEb36u6PsRetBr3YMLdYbA",
"password_import": "ipaNTHash: iEb36u6PsRetBr3YMLdYbA" "password_import": "ipaNTHash: iEb36u6PsRetBr3YMLdYbA"
}, },
{ {
@ -3302,6 +3300,7 @@ mod tests {
"loginshell": "/bin/sh", "loginshell": "/bin/sh",
"name": "testuser", "name": "testuser",
"password_import": "ipaNTHash: iEb36u6PsRetBr3YMLdYbA", "password_import": "ipaNTHash: iEb36u6PsRetBr3YMLdYbA",
"unix_password_import": "ipaNTHash: iEb36u6PsRetBr3YMLdYbA",
"account_valid_from": "2021-11-28T04:57:55Z", "account_valid_from": "2021-11-28T04:57:55Z",
"account_expire": "2023-11-28T04:57:55Z" "account_expire": "2023-11-28T04:57:55Z"
}, },

View file

@ -73,7 +73,7 @@ impl CredImport {
let hint = im_pw.split_at(len).0; let hint = im_pw.split_at(len).0;
let id = e.get_display_id(); let id = e.get_display_id();
error!(%hint, entry_id = %id, "password_import was unable to convert hash format"); error!(%hint, entry_id = %id, "{} was unable to convert hash format", Attribute::PasswordImport);
OperationError::Plugin(PluginError::CredImport( OperationError::Plugin(PluginError::CredImport(
"password_import was unable to convert hash format".to_string(), "password_import was unable to convert hash format".to_string(),
@ -126,6 +126,40 @@ impl CredImport {
} }
} }
// UNIX PASSWORD IMPORT
if let Some(vs) = e.pop_ava(Attribute::UnixPasswordImport) {
// if there are multiple, fail.
let im_pw = vs.to_utf8_single().ok_or_else(|| {
OperationError::Plugin(PluginError::CredImport(
format!("{} has incorrect value type - should be a single utf8 string", Attribute::UnixPasswordImport),
))
})?;
// convert the import_password_string to a password
let pw = Password::try_from(im_pw).map_err(|_| {
let len = if im_pw.len() > 5 {
4
} else {
im_pw.len() - 1
};
let hint = im_pw.split_at(len).0;
let id = e.get_display_id();
error!(%hint, entry_id = %id, "{} was unable to convert hash format", Attribute::UnixPasswordImport);
OperationError::Plugin(PluginError::CredImport(
"unix_password_import was unable to convert hash format".to_string(),
))
})?;
// Unix pw's aren't like primary, we can just splat them here.
let c = Credential::new_from_password(pw);
e.set_ava(
Attribute::UnixPassword,
once(Value::new_credential("primary", c)),
);
};
Ok(()) Ok(())
}) })
} }
@ -147,17 +181,29 @@ mod tests {
fn test_pre_create_password_import_1() { fn test_pre_create_password_import_1() {
let preload: Vec<Entry<EntryInit, EntryNew>> = Vec::new(); let preload: Vec<Entry<EntryInit, EntryNew>> = Vec::new();
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str( let e = entry_init!(
r#"{ (Attribute::Class, EntryClass::Account.to_value()),
"attrs": { (Attribute::Class, EntryClass::Person.to_value()),
"class": ["person", "account"], (Attribute::Name, Value::new_iname("testperson")),
"name": ["testperson"], (
"description": ["testperson"], Attribute::Description,
"displayname": ["testperson"], Value::Utf8("testperson".to_string())
"uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"], ),
"password_import": ["pbkdf2_sha256$36000$xIEozuZVAoYm$uW1b35DUKyhvQAf1mBqMvoBDcqSD06juzyO/nmyV0+w="] (
} Attribute::DisplayName,
}"#, Value::Utf8("testperson".to_string())
),
(
Attribute::Uuid,
Value::Uuid(uuid!("d2b496bd-8493-47b7-8142-f568b5cf47ee"))
),
(
Attribute::PasswordImport,
Value::Utf8(
"pbkdf2_sha256$36000$xIEozuZVAoYm$uW1b35DUKyhvQAf1mBqMvoBDcqSD06juzyO/nmyV0+w="
.into()
)
)
); );
let create = vec![e]; let create = vec![e];
@ -167,17 +213,22 @@ mod tests {
#[test] #[test]
fn test_modify_password_import_1() { fn test_modify_password_import_1() {
// Add another uuid to a type let ea = entry_init!(
let ea: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str( (Attribute::Class, EntryClass::Account.to_value()),
r#"{ (Attribute::Class, EntryClass::Person.to_value()),
"attrs": { (Attribute::Name, Value::new_iname("testperson")),
"class": ["account", "person"], (
"name": ["testperson"], Attribute::Description,
"description": ["testperson"], Value::Utf8("testperson".to_string())
"displayname": ["testperson"], ),
"uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"] (
} Attribute::DisplayName,
}"#, Value::Utf8("testperson".to_string())
),
(
Attribute::Uuid,
Value::Uuid(uuid!("d2b496bd-8493-47b7-8142-f568b5cf47ee"))
)
); );
let preload = vec![ea]; let preload = vec![ea];
@ -198,17 +249,22 @@ mod tests {
#[test] #[test]
fn test_modify_password_import_2() { fn test_modify_password_import_2() {
// Add another uuid to a type let mut ea = entry_init!(
let mut ea: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str( (Attribute::Class, EntryClass::Account.to_value()),
r#"{ (Attribute::Class, EntryClass::Person.to_value()),
"attrs": { (Attribute::Name, Value::new_iname("testperson")),
"class": ["account", "person"], (
"name": ["testperson"], Attribute::Description,
"description": ["testperson"], Value::Utf8("testperson".to_string())
"displayname": ["testperson"], ),
"uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"] (
} Attribute::DisplayName,
}"#, Value::Utf8("testperson".to_string())
),
(
Attribute::Uuid,
Value::Uuid(uuid!("d2b496bd-8493-47b7-8142-f568b5cf47ee"))
)
); );
let p = CryptoPolicy::minimum(); let p = CryptoPolicy::minimum();
@ -236,17 +292,22 @@ mod tests {
#[test] #[test]
fn test_modify_password_import_3_totp() { fn test_modify_password_import_3_totp() {
// Add another uuid to a type let mut ea = entry_init!(
let mut ea: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str( (Attribute::Class, EntryClass::Account.to_value()),
r#"{ (Attribute::Class, EntryClass::Person.to_value()),
"attrs": { (Attribute::Name, Value::new_iname("testperson")),
"class": ["account", "person"], (
"name": ["testperson"], Attribute::Description,
"description": ["testperson"], Value::Utf8("testperson".to_string())
"displayname": ["testperson"], ),
"uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"] (
} Attribute::DisplayName,
}"#, Value::Utf8("testperson".to_string())
),
(
Attribute::Uuid,
Value::Uuid(uuid!("d2b496bd-8493-47b7-8142-f568b5cf47ee"))
)
); );
let totp = Totp::generate_secure(TOTP_DEFAULT_STEP); let totp = Totp::generate_secure(TOTP_DEFAULT_STEP);
@ -393,4 +454,50 @@ mod tests {
|_| {} |_| {}
); );
} }
#[test]
fn test_modify_unix_password_import() {
let ea = entry_init!(
(Attribute::Class, EntryClass::Account.to_value()),
(Attribute::Class, EntryClass::PosixAccount.to_value()),
(Attribute::Class, EntryClass::Person.to_value()),
(Attribute::Name, Value::new_iname("testperson")),
(
Attribute::Description,
Value::Utf8("testperson".to_string())
),
(
Attribute::DisplayName,
Value::Utf8("testperson".to_string())
),
(
Attribute::Uuid,
Value::Uuid(uuid!("d2b496bd-8493-47b7-8142-f568b5cf47ee"))
)
);
let preload = vec![ea];
run_modify_test!(
Ok(()),
preload,
filter!(f_eq(Attribute::Name, PartialValue::new_iutf8("testperson"))),
ModifyList::new_list(vec![Modify::Present(
Attribute::UnixPasswordImport.into(),
Value::from(IMPORT_HASH)
)]),
None,
|_| {},
|qs: &mut QueryServerWriteTransaction| {
let e = qs
.internal_search_uuid(uuid!("d2b496bd-8493-47b7-8142-f568b5cf47ee"))
.expect("failed to get entry");
let c = e
.get_ava_single_credential(Attribute::UnixPassword)
.expect("failed to get unix cred.");
assert!(matches!(&c.type_, CredentialType::Password(_pw)));
}
);
}
} }

View file

@ -1588,6 +1588,24 @@ impl<'a> SchemaWriteTransaction<'a> {
}, },
); );
self.attributes.insert(
Attribute::UnixPasswordImport.into(),
SchemaAttribute {
name: Attribute::UnixPasswordImport.into(),
uuid: UUID_SCHEMA_ATTR_UNIX_PASSWORD_IMPORT,
description: String::from(
"An imported unix password hash from an external system.",
),
multivalue: false,
unique: false,
phantom: true,
sync_allowed: true,
replicated: false,
index: vec![],
syntax: SyntaxType::Utf8String,
},
);
self.attributes.insert( self.attributes.insert(
Attribute::TotpImport.into(), Attribute::TotpImport.into(),
SchemaAttribute { SchemaAttribute {

View file

@ -14,6 +14,8 @@ pub struct Config {
pub ipa_sync_pw: String, pub ipa_sync_pw: String,
pub ipa_sync_base_dn: String, pub ipa_sync_base_dn: String,
pub sync_password_as_unix_password: Option<bool>,
// pub entry: Option<Vec<EntryConfig>>, // pub entry: Option<Vec<EntryConfig>>,
#[serde(flatten)] #[serde(flatten)]
pub entry_map: BTreeMap<Uuid, EntryConfig>, pub entry_map: BTreeMap<Uuid, EntryConfig>,

View file

@ -456,6 +456,9 @@ async fn run_sync(
entries, entries,
&sync_config.entry_map, &sync_config.entry_map,
is_initialise, is_initialise,
sync_config
.sync_password_as_unix_password
.unwrap_or_default(),
) )
.await .await
{ {
@ -521,6 +524,7 @@ async fn process_ipa_sync_result(
ldap_entries: Vec<LdapSyncReplEntry>, ldap_entries: Vec<LdapSyncReplEntry>,
entry_config_map: &BTreeMap<Uuid, EntryConfig>, entry_config_map: &BTreeMap<Uuid, EntryConfig>,
is_initialise: bool, is_initialise: bool,
sync_password_as_unix_password: bool,
) -> Result<Vec<ScimEntry>, ()> { ) -> Result<Vec<ScimEntry>, ()> {
// Because of how TOTP works with freeipa it's a soft referral from // Because of how TOTP works with freeipa it's a soft referral from
// the totp toward the user. This means if a TOTP is added or removed // the totp toward the user. This means if a TOTP is added or removed
@ -758,7 +762,7 @@ async fn process_ipa_sync_result(
let totp = totp_entries.get(&dn).unwrap_or(&empty_slice); let totp = totp_entries.get(&dn).unwrap_or(&empty_slice);
match ipa_to_scim_entry(e, &e_config, totp) { match ipa_to_scim_entry(e, &e_config, totp, sync_password_as_unix_password) {
Ok(Some(e)) => Some(Ok(e)), Ok(Some(e)) => Some(Ok(e)),
Ok(None) => None, Ok(None) => None,
Err(()) => Some(Err(())), Err(()) => Some(Err(())),
@ -767,12 +771,11 @@ async fn process_ipa_sync_result(
.collect::<Result<Vec<_>, _>>() .collect::<Result<Vec<_>, _>>()
} }
// TODO: Allow re-map of uuid -> uuid
fn ipa_to_scim_entry( fn ipa_to_scim_entry(
sync_entry: LdapSyncReplEntry, sync_entry: LdapSyncReplEntry,
entry_config: &EntryConfig, entry_config: &EntryConfig,
totp: &[LdapSyncReplEntry], totp: &[LdapSyncReplEntry],
sync_password_as_unix_password: bool,
) -> Result<Option<ScimEntry>, ()> { ) -> Result<Option<ScimEntry>, ()> {
debug!("{:#?}", sync_entry); debug!("{:#?}", sync_entry);
@ -857,6 +860,12 @@ 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(Attribute::UserPassword.as_ref())); .or_else(|| entry.remove_ava_single(Attribute::UserPassword.as_ref()));
let unix_password_import = if sync_password_as_unix_password {
password_import.clone()
} else {
None
};
let mail: Vec<_> = entry let mail: Vec<_> = entry
.remove_ava(Attribute::Mail.as_ref()) .remove_ava(Attribute::Mail.as_ref())
.map(|set| { .map(|set| {
@ -928,6 +937,7 @@ fn ipa_to_scim_entry(
display_name, display_name,
gidnumber, gidnumber,
password_import, password_import,
unix_password_import,
totp_import, totp_import,
login_shell, login_shell,
mail, mail,

View file

@ -72,6 +72,8 @@ pub struct Config {
pub ldap_filter: LdapFilter, pub ldap_filter: LdapFilter,
pub sync_password_as_unix_password: Option<bool>,
#[serde(default = "person_objectclass")] #[serde(default = "person_objectclass")]
pub person_objectclass: String, pub person_objectclass: String,
#[serde(default = "person_attr_user_name")] #[serde(default = "person_attr_user_name")]

View file

@ -553,6 +553,15 @@ fn ldap_to_scim_entry(
password_import password_import
}; };
let unix_password_import = if sync_config
.sync_password_as_unix_password
.unwrap_or_default()
{
password_import.clone()
} else {
None
};
let mail: Vec<_> = entry let mail: Vec<_> = entry
.remove_ava(&sync_config.person_attr_mail) .remove_ava(&sync_config.person_attr_mail)
.map(|set| { .map(|set| {
@ -610,6 +619,7 @@ fn ldap_to_scim_entry(
display_name, display_name,
gidnumber, gidnumber,
password_import, password_import,
unix_password_import,
totp_import, totp_import,
login_shell, login_shell,
mail, mail,