use crate::audit::AuditScope;
use crate::credential::{Credential, Password};
use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew};
use crate::event::{CreateEvent, ModifyEvent};
use crate::plugins::Plugin;
use crate::server::QueryServerWriteTransaction;
use crate::value::Value;
use kanidm_proto::v1::{OperationError, PluginError};
use std::convert::TryFrom;
pub struct PasswordImport {}
impl Plugin for PasswordImport {
fn id() -> &'static str {
"plugin_password_import"
}
fn pre_create_transform(
_au: &mut AuditScope,
_qs: &QueryServerWriteTransaction,
cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
_ce: &CreateEvent,
) -> Result<(), OperationError> {
cand.iter_mut()
.try_for_each(|e| {
let vs = match e.pop_ava("password_import") {
Some(vs) => vs,
None => return Ok(()),
};
if vs.len() > 1 {
return Err(OperationError::Plugin(PluginError::PasswordImport("multiple password_imports specified".to_string())))
}
let vs: Vec<_> = vs.into_iter().collect();
debug_assert!(!vs.is_empty());
let im_pw = vs.first()
.unwrap()
.to_str()
.ok_or_else(|| OperationError::Plugin(PluginError::PasswordImport("password_import has incorrect value type".to_string())))?;
let pw = Password::try_from(im_pw)
.map_err(|_| OperationError::Plugin(PluginError::PasswordImport("password_import was unable to convert hash format".to_string())))?;
match e.get_ava_single_credential("primary_credential") {
Some(_c) => {
Err(
OperationError::Plugin(PluginError::PasswordImport(
"password_import - impossible state, how did you get a credential into a create!?".to_string()))
)
}
None => {
let c = Credential::new_from_password(pw);
e.set_ava("primary_credential",
btreeset![Value::new_credential("primary", c)]);
Ok(())
}
}
})
}
fn pre_modify(
_au: &mut AuditScope,
_qs: &QueryServerWriteTransaction,
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
_me: &ModifyEvent,
) -> Result<(), OperationError> {
cand.iter_mut().try_for_each(|e| {
let vs = match e.pop_ava("password_import") {
Some(vs) => vs,
None => return Ok(()),
};
if vs.len() > 1 {
return Err(OperationError::Plugin(PluginError::PasswordImport(
"multiple password_imports specified".to_string(),
)));
}
let vs: Vec<_> = vs.into_iter().collect();
debug_assert!(!vs.is_empty());
let im_pw = vs.first().unwrap().to_str().ok_or_else(|| {
OperationError::Plugin(PluginError::PasswordImport(
"password_import has incorrect value type".to_string(),
))
})?;
let pw = Password::try_from(im_pw).map_err(|_| {
OperationError::Plugin(PluginError::PasswordImport(
"password_import was unable to convert hash format".to_string(),
))
})?;
match e.get_ava_single_credential("primary_credential") {
Some(c) => {
let c = c.update_password(pw);
e.set_ava(
"primary_credential",
btreeset![Value::new_credential("primary", c)],
);
Ok(())
}
None => {
let c = Credential::new_from_password(pw);
e.set_ava(
"primary_credential",
btreeset![Value::new_credential("primary", c)],
);
Ok(())
}
}
})
}
}
#[cfg(test)]
mod tests {
use crate::credential::totp::{TOTP, TOTP_DEFAULT_STEP};
use crate::credential::Credential;
use crate::entry::{Entry, EntryInit, EntryNew};
use crate::modify::{Modify, ModifyList};
use crate::server::{QueryServerTransaction, QueryServerWriteTransaction};
use crate::value::{PartialValue, Value};
use uuid::Uuid;
const IMPORT_HASH: &'static str =
"pbkdf2_sha256$36000$xIEozuZVAoYm$uW1b35DUKyhvQAf1mBqMvoBDcqSD06juzyO/nmyV0+w=";
#[test]
fn test_pre_create_password_import_1() {
let preload: Vec<Entry<EntryInit, EntryNew>> = Vec::new();
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["person", "account"],
"name": ["testperson"],
"description": ["testperson"],
"displayname": ["testperson"],
"uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"],
"password_import": ["pbkdf2_sha256$36000$xIEozuZVAoYm$uW1b35DUKyhvQAf1mBqMvoBDcqSD06juzyO/nmyV0+w="]
}
}"#,
);
let create = vec![e.clone()];
run_create_test!(Ok(()), preload, create, None, |_, _| {});
}
#[test]
fn test_modify_password_import_1() {
let ea: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["account", "person"],
"name": ["testperson"],
"description": ["testperson"],
"displayname": ["testperson"],
"uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
}
}"#,
);
let preload = vec![ea];
run_modify_test!(
Ok(()),
preload,
filter!(f_eq("name", PartialValue::new_iutf8s("testperson"))),
ModifyList::new_list(vec![Modify::Present(
"password_import".to_string(),
Value::from(IMPORT_HASH)
)]),
None,
|_, _| {}
);
}
#[test]
fn test_modify_password_import_2() {
let mut ea: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["account", "person"],
"name": ["testperson"],
"description": ["testperson"],
"displayname": ["testperson"],
"uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
}
}"#,
);
let c = Credential::new_password_only("password");
ea.add_ava("primary_credential", Value::new_credential("primary", c));
let preload = vec![ea];
run_modify_test!(
Ok(()),
preload,
filter!(f_eq("name", PartialValue::new_iutf8s("testperson"))),
ModifyList::new_list(vec![Modify::Present(
"password_import".to_string(),
Value::from(IMPORT_HASH)
)]),
None,
|_, _| {}
);
}
#[test]
fn test_modify_password_import_3_totp() {
let mut ea: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["account", "person"],
"name": ["testperson"],
"description": ["testperson"],
"displayname": ["testperson"],
"uuid": ["d2b496bd-8493-47b7-8142-f568b5cf47ee"]
}
}"#,
);
let totp = TOTP::generate_secure("test_totp".to_string(), TOTP_DEFAULT_STEP);
let c = Credential::new_password_only("password").update_totp(totp);
ea.add_ava("primary_credential", Value::new_credential("primary", c));
let preload = vec![ea];
run_modify_test!(
Ok(()),
preload,
filter!(f_eq("name", PartialValue::new_iutf8s("testperson"))),
ModifyList::new_list(vec![Modify::Present(
"password_import".to_string(),
Value::from(IMPORT_HASH)
)]),
None,
|au: &mut AuditScope, qs: &QueryServerWriteTransaction| {
let e = qs
.internal_search_uuid(
au,
&Uuid::parse_str("d2b496bd-8493-47b7-8142-f568b5cf47ee").unwrap(),
)
.expect("failed to get entry");
let c = e
.get_ava_single_credential("primary_credential")
.expect("failed to get primary cred.");
assert!(c.totp.is_some());
assert!(c.password.is_some());
}
);
}
}