397 Caching password badlist (#425)

This commit is contained in:
vcwai 2021-05-05 12:38:32 +08:00 committed by GitHub
parent 77381c1a2a
commit 2bd8606cb6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 176 additions and 81 deletions

View file

@ -2,6 +2,7 @@ use crate::idm::account::Account;
use crate::idm::claim::Claim;
use crate::idm::AuthState;
use crate::prelude::*;
use hashbrown::HashSet;
use kanidm_proto::v1::OperationError;
use kanidm_proto::v1::{AuthAllowed, AuthCredential, AuthMech};
@ -15,7 +16,6 @@ use crate::credential::webauthn::WebauthnDomainConfig;
use std::time::Duration;
use uuid::Uuid;
// 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::{AuthenticationState, Webauthn};
@ -181,13 +181,13 @@ impl CredHandler {
pw: &mut Password,
who: Uuid,
async_tx: &Sender<DelayedAction>,
pw_badlist_set: Option<Set<Value>>,
pw_badlist_set: Option<&HashSet<String>>,
) -> CredState {
match cred {
AuthCredential::Password(cleartext) => {
if pw.verify(cleartext.as_str()).unwrap_or(false) {
match pw_badlist_set {
Some(p) if p.contains(&PartialValue::new_iutf8(cleartext)) => {
Some(p) if p.contains(&cleartext.to_lowercase()) => {
lsecurity!(
au,
"Handler::Password -> Result::Denied - Password found in badlist during login"
@ -227,7 +227,7 @@ impl CredHandler {
webauthn: &Webauthn<WebauthnDomainConfig>,
who: Uuid,
async_tx: &Sender<DelayedAction>,
pw_badlist_set: Option<Set<Value>>,
pw_badlist_set: Option<&HashSet<String>>,
) -> CredState {
match (&pw_mfa.mfa_state, &pw_mfa.pw_state) {
(CredVerifyState::Init, CredVerifyState::Init) => {
@ -290,7 +290,7 @@ impl CredHandler {
AuthCredential::Password(cleartext) => {
if pw_mfa.pw.verify(cleartext.as_str()).unwrap_or(false) {
match pw_badlist_set {
Some(p) if p.contains(&PartialValue::new_iutf8(cleartext)) => {
Some(p) if p.contains(&cleartext.to_lowercase()) => {
pw_mfa.pw_state = CredVerifyState::Fail;
lsecurity!(
au,
@ -404,7 +404,7 @@ impl CredHandler {
who: Uuid,
async_tx: &Sender<DelayedAction>,
webauthn: &Webauthn<WebauthnDomainConfig>,
pw_badlist_set: Option<Set<Value>>,
pw_badlist_set: Option<&HashSet<String>>,
) -> CredState {
match self {
CredHandler::Anonymous => Self::validate_anonymous(au, cred),
@ -501,8 +501,6 @@ pub(crate) struct AuthSession {
//
// This handler will then handle the mfa and stepping up through to generate the auth states
state: AuthSessionState,
// Store the password badlist
pw_badlist_set: Option<Set<Value>>,
}
impl AuthSession {
@ -512,7 +510,6 @@ impl AuthSession {
_appid: &Option<String>,
webauthn: &Webauthn<WebauthnDomainConfig>,
ct: Duration,
pw_badlist_set: Option<Set<Value>>,
) -> (Option<Self>, AuthState) {
// During this setup, determine the credential handler that we'll be using
// for this session. This is currently based on presentation of an application
@ -556,11 +553,7 @@ impl AuthSession {
(None, AuthState::Denied(reason.to_string()))
} else {
// We can proceed
let auth_session = AuthSession {
account,
state,
pw_badlist_set,
};
let auth_session = AuthSession { account, state };
// Get the set of mechanisms that can proceed. This is tied
// to the session so that it can mutate state and have progression
// of what's next, or ordering.
@ -644,6 +637,7 @@ impl AuthSession {
time: &Duration,
async_tx: &Sender<DelayedAction>,
webauthn: &Webauthn<WebauthnDomainConfig>,
pw_badlist_set: Option<&HashSet<String>>,
) -> Result<AuthState, OperationError> {
let (next_state, response) = match &mut self.state {
AuthSessionState::Init(_) | AuthSessionState::Success | AuthSessionState::Denied(_) => {
@ -659,7 +653,7 @@ impl AuthSession {
self.account.uuid,
async_tx,
webauthn,
self.pw_badlist_set.clone(),
pw_badlist_set,
) {
CredState::Success(claims) => {
lsecurity!(au, "Successful cred handling");
@ -738,6 +732,7 @@ mod tests {
use crate::idm::delayed::DelayedAction;
use crate::idm::AuthState;
use crate::prelude::*;
use hashbrown::HashSet;
pub use std::collections::BTreeSet as Set;
use crate::utils::duration_from_epoch_now;
@ -749,6 +744,12 @@ mod tests {
use tokio::sync::mpsc::unbounded_channel as unbounded;
use webauthn_authenticator_rs::{softtok::U2FSoft, WebauthnAuthenticator};
fn create_pw_badlist_cache() -> HashSet<String> {
let mut s = HashSet::new();
s.insert((&"list@no3IBTyqHu$bad").to_lowercase());
s
}
fn create_webauthn() -> Webauthn<WebauthnDomainConfig> {
Webauthn::new(WebauthnDomainConfig {
rp_name: "example.com".to_string(),
@ -774,7 +775,6 @@ mod tests {
&None,
&webauthn,
duration_from_epoch_now(),
Option::None,
);
if let AuthState::Choose(auth_mechs) = state {
@ -822,7 +822,6 @@ mod tests {
&Some("NonExistantAppID".to_string()),
&webauthn,
duration_from_epoch_now(),
Option::None,
);
// We now ignore appids.
@ -841,15 +840,12 @@ mod tests {
$account: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(
$audit,
$account.clone(),
&None,
$webauthn,
duration_from_epoch_now(),
Some(pw_badlist_set),
);
let mut session = session.unwrap();
@ -879,7 +875,7 @@ mod tests {
panic!("Invalid auth state")
}
session
(session, create_pw_badlist_cache())
}};
}
@ -901,7 +897,8 @@ mod tests {
let (async_tx, mut async_rx) = unbounded();
// now check
let mut session = start_password_session!(&mut audit, account, &webauthn);
let (mut session, pw_badlist_cache) =
start_password_session!(&mut audit, account, &webauthn);
let attempt = AuthCredential::Password("bad_password".to_string());
match session.validate_creds(
@ -910,6 +907,7 @@ mod tests {
&Duration::from_secs(0),
&async_tx,
&webauthn,
Some(&pw_badlist_cache),
) {
Ok(AuthState::Denied(_)) => {}
_ => panic!(),
@ -917,7 +915,8 @@ mod tests {
// === Now begin a new session, and use a good pw.
let mut session = start_password_session!(&mut audit, account, &webauthn);
let (mut session, pw_badlist_cache) =
start_password_session!(&mut audit, account, &webauthn);
let attempt = AuthCredential::Password("test_password".to_string());
match session.validate_creds(
@ -926,6 +925,7 @@ mod tests {
&Duration::from_secs(0),
&async_tx,
&webauthn,
Some(&pw_badlist_cache),
) {
Ok(AuthState::Success(_)) => {}
_ => panic!(),
@ -955,7 +955,8 @@ mod tests {
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 (mut session, pw_badlist_cache) =
start_password_session!(&mut audit, account, &webauthn);
let attempt = AuthCredential::Password("list@no3IBTyqHu$bad".to_string());
match session.validate_creds(
@ -964,6 +965,7 @@ mod tests {
&Duration::from_secs(0),
&async_tx,
&webauthn,
Some(&pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == PW_BADLIST_MSG),
_ => panic!(),
@ -980,15 +982,12 @@ mod tests {
$account: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(
$audit,
$account.clone(),
&None,
$webauthn,
duration_from_epoch_now(),
Some(pw_badlist_set),
);
let mut session = session.expect("Session was unable to be created.");
@ -1025,7 +1024,7 @@ mod tests {
panic!("Invalid auth state")
}
(session, rchal)
(session, rchal, create_pw_badlist_cache())
}};
}
@ -1070,7 +1069,8 @@ mod tests {
// check send anon (fail)
{
let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn);
let (mut session, _, pw_badlist_cache) =
start_password_mfa_session!(&mut audit, account, &webauthn);
match session.validate_creds(
&mut audit,
@ -1078,6 +1078,7 @@ mod tests {
&ts,
&async_tx,
&webauthn,
Some(&pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
_ => panic!(),
@ -1088,7 +1089,8 @@ mod tests {
// Sending a PW first is an immediate fail.
{
let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn);
let (mut session, _, pw_badlist_cache) =
start_password_mfa_session!(&mut audit, account, &webauthn);
match session.validate_creds(
&mut audit,
@ -1096,6 +1098,7 @@ mod tests {
&ts,
&async_tx,
&webauthn,
Some(&pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
_ => panic!(),
@ -1103,7 +1106,8 @@ mod tests {
}
// check send bad totp, should fail immediate
{
let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn);
let (mut session, _, pw_badlist_cache) =
start_password_mfa_session!(&mut audit, account, &webauthn);
match session.validate_creds(
&mut audit,
@ -1111,6 +1115,7 @@ mod tests {
&ts,
&async_tx,
&webauthn,
Some(&pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_TOTP_MSG),
_ => panic!(),
@ -1120,7 +1125,8 @@ mod tests {
// check send good totp, should continue
// then bad pw, fail pw
{
let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn);
let (mut session, _, pw_badlist_cache) =
start_password_mfa_session!(&mut audit, account, &webauthn);
match session.validate_creds(
&mut audit,
@ -1128,6 +1134,7 @@ mod tests {
&ts,
&async_tx,
&webauthn,
Some(&pw_badlist_cache),
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -1138,6 +1145,7 @@ mod tests {
&ts,
&async_tx,
&webauthn,
Some(&pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG),
_ => panic!(),
@ -1147,7 +1155,8 @@ mod tests {
// check send good totp, should continue
// then good pw, success
{
let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn);
let (mut session, _, pw_badlist_cache) =
start_password_mfa_session!(&mut audit, account, &webauthn);
match session.validate_creds(
&mut audit,
@ -1155,6 +1164,7 @@ mod tests {
&ts,
&async_tx,
&webauthn,
Some(&pw_badlist_cache),
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -1165,6 +1175,7 @@ mod tests {
&ts,
&async_tx,
&webauthn,
Some(&pw_badlist_cache),
) {
Ok(AuthState::Success(_)) => {}
_ => panic!(),
@ -1215,7 +1226,8 @@ mod tests {
// check send good totp, should continue
// then badlist pw, failed
{
let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn);
let (mut session, _, pw_badlist_cache) =
start_password_mfa_session!(&mut audit, account, &webauthn);
match session.validate_creds(
&mut audit,
@ -1223,6 +1235,7 @@ mod tests {
&ts,
&async_tx,
&webauthn,
Some(&pw_badlist_cache),
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -1233,6 +1246,7 @@ mod tests {
&ts,
&async_tx,
&webauthn,
Some(&pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == PW_BADLIST_MSG),
_ => panic!(),
@ -1256,7 +1270,6 @@ mod tests {
&None,
$webauthn,
duration_from_epoch_now(),
Option::None,
);
let mut session = session.unwrap();
@ -1349,6 +1362,7 @@ mod tests {
&ts,
&async_tx,
&webauthn,
None,
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
_ => panic!(),
@ -1369,6 +1383,7 @@ mod tests {
&ts,
&async_tx,
&webauthn,
None,
) {
Ok(AuthState::Success(_)) => {}
_ => panic!(),
@ -1397,6 +1412,7 @@ mod tests {
&ts,
&async_tx,
&webauthn,
None,
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_WEBAUTHN_MSG),
_ => panic!(),
@ -1441,6 +1457,7 @@ mod tests {
&ts,
&async_tx,
&webauthn,
None,
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_WEBAUTHN_MSG),
_ => panic!(),
@ -1479,7 +1496,8 @@ mod tests {
// check pw first (fail)
{
let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn);
let (mut session, _, pw_badlist_cache) =
start_password_mfa_session!(&mut audit, account, &webauthn);
match session.validate_creds(
&mut audit,
@ -1487,6 +1505,7 @@ mod tests {
&ts,
&async_tx,
&webauthn,
Some(&pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
_ => panic!(),
@ -1495,7 +1514,8 @@ mod tests {
// Check totp first attempt fails.
{
let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn);
let (mut session, _, pw_badlist_cache) =
start_password_mfa_session!(&mut audit, account, &webauthn);
match session.validate_creds(
&mut audit,
@ -1503,6 +1523,7 @@ mod tests {
&ts,
&async_tx,
&webauthn,
Some(&pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
_ => panic!(),
@ -1513,8 +1534,10 @@ mod tests {
// NOTE: We only check bad challenge here as bad softtoken is already
// extensively tested.
{
let (_session, inv_chal) = start_password_mfa_session!(&mut audit, account, &webauthn);
let (mut session, _chal) = start_password_mfa_session!(&mut audit, account, &webauthn);
let (_session, inv_chal, pw_badlist_cache) =
start_password_mfa_session!(&mut audit, account, &webauthn);
let (mut session, _chal, _) =
start_password_mfa_session!(&mut audit, account, &webauthn);
let inv_chal = inv_chal.unwrap();
@ -1529,6 +1552,7 @@ mod tests {
&ts,
&async_tx,
&webauthn,
Some(&pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_WEBAUTHN_MSG),
_ => panic!(),
@ -1537,7 +1561,8 @@ mod tests {
// check good webauthn/bad pw (fail)
{
let (mut session, chal) = start_password_mfa_session!(&mut audit, account, &webauthn);
let (mut session, chal, pw_badlist_cache) =
start_password_mfa_session!(&mut audit, account, &webauthn);
let chal = chal.unwrap();
let resp = wa
@ -1550,6 +1575,7 @@ mod tests {
&ts,
&async_tx,
&webauthn,
Some(&pw_badlist_cache),
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -1560,6 +1586,7 @@ mod tests {
&ts,
&async_tx,
&webauthn,
Some(&pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG),
_ => panic!(),
@ -1574,7 +1601,8 @@ mod tests {
// Check good webauthn/good pw (pass)
{
let (mut session, chal) = start_password_mfa_session!(&mut audit, account, &webauthn);
let (mut session, chal, pw_badlist_cache) =
start_password_mfa_session!(&mut audit, account, &webauthn);
let chal = chal.unwrap();
let resp = wa
@ -1587,6 +1615,7 @@ mod tests {
&ts,
&async_tx,
&webauthn,
Some(&pw_badlist_cache),
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -1597,6 +1626,7 @@ mod tests {
&ts,
&async_tx,
&webauthn,
Some(&pw_badlist_cache),
) {
Ok(AuthState::Success(_)) => {}
_ => panic!(),
@ -1652,7 +1682,8 @@ mod tests {
// check pw first (fail)
{
let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn);
let (mut session, _, pw_badlist_cache) =
start_password_mfa_session!(&mut audit, account, &webauthn);
match session.validate_creds(
&mut audit,
@ -1660,6 +1691,7 @@ mod tests {
&ts,
&async_tx,
&webauthn,
Some(&pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
_ => panic!(),
@ -1668,7 +1700,8 @@ mod tests {
// Check bad totp (fail)
{
let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn);
let (mut session, _, pw_badlist_cache) =
start_password_mfa_session!(&mut audit, account, &webauthn);
match session.validate_creds(
&mut audit,
@ -1676,6 +1709,7 @@ mod tests {
&ts,
&async_tx,
&webauthn,
Some(&pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_TOTP_MSG),
_ => panic!(),
@ -1684,8 +1718,10 @@ mod tests {
// check bad webauthn (fail)
{
let (_session, inv_chal) = start_password_mfa_session!(&mut audit, account, &webauthn);
let (mut session, _chal) = start_password_mfa_session!(&mut audit, account, &webauthn);
let (_session, inv_chal, pw_badlist_cache) =
start_password_mfa_session!(&mut audit, account, &webauthn);
let (mut session, _chal, _) =
start_password_mfa_session!(&mut audit, account, &webauthn);
let inv_chal = inv_chal.unwrap();
@ -1700,6 +1736,7 @@ mod tests {
&ts,
&async_tx,
&webauthn,
Some(&pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_WEBAUTHN_MSG),
_ => panic!(),
@ -1708,7 +1745,8 @@ mod tests {
// check good webauthn/bad pw (fail)
{
let (mut session, chal) = start_password_mfa_session!(&mut audit, account, &webauthn);
let (mut session, chal, pw_badlist_cache) =
start_password_mfa_session!(&mut audit, account, &webauthn);
let chal = chal.unwrap();
let resp = wa
@ -1721,6 +1759,7 @@ mod tests {
&ts,
&async_tx,
&webauthn,
Some(&pw_badlist_cache),
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -1731,6 +1770,7 @@ mod tests {
&ts,
&async_tx,
&webauthn,
Some(&pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG),
_ => panic!(),
@ -1745,7 +1785,8 @@ mod tests {
// check good totp/bad pw (fail)
{
let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn);
let (mut session, _, pw_badlist_cache) =
start_password_mfa_session!(&mut audit, account, &webauthn);
match session.validate_creds(
&mut audit,
@ -1753,6 +1794,7 @@ mod tests {
&ts,
&async_tx,
&webauthn,
Some(&pw_badlist_cache),
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -1763,6 +1805,7 @@ mod tests {
&ts,
&async_tx,
&webauthn,
Some(&pw_badlist_cache),
) {
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG),
_ => panic!(),
@ -1771,7 +1814,8 @@ mod tests {
// check good totp/good pw (pass)
{
let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn);
let (mut session, _, pw_badlist_cache) =
start_password_mfa_session!(&mut audit, account, &webauthn);
match session.validate_creds(
&mut audit,
@ -1779,6 +1823,7 @@ mod tests {
&ts,
&async_tx,
&webauthn,
Some(&pw_badlist_cache),
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -1789,6 +1834,7 @@ mod tests {
&ts,
&async_tx,
&webauthn,
Some(&pw_badlist_cache),
) {
Ok(AuthState::Success(_)) => {}
_ => panic!(),
@ -1797,7 +1843,8 @@ mod tests {
// Check good webauthn/good pw (pass)
{
let (mut session, chal) = start_password_mfa_session!(&mut audit, account, &webauthn);
let (mut session, chal, pw_badlist_cache) =
start_password_mfa_session!(&mut audit, account, &webauthn);
let chal = chal.unwrap();
let resp = wa
@ -1810,6 +1857,7 @@ mod tests {
&ts,
&async_tx,
&webauthn,
Some(&pw_badlist_cache),
) {
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
_ => panic!(),
@ -1820,6 +1868,7 @@ mod tests {
&ts,
&async_tx,
&webauthn,
Some(&pw_badlist_cache),
) {
Ok(AuthState::Success(_)) => {}
_ => panic!(),

View file

@ -23,6 +23,7 @@ use crate::idm::delayed::{
DelayedAction, PasswordUpgrade, UnixPasswordUpgrade, WebauthnCounterIncrement,
};
use hashbrown::HashSet;
use kanidm_proto::v1::CredentialStatus;
use kanidm_proto::v1::RadiusAuthToken;
use kanidm_proto::v1::SetCredentialResponse;
@ -39,10 +40,16 @@ use async_std::task;
use core::task::{Context, Poll};
use futures::task as futures_task;
use concread::bptree::{BptreeMap, BptreeMapWriteTxn};
use concread::hashmap::HashMap;
use concread::{
bptree::{BptreeMap, BptreeMapWriteTxn},
CowCell,
};
use concread::{
cowcell::{CowCellReadTxn, CowCellWriteTxn},
hashmap::HashMap,
};
use rand::prelude::*;
use std::time::Duration;
use std::{sync::Arc, time::Duration};
use url::Url;
use webauthn_rs::Webauthn;
@ -67,6 +74,7 @@ pub struct IdmServer {
async_tx: Sender<DelayedAction>,
// Our webauthn verifier/config
webauthn: Webauthn<WebauthnDomainConfig>,
pw_badlist_cache: Arc<CowCell<HashSet<String>>>,
}
pub struct IdmServerAuthTransaction<'a> {
@ -84,6 +92,7 @@ pub struct IdmServerAuthTransaction<'a> {
// For flagging eventual actions.
async_tx: Sender<DelayedAction>,
webauthn: &'a Webauthn<WebauthnDomainConfig>,
pw_badlist_cache: CowCellReadTxn<HashSet<String>>,
}
pub struct IdmServerProxyReadTransaction<'a> {
@ -101,6 +110,7 @@ pub struct IdmServerProxyWriteTransaction<'a> {
sid: SID,
crypto_policy: &'a CryptoPolicy,
webauthn: &'a Webauthn<WebauthnDomainConfig>,
pw_badlist_cache: CowCellWriteTxn<'a, HashSet<String>>,
}
pub struct IdmServerDelayed {
@ -125,9 +135,12 @@ impl IdmServer {
let (async_tx, async_rx) = unbounded();
// Get the domain name, as the relying party id.
let rp_id = {
let (rp_id, pw_badlist_set) = {
let qs_read = task::block_on(qs.read_async());
qs_read.get_domain_name(au)?
(
qs_read.get_domain_name(au)?,
qs_read.get_password_badlist(au)?,
)
};
// Check that it gels with our origin.
@ -170,6 +183,7 @@ impl IdmServer {
crypto_policy,
async_tx,
webauthn,
pw_badlist_cache: Arc::new(CowCell::new(pw_badlist_set)),
},
IdmServerDelayed { async_rx },
))
@ -199,6 +213,7 @@ impl IdmServer {
sid,
async_tx: self.async_tx.clone(),
webauthn: &self.webauthn,
pw_badlist_cache: self.pw_badlist_cache.read(),
}
}
@ -230,6 +245,7 @@ impl IdmServer {
sid,
crypto_policy: &self.crypto_policy,
webauthn: &self.webauthn,
pw_badlist_cache: self.pw_badlist_cache.write(),
}
}
@ -369,22 +385,7 @@ impl<'a> IdmServerAuthTransaction<'a> {
};
let (auth_session, state) = if is_valid {
//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(),
)
AuthSession::new(au, account, &init.appid, self.webauthn, ct)
} else {
// it's softlocked, don't even bother.
lsecurity!(au, "Account is softlocked.");
@ -514,8 +515,16 @@ impl<'a> IdmServerAuthTransaction<'a> {
// Process the credentials here as required.
// Basically throw them at the auth_session and see what
// falls out.
let pw_badlist_cache = Some(&(*self.pw_badlist_cache));
auth_session
.validate_creds(au, &creds.cred, &ct, &self.async_tx, self.webauthn)
.validate_creds(
au,
&creds.cred,
&ct,
&self.async_tx,
self.webauthn,
pw_badlist_cache,
)
.map(|aus| {
// Inspect the result:
// if it was a failure, we need to inc the softlock.
@ -897,15 +906,8 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
// check a password badlist to eliminate more content
// we check the password as "lower case" to help eliminate possibilities
let lc_password = PartialValue::new_iutf8(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) {
// also, when pw_badlist_cache is read from DB, it is read as Value (iutf8 lowercase)
if (&*self.pw_badlist_cache).contains(&cleartext.to_lowercase()) {
lsecurity!(au, "Password found in badlist, rejecting");
Err(OperationError::PasswordBadListed)
} else {
@ -1517,16 +1519,34 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
}
}
pub fn commit(self, au: &mut AuditScope) -> Result<(), OperationError> {
pub fn commit(mut self, au: &mut AuditScope) -> Result<(), OperationError> {
lperf_trace_segment!(
au,
"idm::server::IdmServerProxyWriteTransaction::commit",
|| {
if self
.qs_write
.get_changed_uuids()
.contains(&UUID_SYSTEM_CONFIG)
{
self.reload_password_badlist(au)?;
self.pw_badlist_cache.commit();
};
self.mfareg_sessions.commit();
self.qs_write.commit(au)
}
)
}
fn reload_password_badlist(&mut self, au: &mut AuditScope) -> Result<(), OperationError> {
match self.qs_write.get_password_badlist(au) {
Ok(badlist_entry) => {
*self.pw_badlist_cache = badlist_entry;
Ok(())
}
Err(e) => Err(e),
}
}
}
// Need tests of the sessions and the auth ...

View file

@ -684,6 +684,32 @@ pub trait QueryServerTransaction<'a> {
e
})
}
// This is a helper to get password badlist.
fn get_password_badlist(
&self,
audit: &mut AuditScope,
) -> Result<HashSet<String>, OperationError> {
self.internal_search_uuid(audit, &UUID_SYSTEM_CONFIG)
.and_then(|e| match e.get_ava_set("badlist_password") {
Some(badlist_entry) => {
let mut badlist_hashset = HashSet::with_capacity(badlist_entry.len());
badlist_entry
.iter()
.filter_map(|e| e.as_string())
.cloned()
.for_each(|s| {
badlist_hashset.insert(s);
});
Ok(badlist_hashset)
}
None => Err(OperationError::InvalidEntryState),
})
.map_err(|e| {
ladmin_error!(audit, "Failed to retrieve system configuration {:?}", e);
e
})
}
}
// Actually conduct a search request