mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-24 13:07:00 +01:00
397 Caching password badlist (#425)
This commit is contained in:
parent
77381c1a2a
commit
2bd8606cb6
|
@ -2,6 +2,7 @@ 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::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use hashbrown::HashSet;
|
||||||
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,7 +16,6 @@ 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;
|
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};
|
||||||
|
@ -181,13 +181,13 @@ 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>>,
|
pw_badlist_set: Option<&HashSet<String>>,
|
||||||
) -> 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 {
|
match pw_badlist_set {
|
||||||
Some(p) if p.contains(&PartialValue::new_iutf8(cleartext)) => {
|
Some(p) if p.contains(&cleartext.to_lowercase()) => {
|
||||||
lsecurity!(
|
lsecurity!(
|
||||||
au,
|
au,
|
||||||
"Handler::Password -> Result::Denied - Password found in badlist during login"
|
"Handler::Password -> Result::Denied - Password found in badlist during login"
|
||||||
|
@ -227,7 +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>>,
|
pw_badlist_set: Option<&HashSet<String>>,
|
||||||
) -> 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) => {
|
||||||
|
@ -290,7 +290,7 @@ impl CredHandler {
|
||||||
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 {
|
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;
|
pw_mfa.pw_state = CredVerifyState::Fail;
|
||||||
lsecurity!(
|
lsecurity!(
|
||||||
au,
|
au,
|
||||||
|
@ -404,7 +404,7 @@ 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>>,
|
pw_badlist_set: Option<&HashSet<String>>,
|
||||||
) -> CredState {
|
) -> CredState {
|
||||||
match self {
|
match self {
|
||||||
CredHandler::Anonymous => Self::validate_anonymous(au, cred),
|
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
|
// 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 {
|
||||||
|
@ -512,7 +510,6 @@ 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
|
||||||
|
@ -556,11 +553,7 @@ 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 {
|
let auth_session = AuthSession { account, state };
|
||||||
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.
|
||||||
|
@ -644,6 +637,7 @@ impl AuthSession {
|
||||||
time: &Duration,
|
time: &Duration,
|
||||||
async_tx: &Sender<DelayedAction>,
|
async_tx: &Sender<DelayedAction>,
|
||||||
webauthn: &Webauthn<WebauthnDomainConfig>,
|
webauthn: &Webauthn<WebauthnDomainConfig>,
|
||||||
|
pw_badlist_set: Option<&HashSet<String>>,
|
||||||
) -> Result<AuthState, OperationError> {
|
) -> Result<AuthState, OperationError> {
|
||||||
let (next_state, response) = match &mut self.state {
|
let (next_state, response) = match &mut self.state {
|
||||||
AuthSessionState::Init(_) | AuthSessionState::Success | AuthSessionState::Denied(_) => {
|
AuthSessionState::Init(_) | AuthSessionState::Success | AuthSessionState::Denied(_) => {
|
||||||
|
@ -659,7 +653,7 @@ impl AuthSession {
|
||||||
self.account.uuid,
|
self.account.uuid,
|
||||||
async_tx,
|
async_tx,
|
||||||
webauthn,
|
webauthn,
|
||||||
self.pw_badlist_set.clone(),
|
pw_badlist_set,
|
||||||
) {
|
) {
|
||||||
CredState::Success(claims) => {
|
CredState::Success(claims) => {
|
||||||
lsecurity!(au, "Successful cred handling");
|
lsecurity!(au, "Successful cred handling");
|
||||||
|
@ -738,6 +732,7 @@ mod tests {
|
||||||
use crate::idm::delayed::DelayedAction;
|
use crate::idm::delayed::DelayedAction;
|
||||||
use crate::idm::AuthState;
|
use crate::idm::AuthState;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use hashbrown::HashSet;
|
||||||
pub use std::collections::BTreeSet as Set;
|
pub use std::collections::BTreeSet as Set;
|
||||||
|
|
||||||
use crate::utils::duration_from_epoch_now;
|
use crate::utils::duration_from_epoch_now;
|
||||||
|
@ -749,6 +744,12 @@ mod tests {
|
||||||
use tokio::sync::mpsc::unbounded_channel as unbounded;
|
use tokio::sync::mpsc::unbounded_channel as unbounded;
|
||||||
use webauthn_authenticator_rs::{softtok::U2FSoft, WebauthnAuthenticator};
|
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> {
|
fn create_webauthn() -> Webauthn<WebauthnDomainConfig> {
|
||||||
Webauthn::new(WebauthnDomainConfig {
|
Webauthn::new(WebauthnDomainConfig {
|
||||||
rp_name: "example.com".to_string(),
|
rp_name: "example.com".to_string(),
|
||||||
|
@ -774,7 +775,6 @@ 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 {
|
||||||
|
@ -822,7 +822,6 @@ 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.
|
||||||
|
@ -841,15 +840,12 @@ 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();
|
||||||
|
|
||||||
|
@ -879,7 +875,7 @@ mod tests {
|
||||||
panic!("Invalid auth state")
|
panic!("Invalid auth state")
|
||||||
}
|
}
|
||||||
|
|
||||||
session
|
(session, create_pw_badlist_cache())
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -901,7 +897,8 @@ mod tests {
|
||||||
let (async_tx, mut async_rx) = unbounded();
|
let (async_tx, mut async_rx) = unbounded();
|
||||||
|
|
||||||
// now check
|
// 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());
|
let attempt = AuthCredential::Password("bad_password".to_string());
|
||||||
match session.validate_creds(
|
match session.validate_creds(
|
||||||
|
@ -910,6 +907,7 @@ mod tests {
|
||||||
&Duration::from_secs(0),
|
&Duration::from_secs(0),
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
Some(&pw_badlist_cache),
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Denied(_)) => {}
|
Ok(AuthState::Denied(_)) => {}
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -917,7 +915,8 @@ mod tests {
|
||||||
|
|
||||||
// === Now begin a new session, and use a good pw.
|
// === 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());
|
let attempt = AuthCredential::Password("test_password".to_string());
|
||||||
match session.validate_creds(
|
match session.validate_creds(
|
||||||
|
@ -926,6 +925,7 @@ mod tests {
|
||||||
&Duration::from_secs(0),
|
&Duration::from_secs(0),
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
Some(&pw_badlist_cache),
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Success(_)) => {}
|
Ok(AuthState::Success(_)) => {}
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -955,7 +955,8 @@ mod tests {
|
||||||
let (async_tx, mut async_rx) = unbounded();
|
let (async_tx, mut async_rx) = unbounded();
|
||||||
|
|
||||||
// now check, even though the password is correct, Auth should be denied since it is in badlist
|
// 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());
|
let attempt = AuthCredential::Password("list@no3IBTyqHu$bad".to_string());
|
||||||
match session.validate_creds(
|
match session.validate_creds(
|
||||||
|
@ -964,6 +965,7 @@ mod tests {
|
||||||
&Duration::from_secs(0),
|
&Duration::from_secs(0),
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
Some(&pw_badlist_cache),
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Denied(msg)) => assert!(msg == PW_BADLIST_MSG),
|
Ok(AuthState::Denied(msg)) => assert!(msg == PW_BADLIST_MSG),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -980,15 +982,12 @@ 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.expect("Session was unable to be created.");
|
let mut session = session.expect("Session was unable to be created.");
|
||||||
|
|
||||||
|
@ -1025,7 +1024,7 @@ mod tests {
|
||||||
panic!("Invalid auth state")
|
panic!("Invalid auth state")
|
||||||
}
|
}
|
||||||
|
|
||||||
(session, rchal)
|
(session, rchal, create_pw_badlist_cache())
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1070,7 +1069,8 @@ mod tests {
|
||||||
|
|
||||||
// check send anon (fail)
|
// 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(
|
match session.validate_creds(
|
||||||
&mut audit,
|
&mut audit,
|
||||||
|
@ -1078,6 +1078,7 @@ mod tests {
|
||||||
&ts,
|
&ts,
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
Some(&pw_badlist_cache),
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
|
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -1088,7 +1089,8 @@ mod tests {
|
||||||
|
|
||||||
// Sending a PW first is an immediate fail.
|
// 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(
|
match session.validate_creds(
|
||||||
&mut audit,
|
&mut audit,
|
||||||
|
@ -1096,6 +1098,7 @@ mod tests {
|
||||||
&ts,
|
&ts,
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
Some(&pw_badlist_cache),
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
|
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -1103,7 +1106,8 @@ mod tests {
|
||||||
}
|
}
|
||||||
// check send bad totp, should fail immediate
|
// 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(
|
match session.validate_creds(
|
||||||
&mut audit,
|
&mut audit,
|
||||||
|
@ -1111,6 +1115,7 @@ mod tests {
|
||||||
&ts,
|
&ts,
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
Some(&pw_badlist_cache),
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_TOTP_MSG),
|
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_TOTP_MSG),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -1120,7 +1125,8 @@ mod tests {
|
||||||
// check send good totp, should continue
|
// check send good totp, should continue
|
||||||
// then bad pw, fail pw
|
// 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(
|
match session.validate_creds(
|
||||||
&mut audit,
|
&mut audit,
|
||||||
|
@ -1128,6 +1134,7 @@ mod tests {
|
||||||
&ts,
|
&ts,
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
Some(&pw_badlist_cache),
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
|
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -1138,6 +1145,7 @@ mod tests {
|
||||||
&ts,
|
&ts,
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
Some(&pw_badlist_cache),
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG),
|
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -1147,7 +1155,8 @@ mod tests {
|
||||||
// check send good totp, should continue
|
// check send good totp, should continue
|
||||||
// then good pw, success
|
// 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(
|
match session.validate_creds(
|
||||||
&mut audit,
|
&mut audit,
|
||||||
|
@ -1155,6 +1164,7 @@ mod tests {
|
||||||
&ts,
|
&ts,
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
Some(&pw_badlist_cache),
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
|
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -1165,6 +1175,7 @@ mod tests {
|
||||||
&ts,
|
&ts,
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
Some(&pw_badlist_cache),
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Success(_)) => {}
|
Ok(AuthState::Success(_)) => {}
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -1215,7 +1226,8 @@ mod tests {
|
||||||
// check send good totp, should continue
|
// check send good totp, should continue
|
||||||
// then badlist pw, failed
|
// 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(
|
match session.validate_creds(
|
||||||
&mut audit,
|
&mut audit,
|
||||||
|
@ -1223,6 +1235,7 @@ mod tests {
|
||||||
&ts,
|
&ts,
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
Some(&pw_badlist_cache),
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
|
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -1233,6 +1246,7 @@ mod tests {
|
||||||
&ts,
|
&ts,
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
Some(&pw_badlist_cache),
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Denied(msg)) => assert!(msg == PW_BADLIST_MSG),
|
Ok(AuthState::Denied(msg)) => assert!(msg == PW_BADLIST_MSG),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -1256,7 +1270,6 @@ 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();
|
||||||
|
|
||||||
|
@ -1349,6 +1362,7 @@ mod tests {
|
||||||
&ts,
|
&ts,
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
None,
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
|
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -1369,6 +1383,7 @@ mod tests {
|
||||||
&ts,
|
&ts,
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
None,
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Success(_)) => {}
|
Ok(AuthState::Success(_)) => {}
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -1397,6 +1412,7 @@ mod tests {
|
||||||
&ts,
|
&ts,
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
None,
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_WEBAUTHN_MSG),
|
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_WEBAUTHN_MSG),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -1441,6 +1457,7 @@ mod tests {
|
||||||
&ts,
|
&ts,
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
None,
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_WEBAUTHN_MSG),
|
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_WEBAUTHN_MSG),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -1479,7 +1496,8 @@ mod tests {
|
||||||
|
|
||||||
// check pw first (fail)
|
// 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(
|
match session.validate_creds(
|
||||||
&mut audit,
|
&mut audit,
|
||||||
|
@ -1487,6 +1505,7 @@ mod tests {
|
||||||
&ts,
|
&ts,
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
Some(&pw_badlist_cache),
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
|
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -1495,7 +1514,8 @@ mod tests {
|
||||||
|
|
||||||
// Check totp first attempt fails.
|
// 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(
|
match session.validate_creds(
|
||||||
&mut audit,
|
&mut audit,
|
||||||
|
@ -1503,6 +1523,7 @@ mod tests {
|
||||||
&ts,
|
&ts,
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
Some(&pw_badlist_cache),
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
|
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -1513,8 +1534,10 @@ mod tests {
|
||||||
// NOTE: We only check bad challenge here as bad softtoken is already
|
// NOTE: We only check bad challenge here as bad softtoken is already
|
||||||
// extensively tested.
|
// extensively tested.
|
||||||
{
|
{
|
||||||
let (_session, inv_chal) = start_password_mfa_session!(&mut audit, account, &webauthn);
|
let (_session, inv_chal, pw_badlist_cache) =
|
||||||
let (mut session, _chal) = start_password_mfa_session!(&mut audit, account, &webauthn);
|
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();
|
let inv_chal = inv_chal.unwrap();
|
||||||
|
|
||||||
|
@ -1529,6 +1552,7 @@ mod tests {
|
||||||
&ts,
|
&ts,
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
Some(&pw_badlist_cache),
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_WEBAUTHN_MSG),
|
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_WEBAUTHN_MSG),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -1537,7 +1561,8 @@ mod tests {
|
||||||
|
|
||||||
// check good webauthn/bad pw (fail)
|
// 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 chal = chal.unwrap();
|
||||||
|
|
||||||
let resp = wa
|
let resp = wa
|
||||||
|
@ -1550,6 +1575,7 @@ mod tests {
|
||||||
&ts,
|
&ts,
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
Some(&pw_badlist_cache),
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
|
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -1560,6 +1586,7 @@ mod tests {
|
||||||
&ts,
|
&ts,
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
Some(&pw_badlist_cache),
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG),
|
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -1574,7 +1601,8 @@ mod tests {
|
||||||
|
|
||||||
// Check good webauthn/good pw (pass)
|
// 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 chal = chal.unwrap();
|
||||||
|
|
||||||
let resp = wa
|
let resp = wa
|
||||||
|
@ -1587,6 +1615,7 @@ mod tests {
|
||||||
&ts,
|
&ts,
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
Some(&pw_badlist_cache),
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
|
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -1597,6 +1626,7 @@ mod tests {
|
||||||
&ts,
|
&ts,
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
Some(&pw_badlist_cache),
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Success(_)) => {}
|
Ok(AuthState::Success(_)) => {}
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -1652,7 +1682,8 @@ mod tests {
|
||||||
|
|
||||||
// check pw first (fail)
|
// 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(
|
match session.validate_creds(
|
||||||
&mut audit,
|
&mut audit,
|
||||||
|
@ -1660,6 +1691,7 @@ mod tests {
|
||||||
&ts,
|
&ts,
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
Some(&pw_badlist_cache),
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
|
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -1668,7 +1700,8 @@ mod tests {
|
||||||
|
|
||||||
// Check bad totp (fail)
|
// 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(
|
match session.validate_creds(
|
||||||
&mut audit,
|
&mut audit,
|
||||||
|
@ -1676,6 +1709,7 @@ mod tests {
|
||||||
&ts,
|
&ts,
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
Some(&pw_badlist_cache),
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_TOTP_MSG),
|
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_TOTP_MSG),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -1684,8 +1718,10 @@ mod tests {
|
||||||
|
|
||||||
// check bad webauthn (fail)
|
// check bad webauthn (fail)
|
||||||
{
|
{
|
||||||
let (_session, inv_chal) = start_password_mfa_session!(&mut audit, account, &webauthn);
|
let (_session, inv_chal, pw_badlist_cache) =
|
||||||
let (mut session, _chal) = start_password_mfa_session!(&mut audit, account, &webauthn);
|
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();
|
let inv_chal = inv_chal.unwrap();
|
||||||
|
|
||||||
|
@ -1700,6 +1736,7 @@ mod tests {
|
||||||
&ts,
|
&ts,
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
Some(&pw_badlist_cache),
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_WEBAUTHN_MSG),
|
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_WEBAUTHN_MSG),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -1708,7 +1745,8 @@ mod tests {
|
||||||
|
|
||||||
// check good webauthn/bad pw (fail)
|
// 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 chal = chal.unwrap();
|
||||||
|
|
||||||
let resp = wa
|
let resp = wa
|
||||||
|
@ -1721,6 +1759,7 @@ mod tests {
|
||||||
&ts,
|
&ts,
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
Some(&pw_badlist_cache),
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
|
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -1731,6 +1770,7 @@ mod tests {
|
||||||
&ts,
|
&ts,
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
Some(&pw_badlist_cache),
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG),
|
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -1745,7 +1785,8 @@ mod tests {
|
||||||
|
|
||||||
// check good totp/bad pw (fail)
|
// 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(
|
match session.validate_creds(
|
||||||
&mut audit,
|
&mut audit,
|
||||||
|
@ -1753,6 +1794,7 @@ mod tests {
|
||||||
&ts,
|
&ts,
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
Some(&pw_badlist_cache),
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
|
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -1763,6 +1805,7 @@ mod tests {
|
||||||
&ts,
|
&ts,
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
Some(&pw_badlist_cache),
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG),
|
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -1771,7 +1814,8 @@ mod tests {
|
||||||
|
|
||||||
// check good totp/good pw (pass)
|
// 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(
|
match session.validate_creds(
|
||||||
&mut audit,
|
&mut audit,
|
||||||
|
@ -1779,6 +1823,7 @@ mod tests {
|
||||||
&ts,
|
&ts,
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
Some(&pw_badlist_cache),
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
|
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -1789,6 +1834,7 @@ mod tests {
|
||||||
&ts,
|
&ts,
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
Some(&pw_badlist_cache),
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Success(_)) => {}
|
Ok(AuthState::Success(_)) => {}
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -1797,7 +1843,8 @@ mod tests {
|
||||||
|
|
||||||
// Check good webauthn/good pw (pass)
|
// 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 chal = chal.unwrap();
|
||||||
|
|
||||||
let resp = wa
|
let resp = wa
|
||||||
|
@ -1810,6 +1857,7 @@ mod tests {
|
||||||
&ts,
|
&ts,
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
Some(&pw_badlist_cache),
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
|
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -1820,6 +1868,7 @@ mod tests {
|
||||||
&ts,
|
&ts,
|
||||||
&async_tx,
|
&async_tx,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
|
Some(&pw_badlist_cache),
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Success(_)) => {}
|
Ok(AuthState::Success(_)) => {}
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
|
|
@ -23,6 +23,7 @@ use crate::idm::delayed::{
|
||||||
DelayedAction, PasswordUpgrade, UnixPasswordUpgrade, WebauthnCounterIncrement,
|
DelayedAction, PasswordUpgrade, UnixPasswordUpgrade, WebauthnCounterIncrement,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use hashbrown::HashSet;
|
||||||
use kanidm_proto::v1::CredentialStatus;
|
use kanidm_proto::v1::CredentialStatus;
|
||||||
use kanidm_proto::v1::RadiusAuthToken;
|
use kanidm_proto::v1::RadiusAuthToken;
|
||||||
use kanidm_proto::v1::SetCredentialResponse;
|
use kanidm_proto::v1::SetCredentialResponse;
|
||||||
|
@ -39,10 +40,16 @@ use async_std::task;
|
||||||
use core::task::{Context, Poll};
|
use core::task::{Context, Poll};
|
||||||
use futures::task as futures_task;
|
use futures::task as futures_task;
|
||||||
|
|
||||||
use concread::bptree::{BptreeMap, BptreeMapWriteTxn};
|
use concread::{
|
||||||
use concread::hashmap::HashMap;
|
bptree::{BptreeMap, BptreeMapWriteTxn},
|
||||||
|
CowCell,
|
||||||
|
};
|
||||||
|
use concread::{
|
||||||
|
cowcell::{CowCellReadTxn, CowCellWriteTxn},
|
||||||
|
hashmap::HashMap,
|
||||||
|
};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use std::time::Duration;
|
use std::{sync::Arc, time::Duration};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use webauthn_rs::Webauthn;
|
use webauthn_rs::Webauthn;
|
||||||
|
@ -67,6 +74,7 @@ pub struct IdmServer {
|
||||||
async_tx: Sender<DelayedAction>,
|
async_tx: Sender<DelayedAction>,
|
||||||
// Our webauthn verifier/config
|
// Our webauthn verifier/config
|
||||||
webauthn: Webauthn<WebauthnDomainConfig>,
|
webauthn: Webauthn<WebauthnDomainConfig>,
|
||||||
|
pw_badlist_cache: Arc<CowCell<HashSet<String>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct IdmServerAuthTransaction<'a> {
|
pub struct IdmServerAuthTransaction<'a> {
|
||||||
|
@ -84,6 +92,7 @@ pub struct IdmServerAuthTransaction<'a> {
|
||||||
// For flagging eventual actions.
|
// For flagging eventual actions.
|
||||||
async_tx: Sender<DelayedAction>,
|
async_tx: Sender<DelayedAction>,
|
||||||
webauthn: &'a Webauthn<WebauthnDomainConfig>,
|
webauthn: &'a Webauthn<WebauthnDomainConfig>,
|
||||||
|
pw_badlist_cache: CowCellReadTxn<HashSet<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct IdmServerProxyReadTransaction<'a> {
|
pub struct IdmServerProxyReadTransaction<'a> {
|
||||||
|
@ -101,6 +110,7 @@ pub struct IdmServerProxyWriteTransaction<'a> {
|
||||||
sid: SID,
|
sid: SID,
|
||||||
crypto_policy: &'a CryptoPolicy,
|
crypto_policy: &'a CryptoPolicy,
|
||||||
webauthn: &'a Webauthn<WebauthnDomainConfig>,
|
webauthn: &'a Webauthn<WebauthnDomainConfig>,
|
||||||
|
pw_badlist_cache: CowCellWriteTxn<'a, HashSet<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct IdmServerDelayed {
|
pub struct IdmServerDelayed {
|
||||||
|
@ -125,9 +135,12 @@ impl IdmServer {
|
||||||
let (async_tx, async_rx) = unbounded();
|
let (async_tx, async_rx) = unbounded();
|
||||||
|
|
||||||
// Get the domain name, as the relying party id.
|
// 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());
|
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.
|
// Check that it gels with our origin.
|
||||||
|
@ -170,6 +183,7 @@ impl IdmServer {
|
||||||
crypto_policy,
|
crypto_policy,
|
||||||
async_tx,
|
async_tx,
|
||||||
webauthn,
|
webauthn,
|
||||||
|
pw_badlist_cache: Arc::new(CowCell::new(pw_badlist_set)),
|
||||||
},
|
},
|
||||||
IdmServerDelayed { async_rx },
|
IdmServerDelayed { async_rx },
|
||||||
))
|
))
|
||||||
|
@ -199,6 +213,7 @@ impl IdmServer {
|
||||||
sid,
|
sid,
|
||||||
async_tx: self.async_tx.clone(),
|
async_tx: self.async_tx.clone(),
|
||||||
webauthn: &self.webauthn,
|
webauthn: &self.webauthn,
|
||||||
|
pw_badlist_cache: self.pw_badlist_cache.read(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,6 +245,7 @@ impl IdmServer {
|
||||||
sid,
|
sid,
|
||||||
crypto_policy: &self.crypto_policy,
|
crypto_policy: &self.crypto_policy,
|
||||||
webauthn: &self.webauthn,
|
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 {
|
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
|
AuthSession::new(au, account, &init.appid, self.webauthn, ct)
|
||||||
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.");
|
||||||
|
@ -514,8 +515,16 @@ impl<'a> IdmServerAuthTransaction<'a> {
|
||||||
// Process the credentials here as required.
|
// Process the credentials here as required.
|
||||||
// Basically throw them at the auth_session and see what
|
// Basically throw them at the auth_session and see what
|
||||||
// falls out.
|
// falls out.
|
||||||
|
let pw_badlist_cache = Some(&(*self.pw_badlist_cache));
|
||||||
auth_session
|
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| {
|
.map(|aus| {
|
||||||
// Inspect the result:
|
// Inspect the result:
|
||||||
// if it was a failure, we need to inc the softlock.
|
// 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
|
// check a password badlist to eliminate more content
|
||||||
// we check the password as "lower case" to help eliminate possibilities
|
// we check the password as "lower case" to help eliminate possibilities
|
||||||
let lc_password = PartialValue::new_iutf8(cleartext);
|
// also, when pw_badlist_cache is read from DB, it is read as Value (iutf8 lowercase)
|
||||||
let badlist_entry = self
|
if (&*self.pw_badlist_cache).contains(&cleartext.to_lowercase()) {
|
||||||
.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");
|
lsecurity!(au, "Password found in badlist, rejecting");
|
||||||
Err(OperationError::PasswordBadListed)
|
Err(OperationError::PasswordBadListed)
|
||||||
} else {
|
} 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!(
|
lperf_trace_segment!(
|
||||||
au,
|
au,
|
||||||
"idm::server::IdmServerProxyWriteTransaction::commit",
|
"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.mfareg_sessions.commit();
|
||||||
self.qs_write.commit(au)
|
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 ...
|
// Need tests of the sessions and the auth ...
|
||||||
|
|
|
@ -684,6 +684,32 @@ pub trait QueryServerTransaction<'a> {
|
||||||
e
|
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
|
// Actually conduct a search request
|
||||||
|
|
Loading…
Reference in a new issue