mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 20:47:01 +01:00
20231218 ipa sync unix password (#2374)
* Add support for importing the users password as unix password
This commit is contained in:
parent
608e4b579d
commit
5c445a4704
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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"
|
||||||
},
|
},
|
||||||
|
|
|
@ -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)));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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>,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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")]
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue