mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-24 13:07:00 +01:00
Add badlist checking when using password login (#394)
This commit is contained in:
parent
b1ac7c0120
commit
8a2f3b65ec
|
@ -5,6 +5,7 @@ pub const JSON_SYSTEM_CONFIG_V1: &str = r####"{
|
||||||
"uuid": ["00000000-0000-0000-0000-ffffff000027"],
|
"uuid": ["00000000-0000-0000-0000-ffffff000027"],
|
||||||
"description": ["System (replicated) configuration options."],
|
"description": ["System (replicated) configuration options."],
|
||||||
"badlist_password": [
|
"badlist_password": [
|
||||||
|
"bad@no3IBTyqHu$list",
|
||||||
"demo_badlist_shohfie3aeci2oobur0aru9uushah6EiPi2woh4hohngoighaiRuepieN3ongoo1",
|
"demo_badlist_shohfie3aeci2oobur0aru9uushah6EiPi2woh4hohngoighaiRuepieN3ongoo1",
|
||||||
"100preteamare",
|
"100preteamare",
|
||||||
"14defebrero",
|
"14defebrero",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::audit::AuditScope;
|
|
||||||
use crate::idm::account::Account;
|
use crate::idm::account::Account;
|
||||||
use crate::idm::claim::Claim;
|
use crate::idm::claim::Claim;
|
||||||
use crate::idm::AuthState;
|
use crate::idm::AuthState;
|
||||||
|
use crate::{audit::AuditScope, value::Value};
|
||||||
use kanidm_proto::v1::OperationError;
|
use kanidm_proto::v1::OperationError;
|
||||||
use kanidm_proto::v1::{AuthAllowed, AuthCredential, AuthMech};
|
use kanidm_proto::v1::{AuthAllowed, AuthCredential, AuthMech};
|
||||||
|
|
||||||
|
@ -15,6 +15,8 @@ use crate::credential::webauthn::WebauthnDomainConfig;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
// use webauthn_rs::proto::Credential as WebauthnCredential;
|
// use webauthn_rs::proto::Credential as WebauthnCredential;
|
||||||
|
use crate::value::PartialValue;
|
||||||
|
pub use std::collections::BTreeSet as Set;
|
||||||
use webauthn_rs::proto::RequestChallengeResponse;
|
use webauthn_rs::proto::RequestChallengeResponse;
|
||||||
use webauthn_rs::{AuthenticationState, Webauthn};
|
use webauthn_rs::{AuthenticationState, Webauthn};
|
||||||
|
|
||||||
|
@ -29,6 +31,7 @@ const BAD_WEBAUTHN_MSG: &str = "invalid webauthn authentication";
|
||||||
const BAD_AUTH_TYPE_MSG: &str = "invalid authentication method in this context";
|
const BAD_AUTH_TYPE_MSG: &str = "invalid authentication method in this context";
|
||||||
const BAD_CREDENTIALS: &str = "invalid credential message";
|
const BAD_CREDENTIALS: &str = "invalid credential message";
|
||||||
const ACCOUNT_EXPIRED: &str = "account expired";
|
const ACCOUNT_EXPIRED: &str = "account expired";
|
||||||
|
const PW_BADLIST_MSG: &str = "password is in badlist";
|
||||||
|
|
||||||
enum CredState {
|
enum CredState {
|
||||||
Success(Vec<Claim>),
|
Success(Vec<Claim>),
|
||||||
|
@ -178,13 +181,25 @@ impl CredHandler {
|
||||||
pw: &mut Password,
|
pw: &mut Password,
|
||||||
who: Uuid,
|
who: Uuid,
|
||||||
async_tx: &Sender<DelayedAction>,
|
async_tx: &Sender<DelayedAction>,
|
||||||
|
pw_badlist_set: Option<Set<Value>>,
|
||||||
) -> CredState {
|
) -> CredState {
|
||||||
match cred {
|
match cred {
|
||||||
AuthCredential::Password(cleartext) => {
|
AuthCredential::Password(cleartext) => {
|
||||||
if pw.verify(cleartext.as_str()).unwrap_or(false) {
|
if pw.verify(cleartext.as_str()).unwrap_or(false) {
|
||||||
|
match pw_badlist_set {
|
||||||
|
Some(p) if p.contains(&PartialValue::new_iutf8(cleartext)) => {
|
||||||
|
lsecurity!(
|
||||||
|
au,
|
||||||
|
"Handler::Password -> Result::Denied - Password found in badlist during login"
|
||||||
|
);
|
||||||
|
CredState::Denied(PW_BADLIST_MSG)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
lsecurity!(au, "Handler::Password -> Result::Success");
|
lsecurity!(au, "Handler::Password -> Result::Success");
|
||||||
Self::maybe_pw_upgrade(au, pw, who, cleartext.as_str(), async_tx);
|
Self::maybe_pw_upgrade(au, pw, who, cleartext.as_str(), async_tx);
|
||||||
CredState::Success(Vec::new())
|
CredState::Success(Vec::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
lsecurity!(
|
lsecurity!(
|
||||||
au,
|
au,
|
||||||
|
@ -197,7 +212,7 @@ impl CredHandler {
|
||||||
_ => {
|
_ => {
|
||||||
lsecurity!(
|
lsecurity!(
|
||||||
au,
|
au,
|
||||||
"Handler::Anonymous -> Result::Denied - invalid cred type for handler"
|
"Handler::Password -> Result::Denied - invalid cred type for handler"
|
||||||
);
|
);
|
||||||
CredState::Denied(BAD_AUTH_TYPE_MSG)
|
CredState::Denied(BAD_AUTH_TYPE_MSG)
|
||||||
}
|
}
|
||||||
|
@ -212,6 +227,7 @@ impl CredHandler {
|
||||||
webauthn: &Webauthn<WebauthnDomainConfig>,
|
webauthn: &Webauthn<WebauthnDomainConfig>,
|
||||||
who: Uuid,
|
who: Uuid,
|
||||||
async_tx: &Sender<DelayedAction>,
|
async_tx: &Sender<DelayedAction>,
|
||||||
|
pw_badlist_set: Option<Set<Value>>,
|
||||||
) -> CredState {
|
) -> CredState {
|
||||||
match (&pw_mfa.mfa_state, &pw_mfa.pw_state) {
|
match (&pw_mfa.mfa_state, &pw_mfa.pw_state) {
|
||||||
(CredVerifyState::Init, CredVerifyState::Init) => {
|
(CredVerifyState::Init, CredVerifyState::Init) => {
|
||||||
|
@ -273,6 +289,16 @@ impl CredHandler {
|
||||||
match cred {
|
match cred {
|
||||||
AuthCredential::Password(cleartext) => {
|
AuthCredential::Password(cleartext) => {
|
||||||
if pw_mfa.pw.verify(cleartext.as_str()).unwrap_or(false) {
|
if pw_mfa.pw.verify(cleartext.as_str()).unwrap_or(false) {
|
||||||
|
match pw_badlist_set {
|
||||||
|
Some(p) if p.contains(&PartialValue::new_iutf8(cleartext)) => {
|
||||||
|
pw_mfa.pw_state = CredVerifyState::Fail;
|
||||||
|
lsecurity!(
|
||||||
|
au,
|
||||||
|
"Handler::PasswordMFA -> Result::Denied - Password found in badlist during login"
|
||||||
|
);
|
||||||
|
CredState::Denied(PW_BADLIST_MSG)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
pw_mfa.pw_state = CredVerifyState::Success;
|
pw_mfa.pw_state = CredVerifyState::Success;
|
||||||
lsecurity!(
|
lsecurity!(
|
||||||
au,
|
au,
|
||||||
|
@ -286,6 +312,8 @@ impl CredHandler {
|
||||||
async_tx,
|
async_tx,
|
||||||
);
|
);
|
||||||
CredState::Success(Vec::new())
|
CredState::Success(Vec::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
pw_mfa.pw_state = CredVerifyState::Fail;
|
pw_mfa.pw_state = CredVerifyState::Fail;
|
||||||
lsecurity!(
|
lsecurity!(
|
||||||
|
@ -375,15 +403,23 @@ impl CredHandler {
|
||||||
who: Uuid,
|
who: Uuid,
|
||||||
async_tx: &Sender<DelayedAction>,
|
async_tx: &Sender<DelayedAction>,
|
||||||
webauthn: &Webauthn<WebauthnDomainConfig>,
|
webauthn: &Webauthn<WebauthnDomainConfig>,
|
||||||
|
pw_badlist_set: Option<Set<Value>>,
|
||||||
) -> CredState {
|
) -> CredState {
|
||||||
match self {
|
match self {
|
||||||
CredHandler::Anonymous => Self::validate_anonymous(au, cred),
|
CredHandler::Anonymous => Self::validate_anonymous(au, cred),
|
||||||
CredHandler::Password(ref mut pw) => {
|
CredHandler::Password(ref mut pw) => {
|
||||||
Self::validate_password(au, cred, pw, who, async_tx)
|
Self::validate_password(au, cred, pw, who, async_tx, pw_badlist_set)
|
||||||
}
|
|
||||||
CredHandler::PasswordMFA(ref mut pw_mfa) => {
|
|
||||||
Self::validate_password_mfa(au, cred, ts, pw_mfa, webauthn, who, async_tx)
|
|
||||||
}
|
}
|
||||||
|
CredHandler::PasswordMFA(ref mut pw_mfa) => Self::validate_password_mfa(
|
||||||
|
au,
|
||||||
|
cred,
|
||||||
|
ts,
|
||||||
|
pw_mfa,
|
||||||
|
webauthn,
|
||||||
|
who,
|
||||||
|
async_tx,
|
||||||
|
pw_badlist_set,
|
||||||
|
),
|
||||||
CredHandler::Webauthn(ref mut wan_cred) => {
|
CredHandler::Webauthn(ref mut wan_cred) => {
|
||||||
Self::validate_webauthn(au, cred, wan_cred, webauthn, who, async_tx)
|
Self::validate_webauthn(au, cred, wan_cred, webauthn, who, async_tx)
|
||||||
}
|
}
|
||||||
|
@ -464,6 +500,8 @@ pub(crate) struct AuthSession {
|
||||||
//
|
//
|
||||||
// This handler will then handle the mfa and stepping up through to generate the auth states
|
// This handler will then handle the mfa and stepping up through to generate the auth states
|
||||||
state: AuthSessionState,
|
state: AuthSessionState,
|
||||||
|
// Store the password badlist
|
||||||
|
pw_badlist_set: Option<Set<Value>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AuthSession {
|
impl AuthSession {
|
||||||
|
@ -473,6 +511,7 @@ impl AuthSession {
|
||||||
_appid: &Option<String>,
|
_appid: &Option<String>,
|
||||||
webauthn: &Webauthn<WebauthnDomainConfig>,
|
webauthn: &Webauthn<WebauthnDomainConfig>,
|
||||||
ct: Duration,
|
ct: Duration,
|
||||||
|
pw_badlist_set: Option<Set<Value>>,
|
||||||
) -> (Option<Self>, AuthState) {
|
) -> (Option<Self>, AuthState) {
|
||||||
// During this setup, determine the credential handler that we'll be using
|
// During this setup, determine the credential handler that we'll be using
|
||||||
// for this session. This is currently based on presentation of an application
|
// for this session. This is currently based on presentation of an application
|
||||||
|
@ -516,7 +555,11 @@ impl AuthSession {
|
||||||
(None, AuthState::Denied(reason.to_string()))
|
(None, AuthState::Denied(reason.to_string()))
|
||||||
} else {
|
} else {
|
||||||
// We can proceed
|
// We can proceed
|
||||||
let auth_session = AuthSession { account, state };
|
let auth_session = AuthSession {
|
||||||
|
account,
|
||||||
|
state,
|
||||||
|
pw_badlist_set,
|
||||||
|
};
|
||||||
// Get the set of mechanisms that can proceed. This is tied
|
// Get the set of mechanisms that can proceed. This is tied
|
||||||
// to the session so that it can mutate state and have progression
|
// to the session so that it can mutate state and have progression
|
||||||
// of what's next, or ordering.
|
// of what's next, or ordering.
|
||||||
|
@ -608,7 +651,15 @@ impl AuthSession {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
AuthSessionState::InProgress(ref mut handler) => {
|
AuthSessionState::InProgress(ref mut handler) => {
|
||||||
match handler.validate(au, cred, time, self.account.uuid, async_tx, webauthn) {
|
match handler.validate(
|
||||||
|
au,
|
||||||
|
cred,
|
||||||
|
time,
|
||||||
|
self.account.uuid,
|
||||||
|
async_tx,
|
||||||
|
webauthn,
|
||||||
|
self.pw_badlist_set.clone(),
|
||||||
|
) {
|
||||||
CredState::Success(claims) => {
|
CredState::Success(claims) => {
|
||||||
lsecurity!(au, "Successful cred handling");
|
lsecurity!(au, "Successful cred handling");
|
||||||
let uat = self
|
let uat = self
|
||||||
|
@ -683,9 +734,13 @@ mod tests {
|
||||||
use crate::credential::Credential;
|
use crate::credential::Credential;
|
||||||
use crate::idm::authsession::{
|
use crate::idm::authsession::{
|
||||||
AuthSession, BAD_AUTH_TYPE_MSG, BAD_PASSWORD_MSG, BAD_TOTP_MSG, BAD_WEBAUTHN_MSG,
|
AuthSession, BAD_AUTH_TYPE_MSG, BAD_PASSWORD_MSG, BAD_TOTP_MSG, BAD_WEBAUTHN_MSG,
|
||||||
|
PW_BADLIST_MSG,
|
||||||
};
|
};
|
||||||
use crate::idm::delayed::DelayedAction;
|
use crate::idm::delayed::DelayedAction;
|
||||||
use crate::idm::AuthState;
|
use crate::idm::AuthState;
|
||||||
|
use crate::value::Value;
|
||||||
|
pub use std::collections::BTreeSet as Set;
|
||||||
|
|
||||||
use crate::utils::duration_from_epoch_now;
|
use crate::utils::duration_from_epoch_now;
|
||||||
use kanidm_proto::v1::{AuthAllowed, AuthCredential, AuthMech};
|
use kanidm_proto::v1::{AuthAllowed, AuthCredential, AuthMech};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
@ -720,6 +775,7 @@ mod tests {
|
||||||
&None,
|
&None,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
duration_from_epoch_now(),
|
duration_from_epoch_now(),
|
||||||
|
Option::None,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let AuthState::Choose(auth_mechs) = state {
|
if let AuthState::Choose(auth_mechs) = state {
|
||||||
|
@ -767,6 +823,7 @@ mod tests {
|
||||||
&Some("NonExistantAppID".to_string()),
|
&Some("NonExistantAppID".to_string()),
|
||||||
&webauthn,
|
&webauthn,
|
||||||
duration_from_epoch_now(),
|
duration_from_epoch_now(),
|
||||||
|
Option::None,
|
||||||
);
|
);
|
||||||
|
|
||||||
// We now ignore appids.
|
// We now ignore appids.
|
||||||
|
@ -785,12 +842,15 @@ mod tests {
|
||||||
$account:expr,
|
$account:expr,
|
||||||
$webauthn:expr
|
$webauthn:expr
|
||||||
) => {{
|
) => {{
|
||||||
|
let mut pw_badlist_set = Set::new();
|
||||||
|
pw_badlist_set.insert(Value::new_iutf8("list@no3IBTyqHu$bad"));
|
||||||
let (session, state) = AuthSession::new(
|
let (session, state) = AuthSession::new(
|
||||||
$audit,
|
$audit,
|
||||||
$account.clone(),
|
$account.clone(),
|
||||||
&None,
|
&None,
|
||||||
$webauthn,
|
$webauthn,
|
||||||
duration_from_epoch_now(),
|
duration_from_epoch_now(),
|
||||||
|
Some(pw_badlist_set),
|
||||||
);
|
);
|
||||||
let mut session = session.unwrap();
|
let mut session = session.unwrap();
|
||||||
|
|
||||||
|
@ -878,18 +938,58 @@ mod tests {
|
||||||
audit.write_log();
|
audit.write_log();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_idm_authsession_simple_password_badlist() {
|
||||||
|
let mut audit = AuditScope::new(
|
||||||
|
"test_idm_authsession_simple_password_badlist",
|
||||||
|
uuid::Uuid::new_v4(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let webauthn = create_webauthn();
|
||||||
|
// create the ent
|
||||||
|
let mut account = entry_str_to_account!(JSON_ADMIN_V1);
|
||||||
|
// manually load in a cred
|
||||||
|
let p = CryptoPolicy::minimum();
|
||||||
|
let cred = Credential::new_password_only(&p, "list@no3IBTyqHu$bad").unwrap();
|
||||||
|
account.primary = Some(cred);
|
||||||
|
|
||||||
|
let (async_tx, mut async_rx) = unbounded();
|
||||||
|
|
||||||
|
// now check, even though the password is correct, Auth should be denied since it is in badlist
|
||||||
|
let mut session = start_password_session!(&mut audit, account, &webauthn);
|
||||||
|
|
||||||
|
let attempt = AuthCredential::Password("list@no3IBTyqHu$bad".to_string());
|
||||||
|
match session.validate_creds(
|
||||||
|
&mut audit,
|
||||||
|
&attempt,
|
||||||
|
&Duration::from_secs(0),
|
||||||
|
&async_tx,
|
||||||
|
&webauthn,
|
||||||
|
) {
|
||||||
|
Ok(AuthState::Denied(msg)) => assert!(msg == PW_BADLIST_MSG),
|
||||||
|
_ => panic!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
drop(async_tx);
|
||||||
|
assert!(async_rx.blocking_recv().is_none());
|
||||||
|
audit.write_log();
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! start_password_mfa_session {
|
macro_rules! start_password_mfa_session {
|
||||||
(
|
(
|
||||||
$audit:expr,
|
$audit:expr,
|
||||||
$account:expr,
|
$account:expr,
|
||||||
$webauthn:expr
|
$webauthn:expr
|
||||||
) => {{
|
) => {{
|
||||||
|
let mut pw_badlist_set = Set::new();
|
||||||
|
pw_badlist_set.insert(Value::new_iutf8("list@no3IBTyqHu$bad"));
|
||||||
let (session, state) = AuthSession::new(
|
let (session, state) = AuthSession::new(
|
||||||
$audit,
|
$audit,
|
||||||
$account.clone(),
|
$account.clone(),
|
||||||
&None,
|
&None,
|
||||||
$webauthn,
|
$webauthn,
|
||||||
duration_from_epoch_now(),
|
duration_from_epoch_now(),
|
||||||
|
Some(pw_badlist_set),
|
||||||
);
|
);
|
||||||
let mut session = session.expect("Session was unable to be created.");
|
let mut session = session.expect("Session was unable to be created.");
|
||||||
|
|
||||||
|
@ -1077,6 +1177,74 @@ mod tests {
|
||||||
audit.write_log();
|
audit.write_log();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_idm_authsession_password_mfa_badlist() {
|
||||||
|
let mut audit = AuditScope::new(
|
||||||
|
"test_idm_authsession_password_mfa_badlist",
|
||||||
|
uuid::Uuid::new_v4(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let webauthn = create_webauthn();
|
||||||
|
// create the ent
|
||||||
|
let mut account = entry_str_to_account!(JSON_ADMIN_V1);
|
||||||
|
|
||||||
|
// Setup a fake time stamp for consistency.
|
||||||
|
let ts = Duration::from_secs(12345);
|
||||||
|
|
||||||
|
// manually load in a cred
|
||||||
|
let totp = TOTP::generate_secure("test_totp".to_string(), TOTP_DEFAULT_STEP);
|
||||||
|
|
||||||
|
let totp_good = totp
|
||||||
|
.do_totp_duration_from_epoch(&ts)
|
||||||
|
.expect("failed to perform totp.");
|
||||||
|
|
||||||
|
let pw_badlist = "list@no3IBTyqHu$bad";
|
||||||
|
|
||||||
|
let p = CryptoPolicy::minimum();
|
||||||
|
let cred = Credential::new_password_only(&p, pw_badlist)
|
||||||
|
.unwrap()
|
||||||
|
.update_totp(totp);
|
||||||
|
// add totp also
|
||||||
|
account.primary = Some(cred);
|
||||||
|
|
||||||
|
let (async_tx, mut async_rx) = unbounded();
|
||||||
|
|
||||||
|
// now check
|
||||||
|
|
||||||
|
// == two step checks
|
||||||
|
|
||||||
|
// check send good totp, should continue
|
||||||
|
// then badlist pw, failed
|
||||||
|
{
|
||||||
|
let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||||
|
|
||||||
|
match session.validate_creds(
|
||||||
|
&mut audit,
|
||||||
|
&AuthCredential::TOTP(totp_good),
|
||||||
|
&ts,
|
||||||
|
&async_tx,
|
||||||
|
&webauthn,
|
||||||
|
) {
|
||||||
|
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
|
||||||
|
_ => panic!(),
|
||||||
|
};
|
||||||
|
match session.validate_creds(
|
||||||
|
&mut audit,
|
||||||
|
&AuthCredential::Password(pw_badlist.to_string()),
|
||||||
|
&ts,
|
||||||
|
&async_tx,
|
||||||
|
&webauthn,
|
||||||
|
) {
|
||||||
|
Ok(AuthState::Denied(msg)) => assert!(msg == PW_BADLIST_MSG),
|
||||||
|
_ => panic!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(async_tx);
|
||||||
|
assert!(async_rx.blocking_recv().is_none());
|
||||||
|
audit.write_log();
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! start_webauthn_only_session {
|
macro_rules! start_webauthn_only_session {
|
||||||
(
|
(
|
||||||
$audit:expr,
|
$audit:expr,
|
||||||
|
@ -1089,6 +1257,7 @@ mod tests {
|
||||||
&None,
|
&None,
|
||||||
$webauthn,
|
$webauthn,
|
||||||
duration_from_epoch_now(),
|
duration_from_epoch_now(),
|
||||||
|
Option::None,
|
||||||
);
|
);
|
||||||
let mut session = session.unwrap();
|
let mut session = session.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -378,7 +378,22 @@ impl<'a> IdmServerWriteTransaction<'a> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let (auth_session, state) = if is_valid {
|
let (auth_session, state) = if is_valid {
|
||||||
AuthSession::new(au, account, &init.appid, self.webauthn, ct)
|
//TODO #397: we can keep a cached map of the badlist, and pass by reference rather than by value
|
||||||
|
let badlist_entry = self
|
||||||
|
.qs_read
|
||||||
|
.internal_search_uuid(au, &UUID_SYSTEM_CONFIG)
|
||||||
|
.map_err(|e| {
|
||||||
|
ladmin_error!(au, "Failed to retrieve system configuration {:?}", e);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
AuthSession::new(
|
||||||
|
au,
|
||||||
|
account,
|
||||||
|
&init.appid,
|
||||||
|
self.webauthn,
|
||||||
|
ct,
|
||||||
|
badlist_entry.get_ava_set("badlist_password").cloned(),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
// it's softlocked, don't even bother.
|
// it's softlocked, don't even bother.
|
||||||
lsecurity!(au, "Account is softlocked.");
|
lsecurity!(au, "Account is softlocked.");
|
||||||
|
@ -2101,6 +2116,23 @@ mod tests {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_idm_simple_password_reject_badlist() {
|
||||||
|
run_idm_test!(|_qs: &QueryServer,
|
||||||
|
idms: &IdmServer,
|
||||||
|
_idms_delayed: &IdmServerDelayed,
|
||||||
|
au: &mut AuditScope| {
|
||||||
|
let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now());
|
||||||
|
|
||||||
|
// Check that the badlist password inserted is rejected.
|
||||||
|
let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, "bad@no3IBTyqHu$list", None);
|
||||||
|
let e = idms_prox_write.set_account_password(au, &pce);
|
||||||
|
assert!(e.is_err());
|
||||||
|
|
||||||
|
assert!(idms_prox_write.commit(au).is_ok());
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_idm_unixusertoken() {
|
fn test_idm_unixusertoken() {
|
||||||
run_idm_test!(|_qs: &QueryServer,
|
run_idm_test!(|_qs: &QueryServer,
|
||||||
|
|
Loading…
Reference in a new issue