use crate::audit::AuditScope;
use crate::constants::{AUTH_SESSION_TIMEOUT, MFAREG_SESSION_TIMEOUT, PW_MIN_LENGTH};
use crate::constants::{UUID_ANONYMOUS, UUID_SYSTEM_CONFIG};
use crate::event::{AuthEvent, AuthEventStep, AuthResult};
use crate::idm::account::Account;
use crate::idm::authsession::AuthSession;
use crate::idm::event::{
GeneratePasswordEvent, GenerateTOTPEvent, LdapAuthEvent, PasswordChangeEvent,
RadiusAuthTokenEvent, RegenerateRadiusSecretEvent, UnixGroupTokenEvent,
UnixPasswordChangeEvent, UnixUserAuthEvent, UnixUserTokenEvent, VerifyTOTPEvent,
};
use crate::idm::mfareg::{MfaRegCred, MfaRegNext, MfaRegSession, MfaReqInit, MfaReqStep};
use crate::idm::radius::RadiusAccount;
use crate::idm::unix::{UnixGroup, UnixUserAccount};
use crate::ldap::LdapBoundToken;
use crate::server::QueryServerReadTransaction;
use crate::server::{QueryServer, QueryServerTransaction, QueryServerWriteTransaction};
use crate::utils::{password_from_random, readable_password_from_random, uuid_from_duration, SID};
use crate::value::PartialValue;
use kanidm_proto::v1::AuthState;
use kanidm_proto::v1::OperationError;
use kanidm_proto::v1::RadiusAuthToken;
use kanidm_proto::v1::SetCredentialResponse;
use kanidm_proto::v1::UnixGroupToken;
use kanidm_proto::v1::UnixUserToken;
use concread::collections::bptree::*;
use rand::prelude::*;
use std::time::Duration;
use uuid::Uuid;
pub struct IdmServer {
sessions: BptreeMap<Uuid, AuthSession>,
mfareg_sessions: BptreeMap<Uuid, MfaRegSession>,
qs: QueryServer,
}
pub struct IdmServerWriteTransaction<'a> {
sessions: BptreeMapWriteTxn<'a, Uuid, AuthSession>,
pub qs_read: QueryServerReadTransaction<'a>,
sid: SID,
}
pub struct IdmServerProxyReadTransaction<'a> {
pub qs_read: QueryServerReadTransaction<'a>,
}
pub struct IdmServerProxyWriteTransaction<'a> {
pub qs_write: QueryServerWriteTransaction<'a>,
mfareg_sessions: BptreeMapWriteTxn<'a, Uuid, MfaRegSession>,
sid: SID,
}
impl IdmServer {
pub fn new(qs: QueryServer) -> IdmServer {
IdmServer {
sessions: BptreeMap::new(),
mfareg_sessions: BptreeMap::new(),
qs,
}
}
pub fn write(&self) -> IdmServerWriteTransaction {
let mut sid = [0; 4];
let mut rng = StdRng::from_entropy();
rng.fill(&mut sid);
IdmServerWriteTransaction {
sessions: self.sessions.write(),
qs_read: self.qs.read(),
sid,
}
}
pub fn proxy_read(&self) -> IdmServerProxyReadTransaction {
IdmServerProxyReadTransaction {
qs_read: self.qs.read(),
}
}
pub fn proxy_write(&self, ts: Duration) -> IdmServerProxyWriteTransaction {
let mut sid = [0; 4];
let mut rng = StdRng::from_entropy();
rng.fill(&mut sid);
IdmServerProxyWriteTransaction {
mfareg_sessions: self.mfareg_sessions.write(),
qs_write: self.qs.write(ts),
sid,
}
}
}
impl<'a> IdmServerWriteTransaction<'a> {
#[cfg(test)]
pub fn is_sessionid_present(&self, sessionid: &Uuid) -> bool {
self.sessions.contains_key(sessionid)
}
pub fn expire_auth_sessions(&mut self, ct: Duration) {
let expire = ct - Duration::from_secs(AUTH_SESSION_TIMEOUT);
let split_at = uuid_from_duration(expire, self.sid);
self.sessions.split_off_lt(&split_at);
}
pub fn auth(
&mut self,
au: &mut AuditScope,
ae: &AuthEvent,
ct: Duration,
) -> Result<AuthResult, OperationError> {
ltrace!(au, "Received -> {:?}", ae);
match &ae.step {
AuthEventStep::Init(init) => {
lperf_segment!(au, "idm::server::auth<Init>", || {
let sessionid = uuid_from_duration(ct, self.sid);
let euuid = self.qs_read.name_to_uuid(au, init.name.as_str())?;
let entry = self.qs_read.internal_search_uuid(au, &euuid)?;
lsecurity!(
au,
"Initiating Authentication Session for ... {:?}: {:?}",
euuid,
entry
);
let account = Account::try_from_entry_ro(au, entry, &mut self.qs_read)?;
let auth_session = AuthSession::new(au, account, init.appid.clone());
let next_mech = auth_session.valid_auth_mechs();
lperf_segment!(au, "idm::server::auth<Init> -> sessions", || {
if self.sessions.contains_key(&sessionid) {
Err(OperationError::InvalidSessionState)
} else {
self.sessions.insert(sessionid, auth_session);
debug_assert!(self.sessions.get(&sessionid).is_some());
Ok(())
}
})?;
Ok(AuthResult {
sessionid,
state: AuthState::Continue(next_mech),
})
})
}
AuthEventStep::Creds(creds) => {
lperf_segment!(au, "idm::server::auth<Creds>", || {
let auth_session = self
.sessions
.get_mut(&creds.sessionid)
.ok_or_else(|| {
ladmin_error!(au, "Invalid Session State (no present session uuid)");
OperationError::InvalidSessionState
})?;
auth_session
.validate_creds(au, &creds.creds, &ct)
.map(|aus| {
AuthResult {
sessionid: creds.sessionid,
state: aus,
}
})
})
}
}
}
pub fn auth_unix(
&mut self,
au: &mut AuditScope,
uae: &UnixUserAuthEvent,
_ct: Duration,
) -> Result<Option<UnixUserToken>, OperationError> {
let account = self
.qs_read
.internal_search_uuid(au, &uae.target)
.and_then(|account_entry| {
UnixUserAccount::try_from_entry_ro(au, account_entry, &mut self.qs_read)
})
.map_err(|e| {
ladmin_error!(au, "Failed to start auth unix -> {:?}", e);
e
})?;
account.verify_unix_credential(au, uae.cleartext.as_str())
}
pub fn auth_ldap(
&mut self,
au: &mut AuditScope,
lae: LdapAuthEvent,
_ct: Duration,
) -> Result<Option<LdapBoundToken>, OperationError> {
let account_entry = self
.qs_read
.internal_search_uuid(au, &lae.target)
.map_err(|e| {
ladmin_error!(au, "Failed to start auth ldap -> {:?}", e);
e
})?;
if lae.target == *UUID_ANONYMOUS {
let account = Account::try_from_entry_ro(au, account_entry, &mut self.qs_read)?;
Ok(Some(LdapBoundToken {
spn: account.spn,
uuid: *UUID_ANONYMOUS,
effective_uuid: *UUID_ANONYMOUS,
}))
} else {
let account = UnixUserAccount::try_from_entry_ro(au, account_entry, &mut self.qs_read)?;
if account
.verify_unix_credential(au, lae.cleartext.as_str())?
.is_some()
{
Ok(Some(LdapBoundToken {
spn: account.spn,
uuid: account.uuid,
effective_uuid: *UUID_ANONYMOUS,
}))
} else {
Ok(None)
}
}
}
pub fn commit(self, au: &mut AuditScope) -> Result<(), OperationError> {
lperf_trace_segment!(au, "idm::server::IdmServerWriteTransaction::commit", || {
self.sessions.commit();
Ok(())
})
}
}
impl<'a> IdmServerProxyReadTransaction<'a> {
pub fn get_radiusauthtoken(
&mut self,
au: &mut AuditScope,
rate: &RadiusAuthTokenEvent,
) -> Result<RadiusAuthToken, OperationError> {
let account = self
.qs_read
.impersonate_search_ext_uuid(au, &rate.target, &rate.event)
.and_then(|account_entry| {
RadiusAccount::try_from_entry_reduced(au, account_entry, &mut self.qs_read)
})
.map_err(|e| {
ladmin_error!(au, "Failed to start radius auth token {:?}", e);
e
})?;
account.to_radiusauthtoken()
}
pub fn get_unixusertoken(
&mut self,
au: &mut AuditScope,
uute: &UnixUserTokenEvent,
) -> Result<UnixUserToken, OperationError> {
let account = self
.qs_read
.impersonate_search_ext_uuid(au, &uute.target, &uute.event)
.and_then(|account_entry| {
UnixUserAccount::try_from_entry_reduced(au, account_entry, &mut self.qs_read)
})
.map_err(|e| {
ladmin_error!(au, "Failed to start unix user token -> {:?}", e);
e
})?;
account.to_unixusertoken()
}
pub fn get_unixgrouptoken(
&mut self,
au: &mut AuditScope,
uute: &UnixGroupTokenEvent,
) -> Result<UnixGroupToken, OperationError> {
let group = self
.qs_read
.impersonate_search_ext_uuid(au, &uute.target, &uute.event)
.and_then(UnixGroup::try_from_entry_reduced)
.map_err(|e| {
ladmin_error!(au, "Failed to start unix group token {:?}", e);
e
})?;
group.to_unixgrouptoken()
}
}
impl<'a> IdmServerProxyWriteTransaction<'a> {
pub fn expire_mfareg_sessions(&mut self, ct: Duration) {
let expire = ct - Duration::from_secs(MFAREG_SESSION_TIMEOUT);
let split_at = uuid_from_duration(expire, self.sid);
self.mfareg_sessions.split_off_lt(&split_at);
}
fn check_password_quality(
&mut self,
au: &mut AuditScope,
cleartext: &str,
related_inputs: &[&str],
) -> Result<(), OperationError> {
if cleartext.len() < PW_MIN_LENGTH {
return Err(OperationError::PasswordTooShort(PW_MIN_LENGTH));
}
let entropy = zxcvbn::zxcvbn(cleartext, related_inputs).map_err(|e| {
ladmin_error!(au, "zxcvbn check failure (password empty?) {:?}", e);
OperationError::PasswordEmpty
})?;
if entropy.score() < 3 {
let feedback: zxcvbn::feedback::Feedback = entropy
.feedback()
.as_ref()
.ok_or(OperationError::InvalidState)
.map(|v| v.clone())
.map_err(|e| {
lsecurity!(au, "zxcvbn returned no feedback when score < 3");
e
})?;
lsecurity!(au, "pw feedback -> {:?}", feedback);
return Err(OperationError::PasswordTooWeak);
}
let lc_password = PartialValue::new_iutf8s(cleartext);
let badlist_entry = self
.qs_write
.internal_search_uuid(au, &UUID_SYSTEM_CONFIG)
.map_err(|e| {
ladmin_error!(au, "Failed to retrieve system configuration {:?}", e);
e
})?;
if badlist_entry.attribute_value_pres("badlist_password", &lc_password) {
lsecurity!(au, "Password found in badlist, rejecting");
Err(OperationError::PasswordBadListed)
} else {
Ok(())
}
}
fn target_to_account(
&mut self,
au: &mut AuditScope,
target: &Uuid,
) -> Result<Account, OperationError> {
let account = self
.qs_write
.internal_search_uuid(au, target)
.and_then(|account_entry| {
Account::try_from_entry_rw(au, account_entry, &mut self.qs_write)
})
.map_err(|e| {
ladmin_error!(au, "Failed to search account {:?}", e);
e
})?;
if account.is_anonymous() {
Err(OperationError::SystemProtectedObject)
} else {
Ok(account)
}
}
pub fn set_account_password(
&mut self,
au: &mut AuditScope,
pce: &PasswordChangeEvent,
) -> Result<(), OperationError> {
let account = self.target_to_account(au, &pce.target)?;
let related_inputs: Vec<&str> = vec![
account.name.as_str(),
account.displayname.as_str(),
account.spn.as_str(),
];
self.check_password_quality(au, pce.cleartext.as_str(), related_inputs.as_slice())
.map_err(|e| {
lrequest_error!(au, "check_password_quality -> {:?}", e);
e
})?;
let modlist = account
.gen_password_mod(pce.cleartext.as_str(), &pce.appid)
.map_err(|e| {
ladmin_error!(au, "Failed to generate password mod {:?}", e);
e
})?;
ltrace!(au, "processing change {:?}", modlist);
self.qs_write
.impersonate_modify(
au,
filter!(f_eq("uuid", PartialValue::new_uuidr(&pce.target))),
filter_all!(f_eq("uuid", PartialValue::new_uuidr(&pce.target))),
modlist,
&pce.event,
)
.map_err(|e| {
lrequest_error!(au, "error -> {:?}", e);
e
})?;
Ok(())
}
pub fn set_unix_account_password(
&mut self,
au: &mut AuditScope,
pce: &UnixPasswordChangeEvent,
) -> Result<(), OperationError> {
let account = self
.qs_write
.internal_search_uuid(au, &pce.target)
.and_then(|account_entry| {
UnixUserAccount::try_from_entry_rw(au, account_entry, &mut self.qs_write)
})
.map_err(|e| {
ladmin_error!(au, "Failed to start set unix account password {:?}", e);
e
})?;
if account.is_anonymous() {
return Err(OperationError::SystemProtectedObject);
}
let related_inputs: Vec<&str> = vec![
account.name.as_str(),
account.displayname.as_str(),
account.spn.as_str(),
];
self.check_password_quality(au, pce.cleartext.as_str(), related_inputs.as_slice())
.map_err(|e| {
ladmin_error!(au, "Failed to checked password quality {:?}", e);
e
})?;
let modlist = account
.gen_password_mod(pce.cleartext.as_str())
.map_err(|e| {
ladmin_error!(au, "Unable to generate password change modlist {:?}", e);
e
})?;
ltrace!(au, "processing change {:?}", modlist);
self.qs_write
.impersonate_modify(
au,
filter!(f_eq("uuid", PartialValue::new_uuidr(&pce.target))),
filter_all!(f_eq("uuid", PartialValue::new_uuidr(&pce.target))),
modlist,
&pce.event,
)
.map_err(|e| {
lrequest_error!(au, "error -> {:?}", e);
e
})
.map(|_| ())
}
pub fn recover_account(
&mut self,
au: &mut AuditScope,
name: String,
cleartext: String,
) -> Result<(), OperationError> {
let target = self.qs_write.name_to_uuid(au, name.as_str()).map_err(|e| {
ladmin_error!(au, "name to uuid failed {:?}", e);
e
})?;
let pce = PasswordChangeEvent::new_internal(&target, cleartext.as_str(), None);
self.set_account_password(au, &pce)
}
pub fn generate_account_password(
&mut self,
au: &mut AuditScope,
gpe: &GeneratePasswordEvent,
) -> Result<String, OperationError> {
let account = self.target_to_account(au, &gpe.target)?;
let cleartext = password_from_random();
let modlist = account
.gen_password_mod(cleartext.as_str(), &gpe.appid)
.map_err(|e| {
ladmin_error!(au, "Unable to generate password mod {:?}", e);
e
})?;
ltrace!(au, "processing change {:?}", modlist);
self.qs_write
.impersonate_modify(
au,
filter!(f_eq("uuid", PartialValue::new_uuidr(&gpe.target))),
filter_all!(f_eq("uuid", PartialValue::new_uuidr(&gpe.target))),
modlist,
&gpe.event,
)
.map(|_| cleartext)
.map_err(|e| {
ladmin_error!(au, "Failed to generate account password {:?}", e);
e
})
}
pub fn regenerate_radius_secret(
&mut self,
au: &mut AuditScope,
rrse: &RegenerateRadiusSecretEvent,
) -> Result<String, OperationError> {
let account = self.target_to_account(au, &rrse.target)?;
let cleartext = readable_password_from_random();
let modlist = account
.regenerate_radius_secret_mod(cleartext.as_str())
.map_err(|e| {
ladmin_error!(au, "Unable to generate radius secret mod {:?}", e);
e
})?;
ltrace!(au, "processing change {:?}", modlist);
self.qs_write
.impersonate_modify(
au,
filter!(f_eq("uuid", PartialValue::new_uuidr(&rrse.target))),
filter_all!(f_eq("uuid", PartialValue::new_uuidr(&rrse.target))),
modlist,
&rrse.event,
)
.map_err(|e| {
lrequest_error!(au, "error -> {:?}", e);
e
})
.map(|_| cleartext)
}
pub fn generate_account_totp(
&mut self,
au: &mut AuditScope,
gte: &GenerateTOTPEvent,
ct: Duration,
) -> Result<SetCredentialResponse, OperationError> {
let account = self.target_to_account(au, >e.target)?;
let sessionid = uuid_from_duration(ct, self.sid);
let origin = (>e.event.origin).into();
let label = gte.label.clone();
let (session, next) = MfaRegSession::new(origin, account, MfaReqInit::TOTP(label))
.map_err(|e| {
ladmin_error!(au, "Unable to start totp MfaRegSession {:?}", e);
e
})?;
let next = next.to_proto(&sessionid);
self.mfareg_sessions.insert(sessionid, session);
ltrace!(au, "Start mfa reg session -> {:?}", sessionid);
Ok(next)
}
pub fn verify_account_totp(
&mut self,
au: &mut AuditScope,
vte: &VerifyTOTPEvent,
ct: Duration,
) -> Result<SetCredentialResponse, OperationError> {
let sessionid = vte.session;
let origin = (&vte.event.origin).into();
let chal = vte.chal;
ltrace!(au, "Attempting to find mfareg_session -> {:?}", sessionid);
let (next, opt_cred) = self
.mfareg_sessions
.get_mut(&sessionid)
.ok_or(OperationError::InvalidRequestState)
.and_then(|session| {
session.step(&origin, &vte.target, MfaReqStep::TOTPVerify(chal), &ct)
})
.map_err(|e| {
ladmin_error!(au, "Failed to verify totp {:?}", e);
e
})?;
if let (MfaRegNext::Success, Some(MfaRegCred::TOTP(token))) = (&next, opt_cred) {
let session = self
.mfareg_sessions
.remove(&sessionid)
.expect("Session within transaction vanished!");
let modlist = session.account.gen_totp_mod(token).map_err(|e| {
ladmin_error!(au, "Failed to gen totp mod {:?}", e);
e
})?;
self.qs_write
.impersonate_modify(
au,
filter!(f_eq("uuid", PartialValue::new_uuidr(&session.account.uuid))),
filter_all!(f_eq("uuid", PartialValue::new_uuidr(&session.account.uuid))),
modlist,
&vte.event,
)
.map_err(|e| {
ladmin_error!(au, "verify_account_totp {:?}", e);
e
})?;
};
let next = next.to_proto(&sessionid);
Ok(next)
}
pub fn commit(self, au: &mut AuditScope) -> Result<(), OperationError> {
lperf_trace_segment!(au, "idm::server::IdmServerWriteTransaction::commit", || {
self.mfareg_sessions.commit();
self.qs_write.commit(au)
})
}
}
#[cfg(test)]
mod tests {
use crate::constants::{
AUTH_SESSION_TIMEOUT, MFAREG_SESSION_TIMEOUT, UUID_ADMIN, UUID_ANONYMOUS,
};
use crate::credential::totp::TOTP;
use crate::credential::Credential;
use crate::entry::{Entry, EntryInit, EntryNew};
use crate::event::{AuthEvent, AuthResult, CreateEvent, ModifyEvent};
use crate::idm::event::{
GenerateTOTPEvent, PasswordChangeEvent, RadiusAuthTokenEvent, RegenerateRadiusSecretEvent,
UnixGroupTokenEvent, UnixPasswordChangeEvent, UnixUserAuthEvent, UnixUserTokenEvent,
VerifyTOTPEvent,
};
use crate::modify::{Modify, ModifyList};
use crate::value::{PartialValue, Value};
use kanidm_proto::v1::OperationError;
use kanidm_proto::v1::SetCredentialResponse;
use kanidm_proto::v1::{AuthAllowed, AuthState};
use crate::audit::AuditScope;
use crate::idm::server::IdmServer;
use crate::server::QueryServer;
use crate::utils::duration_from_epoch_now;
use std::time::Duration;
use uuid::Uuid;
const TEST_PASSWORD: &'static str = "ntaoeuntnaoeuhraohuercahu😍";
const TEST_PASSWORD_INC: &'static str = "ntaoentu nkrcgaeunhibwmwmqj;k wqjbkx ";
const TEST_CURRENT_TIME: u64 = 6000;
const TEST_CURRENT_EXPIRE: u64 = TEST_CURRENT_TIME + AUTH_SESSION_TIMEOUT + 1;
#[test]
fn test_idm_anonymous_auth() {
run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| {
let sid = {
let mut idms_write = idms.write();
let anon_init = AuthEvent::anonymous_init();
let r1 = idms_write.auth(au, &anon_init, Duration::from_secs(TEST_CURRENT_TIME));
let sid = match r1 {
Ok(ar) => {
let AuthResult { sessionid, state } = ar;
match state {
AuthState::Continue(mut conts) => {
assert!(conts.len() == 1);
let m = conts.pop().expect("Should not fail");
assert!(m == AuthAllowed::Anonymous);
}
_ => {
error!(
"A critical error has occured! We have a non-continue result!"
);
panic!();
}
};
sessionid
}
Err(e) => {
error!("A critical error has occured! {:?}", e);
panic!();
}
};
debug!("sessionid is ==> {:?}", sid);
idms_write.commit(au).expect("Must not fail");
sid
};
{
let mut idms_write = idms.write();
let anon_step = AuthEvent::cred_step_anonymous(sid);
let r2 = idms_write.auth(au, &anon_step, Duration::from_secs(TEST_CURRENT_TIME));
debug!("r2 ==> {:?}", r2);
match r2 {
Ok(ar) => {
let AuthResult {
sessionid: _,
state,
} = ar;
match state {
AuthState::Success(_uat) => {
}
_ => {
error!(
"A critical error has occured! We have a non-succcess result!"
);
panic!();
}
}
}
Err(e) => {
error!("A critical error has occured! {:?}", e);
panic!();
}
};
idms_write.commit(au).expect("Must not fail");
}
});
}
#[test]
fn test_idm_anonymous_auth_invalid_states() {
run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| {
{
let mut idms_write = idms.write();
let sid = Uuid::new_v4();
let anon_step = AuthEvent::cred_step_anonymous(sid);
let r2 = idms_write.auth(au, &anon_step, Duration::from_secs(TEST_CURRENT_TIME));
debug!("r2 ==> {:?}", r2);
match r2 {
Ok(_) => {
error!("Auth state machine not correctly enforced!");
panic!();
}
Err(e) => match e {
OperationError::InvalidSessionState => {}
_ => panic!(),
},
};
}
})
}
fn init_admin_w_password(
au: &mut AuditScope,
qs: &QueryServer,
pw: &str,
) -> Result<(), OperationError> {
let cred = Credential::new_password_only(pw);
let v_cred = Value::new_credential("primary", cred);
let qs_write = qs.write(duration_from_epoch_now());
let me_inv_m = unsafe {
ModifyEvent::new_internal_invalid(
filter!(f_eq("name", PartialValue::new_iname("admin"))),
ModifyList::new_list(vec![Modify::Present(
"primary_credential".to_string(),
v_cred,
)]),
)
};
assert!(qs_write.modify(au, &me_inv_m).is_ok());
qs_write.commit(au)
}
fn init_admin_authsession_sid(idms: &IdmServer, au: &mut AuditScope) -> Uuid {
let mut idms_write = idms.write();
let admin_init = AuthEvent::named_init("admin");
let r1 = idms_write.auth(au, &admin_init, Duration::from_secs(TEST_CURRENT_TIME));
let ar = r1.unwrap();
let AuthResult { sessionid, state } = ar;
match state {
AuthState::Continue(_) => {}
_ => {
error!("Sessions was not initialised");
panic!();
}
};
idms_write.commit(au).expect("Must not fail");
sessionid
}
#[test]
fn test_idm_simple_password_auth() {
run_idm_test!(|qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| {
init_admin_w_password(au, qs, TEST_PASSWORD).expect("Failed to setup admin account");
let sid = init_admin_authsession_sid(idms, au);
let mut idms_write = idms.write();
let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD);
let r2 = idms_write.auth(au, &anon_step, Duration::from_secs(TEST_CURRENT_TIME));
debug!("r2 ==> {:?}", r2);
match r2 {
Ok(ar) => {
let AuthResult {
sessionid: _,
state,
} = ar;
match state {
AuthState::Success(_uat) => {
}
_ => {
error!("A critical error has occured! We have a non-succcess result!");
panic!();
}
}
}
Err(e) => {
error!("A critical error has occured! {:?}", e);
panic!();
}
};
idms_write.commit(au).expect("Must not fail");
})
}
#[test]
fn test_idm_simple_password_spn_auth() {
run_idm_test!(|qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| {
init_admin_w_password(au, qs, TEST_PASSWORD).expect("Failed to setup admin account");
let mut idms_write = idms.write();
let admin_init = AuthEvent::named_init("admin@example.com");
let r1 = idms_write.auth(au, &admin_init, Duration::from_secs(TEST_CURRENT_TIME));
let ar = r1.unwrap();
let AuthResult { sessionid, state } = ar;
match state {
AuthState::Continue(_) => {}
_ => {
error!("Sessions was not initialised");
panic!();
}
};
idms_write.commit(au).expect("Must not fail");
let sid = sessionid;
let mut idms_write = idms.write();
let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD);
let r2 = idms_write.auth(au, &anon_step, Duration::from_secs(TEST_CURRENT_TIME));
debug!("r2 ==> {:?}", r2);
match r2 {
Ok(ar) => {
let AuthResult {
sessionid: _,
state,
} = ar;
match state {
AuthState::Success(_uat) => {
}
_ => {
error!("A critical error has occured! We have a non-succcess result!");
panic!();
}
}
}
Err(e) => {
error!("A critical error has occured! {:?}", e);
panic!();
}
};
idms_write.commit(au).expect("Must not fail");
})
}
#[test]
fn test_idm_simple_password_invalid() {
run_idm_test!(|qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| {
init_admin_w_password(au, qs, TEST_PASSWORD).expect("Failed to setup admin account");
let sid = init_admin_authsession_sid(idms, au);
let mut idms_write = idms.write();
let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD_INC);
let r2 = idms_write.auth(au, &anon_step, Duration::from_secs(TEST_CURRENT_TIME));
debug!("r2 ==> {:?}", r2);
match r2 {
Ok(ar) => {
let AuthResult {
sessionid: _,
state,
} = ar;
match state {
AuthState::Denied(_reason) => {
}
_ => {
error!("A critical error has occured! We have a non-denied result!");
panic!();
}
}
}
Err(e) => {
error!("A critical error has occured! {:?}", e);
panic!();
}
};
idms_write.commit(au).expect("Must not fail");
})
}
#[test]
fn test_idm_simple_password_reset() {
run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| {
let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, TEST_PASSWORD, None);
let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now());
assert!(idms_prox_write.set_account_password(au, &pce).is_ok());
assert!(idms_prox_write.set_account_password(au, &pce).is_ok());
assert!(idms_prox_write.commit(au).is_ok());
})
}
#[test]
fn test_idm_anonymous_set_password_denied() {
run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| {
let pce = PasswordChangeEvent::new_internal(&UUID_ANONYMOUS, TEST_PASSWORD, None);
let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now());
assert!(idms_prox_write.set_account_password(au, &pce).is_err());
assert!(idms_prox_write.commit(au).is_ok());
})
}
#[test]
fn test_idm_session_expire() {
run_idm_test!(|qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| {
init_admin_w_password(au, qs, TEST_PASSWORD).expect("Failed to setup admin account");
let sid = init_admin_authsession_sid(idms, au);
let mut idms_write = idms.write();
assert!(idms_write.is_sessionid_present(&sid));
idms_write.expire_auth_sessions(Duration::from_secs(TEST_CURRENT_TIME));
assert!(idms_write.is_sessionid_present(&sid));
idms_write.expire_auth_sessions(Duration::from_secs(TEST_CURRENT_EXPIRE));
assert!(!idms_write.is_sessionid_present(&sid));
assert!(idms_write.commit(au).is_ok());
let idms_write = idms.write();
assert!(!idms_write.is_sessionid_present(&sid));
})
}
#[test]
fn test_idm_regenerate_radius_secret() {
run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| {
let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now());
let rrse = RegenerateRadiusSecretEvent::new_internal(UUID_ADMIN.clone());
let r1 = idms_prox_write
.regenerate_radius_secret(au, &rrse)
.expect("Failed to reset radius credential 1");
let r2 = idms_prox_write
.regenerate_radius_secret(au, &rrse)
.expect("Failed to reset radius credential 2");
assert!(r1 != r2);
})
}
#[test]
fn test_idm_radiusauthtoken() {
run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| {
let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now());
let rrse = RegenerateRadiusSecretEvent::new_internal(UUID_ADMIN.clone());
let r1 = idms_prox_write
.regenerate_radius_secret(au, &rrse)
.expect("Failed to reset radius credential 1");
idms_prox_write.commit(au).expect("failed to commit");
let mut idms_prox_read = idms.proxy_read();
let rate = RadiusAuthTokenEvent::new_internal(UUID_ADMIN.clone());
let tok_r = idms_prox_read
.get_radiusauthtoken(au, &rate)
.expect("Failed to generate radius auth token");
assert!(r1 == tok_r.secret);
})
}
#[test]
fn test_idm_simple_password_reject_weak() {
run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| {
let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now());
let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, "password", None);
let e = idms_prox_write.set_account_password(au, &pce);
assert!(e.is_err());
let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, "password1234", None);
let e = idms_prox_write.set_account_password(au, &pce);
assert!(e.is_err());
let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, "admin_nta", None);
let e = idms_prox_write.set_account_password(au, &pce);
assert!(e.is_err());
let pce = PasswordChangeEvent::new_internal(
&UUID_ADMIN,
"demo_badlist_shohfie3aeci2oobur0aru9uushah6EiPi2woh4hohngoighaiRuepieN3ongoo1",
None,
);
let e = idms_prox_write.set_account_password(au, &pce);
assert!(e.is_err());
assert!(idms_prox_write.commit(au).is_ok());
})
}
#[test]
fn test_idm_unixusertoken() {
run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| {
let idms_prox_write = idms.proxy_write(duration_from_epoch_now());
let me_posix = unsafe {
ModifyEvent::new_internal_invalid(
filter!(f_eq("name", PartialValue::new_iname("admin"))),
ModifyList::new_list(vec![
Modify::Present("class".to_string(), Value::new_class("posixaccount")),
Modify::Present("gidnumber".to_string(), Value::new_uint32(2001)),
]),
)
};
assert!(idms_prox_write.qs_write.modify(au, &me_posix).is_ok());
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["object", "group", "posixgroup"],
"name": ["testgroup"],
"uuid": ["01609135-a1c4-43d5-966b-a28227644445"],
"description": ["testgroup"],
"member": ["00000000-0000-0000-0000-000000000000"]
}
}"#,
);
let ce = CreateEvent::new_internal(vec![e.clone()]);
assert!(idms_prox_write.qs_write.create(au, &ce).is_ok());
idms_prox_write.commit(au).expect("failed to commit");
let mut idms_prox_read = idms.proxy_read();
let ugte = UnixGroupTokenEvent::new_internal(
Uuid::parse_str("01609135-a1c4-43d5-966b-a28227644445")
.expect("failed to parse uuid"),
);
let tok_g = idms_prox_read
.get_unixgrouptoken(au, &ugte)
.expect("Failed to generate unix group token");
assert!(tok_g.name == "testgroup");
assert!(tok_g.spn == "testgroup@example.com");
let uute = UnixUserTokenEvent::new_internal(UUID_ADMIN.clone());
let tok_r = idms_prox_read
.get_unixusertoken(au, &uute)
.expect("Failed to generate unix user token");
assert!(tok_r.name == "admin");
assert!(tok_r.spn == "admin@example.com");
assert!(tok_r.groups.len() == 2);
assert!(tok_r.groups[0].name == "admin");
assert!(tok_r.groups[1].name == "testgroup");
let ugte = UnixGroupTokenEvent::new_internal(
Uuid::parse_str("00000000-0000-0000-0000-000000000000")
.expect("failed to parse uuid"),
);
let tok_g = idms_prox_read
.get_unixgrouptoken(au, &ugte)
.expect("Failed to generate unix group token");
assert!(tok_g.name == "admin");
assert!(tok_g.spn == "admin@example.com");
})
}
#[test]
fn test_idm_simple_unix_password_reset() {
run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| {
let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now());
let me_posix = unsafe {
ModifyEvent::new_internal_invalid(
filter!(f_eq("name", PartialValue::new_iname("admin"))),
ModifyList::new_list(vec![
Modify::Present("class".to_string(), Value::new_class("posixaccount")),
Modify::Present("gidnumber".to_string(), Value::new_uint32(2001)),
]),
)
};
assert!(idms_prox_write.qs_write.modify(au, &me_posix).is_ok());
let pce = UnixPasswordChangeEvent::new_internal(&UUID_ADMIN, TEST_PASSWORD);
assert!(idms_prox_write.set_unix_account_password(au, &pce).is_ok());
assert!(idms_prox_write.set_unix_account_password(au, &pce).is_ok());
assert!(idms_prox_write.commit(au).is_ok());
let mut idms_write = idms.write();
let uuae_good = UnixUserAuthEvent::new_internal(&UUID_ADMIN, TEST_PASSWORD);
let a1 = idms_write.auth_unix(au, &uuae_good, Duration::from_secs(TEST_CURRENT_TIME));
match a1 {
Ok(Some(_tok)) => {}
_ => assert!(false),
};
let uuae_bad = UnixUserAuthEvent::new_internal(&UUID_ADMIN, TEST_PASSWORD_INC);
let a2 = idms_write.auth_unix(au, &uuae_bad, Duration::from_secs(TEST_CURRENT_TIME));
match a2 {
Ok(None) => {}
_ => assert!(false),
};
assert!(idms_write.commit(au).is_ok());
let idms_prox_write = idms.proxy_write(duration_from_epoch_now());
let me_purge_up = unsafe {
ModifyEvent::new_internal_invalid(
filter!(f_eq("name", PartialValue::new_iname("admin"))),
ModifyList::new_list(vec![Modify::Purged("unix_password".to_string())]),
)
};
assert!(idms_prox_write.qs_write.modify(au, &me_purge_up).is_ok());
assert!(idms_prox_write.commit(au).is_ok());
let mut idms_write = idms.write();
let a3 = idms_write.auth_unix(au, &uuae_good, Duration::from_secs(TEST_CURRENT_TIME));
match a3 {
Ok(None) => {}
_ => assert!(false),
};
assert!(idms_write.commit(au).is_ok());
})
}
#[test]
fn test_idm_totp_registration() {
run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| {
let ct = duration_from_epoch_now();
let expire = Duration::from_secs(ct.as_secs() + MFAREG_SESSION_TIMEOUT + 2);
let mut idms_prox_write = idms.proxy_write(ct.clone());
let vte1 = VerifyTOTPEvent::new_internal(UUID_ADMIN.clone(), Uuid::new_v4(), 0);
match idms_prox_write.verify_account_totp(au, &vte1, ct.clone()) {
Err(e) => {
assert!(e == OperationError::InvalidRequestState);
}
_ => panic!(),
};
let gte1 = GenerateTOTPEvent::new_internal(UUID_ADMIN.clone());
let res = idms_prox_write
.generate_account_totp(au, >e1, ct.clone())
.unwrap();
let sesid = match res {
SetCredentialResponse::TOTPCheck(id, _) => id,
_ => panic!("invalid state!"),
};
idms_prox_write.expire_mfareg_sessions(expire.clone());
let vte2 = VerifyTOTPEvent::new_internal(UUID_ADMIN.clone(), sesid, 0);
match idms_prox_write.verify_account_totp(au, &vte1, ct.clone()) {
Err(e) => {
assert!(e == OperationError::InvalidRequestState);
}
_ => panic!(),
};
let res = idms_prox_write
.generate_account_totp(au, >e1, ct.clone())
.unwrap();
let (sesid, tok) = match res {
SetCredentialResponse::TOTPCheck(id, tok) => (id, tok),
_ => panic!("invalid state!"),
};
let r_tok: TOTP = tok.into();
let chal = r_tok
.do_totp_duration_from_epoch(&ct)
.expect("Failed to do totp?");
let vte3 = VerifyTOTPEvent::new_internal(UUID_ADMIN.clone(), sesid, chal);
match idms_prox_write.verify_account_totp(au, &vte3, ct.clone()) {
Err(e) => assert!(e == OperationError::InvalidState),
_ => panic!(),
};
idms_prox_write.expire_mfareg_sessions(expire.clone());
let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, TEST_PASSWORD, None);
assert!(idms_prox_write.set_account_password(au, &pce).is_ok());
let res = idms_prox_write
.generate_account_totp(au, >e1, ct.clone())
.unwrap();
let (sesid, tok) = match res {
SetCredentialResponse::TOTPCheck(id, tok) => (id, tok),
_ => panic!("invalid state!"),
};
let r_tok: TOTP = tok.into();
let chal = r_tok
.do_totp_duration_from_epoch(&ct)
.expect("Failed to do totp?");
let vte3 = VerifyTOTPEvent::new_internal(UUID_ANONYMOUS.clone(), sesid, chal);
match idms_prox_write.verify_account_totp(au, &vte3, ct.clone()) {
Err(e) => assert!(e == OperationError::InvalidRequestState),
_ => panic!(),
};
let res = idms_prox_write
.generate_account_totp(au, >e1, ct.clone())
.unwrap();
let (_sesid, _tok) = match res {
SetCredentialResponse::TOTPCheck(id, tok) => (id, tok),
_ => panic!("invalid state!"),
};
match idms_prox_write.verify_account_totp(au, &vte2, ct.clone()) {
Ok(SetCredentialResponse::TOTPCheck(_id, _tok)) => {}
_ => panic!(),
};
idms_prox_write.expire_mfareg_sessions(expire.clone());
let res = idms_prox_write
.generate_account_totp(au, >e1, ct.clone())
.unwrap();
let (sesid, tok) = match res {
SetCredentialResponse::TOTPCheck(id, tok) => (id, tok),
_ => panic!("invalid state!"),
};
let r_tok: TOTP = tok.into();
let chal = r_tok
.do_totp_duration_from_epoch(&ct)
.expect("Failed to do totp?");
let vte3 = VerifyTOTPEvent::new_internal(UUID_ADMIN.clone(), sesid, chal);
match idms_prox_write.verify_account_totp(au, &vte3, ct.clone()) {
Ok(SetCredentialResponse::Success) => {}
_ => panic!(),
};
idms_prox_write.expire_mfareg_sessions(expire.clone());
assert!(idms_prox_write.commit(au).is_ok());
})
}
}