mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 12:37:00 +01:00
Absolutely minimal implementation (#1711)
* Absolutely minimal implementation * Add support for ip address to audit event
This commit is contained in:
parent
152bf95e71
commit
0ba4aec86b
|
@ -1,5 +1,6 @@
|
|||
use std::convert::TryFrom;
|
||||
use std::fs;
|
||||
use std::net::IpAddr;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
|
@ -110,6 +111,7 @@ impl QueryServerReadV1 {
|
|||
sessionid: Option<Uuid>,
|
||||
req: AuthRequest,
|
||||
eventid: Uuid,
|
||||
ip_addr: IpAddr,
|
||||
) -> Result<AuthResult, OperationError> {
|
||||
// This is probably the first function that really implements logic
|
||||
// "on top" of the db server concept. In this case we check if
|
||||
|
@ -132,10 +134,12 @@ impl QueryServerReadV1 {
|
|||
// the session are enforced.
|
||||
idm_auth.expire_auth_sessions(ct).await;
|
||||
|
||||
let source = Source::Https(ip_addr);
|
||||
|
||||
// Generally things like auth denied are in Ok() msgs
|
||||
// so true errors should always trigger a rollback.
|
||||
let res = idm_auth
|
||||
.auth(&ae, ct)
|
||||
.auth(&ae, ct, source)
|
||||
.await
|
||||
.and_then(|r| idm_auth.commit().map(|_| r));
|
||||
|
||||
|
@ -155,6 +159,7 @@ impl QueryServerReadV1 {
|
|||
uat: Option<String>,
|
||||
issue: AuthIssueSession,
|
||||
eventid: Uuid,
|
||||
ip_addr: IpAddr,
|
||||
) -> Result<AuthResult, OperationError> {
|
||||
let ct = duration_from_epoch_now();
|
||||
let mut idm_auth = self.idms.auth().await;
|
||||
|
@ -172,10 +177,12 @@ impl QueryServerReadV1 {
|
|||
// the session are enforced.
|
||||
idm_auth.expire_auth_sessions(ct).await;
|
||||
|
||||
let source = Source::Https(ip_addr);
|
||||
|
||||
// Generally things like auth denied are in Ok() msgs
|
||||
// so true errors should always trigger a rollback.
|
||||
let res = idm_auth
|
||||
.reauth_init(ident, issue, ct)
|
||||
.reauth_init(ident, issue, ct, source)
|
||||
.await
|
||||
.and_then(|r| idm_auth.commit().map(|_| r));
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ mod v1;
|
|||
mod v1_scim;
|
||||
|
||||
use std::fs::canonicalize;
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
|
@ -71,6 +72,7 @@ pub struct AppState {
|
|||
pub jws_validator: std::sync::Arc<JwsValidator>,
|
||||
/// The SHA384 hashes of javascript files we're going to serve to users
|
||||
pub js_files: Vec<JavaScriptFile>,
|
||||
pub(crate) trust_x_forward_for: bool,
|
||||
}
|
||||
|
||||
pub trait RequestExtensions {
|
||||
|
@ -85,6 +87,8 @@ pub trait RequestExtensions {
|
|||
fn get_url_param_uuid(&self, param: &str) -> Result<Uuid, tide::Error>;
|
||||
|
||||
fn new_eventid(&self) -> (Uuid, String);
|
||||
|
||||
fn get_remote_addr(&self) -> Option<IpAddr>;
|
||||
}
|
||||
|
||||
impl RequestExtensions for tide::Request<AppState> {
|
||||
|
@ -178,6 +182,16 @@ impl RequestExtensions for tide::Request<AppState> {
|
|||
let hv = eventid.as_hyphenated().to_string();
|
||||
(eventid, hv)
|
||||
}
|
||||
|
||||
fn get_remote_addr(&self) -> Option<IpAddr> {
|
||||
if self.state().trust_x_forward_for {
|
||||
self.remote()
|
||||
} else {
|
||||
self.peer_addr()
|
||||
}
|
||||
.and_then(|add_str| add_str.parse().ok())
|
||||
.map(|s_ad: SocketAddr| s_ad.ip())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_tide_response<T: Serialize>(
|
||||
|
@ -387,6 +401,7 @@ pub async fn create_https_server(
|
|||
jws_signer,
|
||||
jws_validator,
|
||||
js_files: js_files.to_owned(),
|
||||
trust_x_forward_for,
|
||||
});
|
||||
|
||||
// Add the logging subsystem.
|
||||
|
|
|
@ -1068,6 +1068,14 @@ pub async fn reauth(mut req: tide::Request<AppState>) -> tide::Result {
|
|||
let uat = req.get_current_uat();
|
||||
let (eventid, hvalue) = req.new_eventid();
|
||||
|
||||
let ip_addr = req.get_remote_addr().ok_or_else(|| {
|
||||
error!("Unable to process remote addr, refusing to proceed");
|
||||
tide::Error::from_str(
|
||||
tide::StatusCode::InternalServerError,
|
||||
"unable to validate peer address",
|
||||
)
|
||||
})?;
|
||||
|
||||
let obj: AuthIssueSession = req.body_json().await.map_err(|e| {
|
||||
debug!("Failed get body JSON? {:?}", e);
|
||||
e
|
||||
|
@ -1077,7 +1085,7 @@ pub async fn reauth(mut req: tide::Request<AppState>) -> tide::Result {
|
|||
.state()
|
||||
// This may change in the future ...
|
||||
.qe_r_ref
|
||||
.handle_reauth(uat, obj, eventid)
|
||||
.handle_reauth(uat, obj, eventid, ip_addr)
|
||||
.await;
|
||||
|
||||
auth_session_state_management(req, inter, hvalue)
|
||||
|
@ -1091,6 +1099,14 @@ pub async fn auth(mut req: tide::Request<AppState>) -> tide::Result {
|
|||
|
||||
let maybe_sessionid: Option<Uuid> = req.get_current_auth_session_id();
|
||||
|
||||
let ip_addr = req.get_remote_addr().ok_or_else(|| {
|
||||
error!("Unable to process remote addr, refusing to proceed");
|
||||
tide::Error::from_str(
|
||||
tide::StatusCode::InternalServerError,
|
||||
"unable to validate peer address",
|
||||
)
|
||||
})?;
|
||||
|
||||
let obj: AuthRequest = req.body_json().await.map_err(|e| {
|
||||
debug!("Failed get body JSON? {:?}", e);
|
||||
e
|
||||
|
@ -1103,7 +1119,7 @@ pub async fn auth(mut req: tide::Request<AppState>) -> tide::Result {
|
|||
.state()
|
||||
// This may change in the future ...
|
||||
.qe_r_ref
|
||||
.handle_auth(maybe_sessionid, obj, eventid)
|
||||
.handle_auth(maybe_sessionid, obj, eventid, ip_addr)
|
||||
.await;
|
||||
|
||||
auth_session_state_management(req, inter, hvalue)
|
||||
|
|
|
@ -39,7 +39,6 @@ use kanidm_proto::messages::{AccountChangeMessage, MessageStatus};
|
|||
use kanidm_proto::v1::OperationError;
|
||||
use kanidmd_lib::be::{Backend, BackendConfig, BackendTransaction, FsType};
|
||||
use kanidmd_lib::idm::ldap::LdapServer;
|
||||
use kanidmd_lib::idm::server::{IdmServer, IdmServerDelayed};
|
||||
use kanidmd_lib::prelude::*;
|
||||
use kanidmd_lib::schema::Schema;
|
||||
use kanidmd_lib::status::StatusActor;
|
||||
|
@ -101,7 +100,7 @@ async fn setup_qs_idms(
|
|||
be: Backend,
|
||||
schema: Schema,
|
||||
config: &Configuration,
|
||||
) -> Result<(QueryServer, IdmServer, IdmServerDelayed), OperationError> {
|
||||
) -> Result<(QueryServer, IdmServer, IdmServerDelayed, IdmServerAudit), OperationError> {
|
||||
// Create a query_server implementation
|
||||
let query_server = QueryServer::new(be, schema, config.domain.clone());
|
||||
|
||||
|
@ -119,9 +118,10 @@ async fn setup_qs_idms(
|
|||
|
||||
// We generate a SINGLE idms only!
|
||||
|
||||
let (idms, idms_delayed) = IdmServer::new(query_server.clone(), &config.origin).await?;
|
||||
let (idms, idms_delayed, idms_audit) =
|
||||
IdmServer::new(query_server.clone(), &config.origin).await?;
|
||||
|
||||
Ok((query_server, idms, idms_delayed))
|
||||
Ok((query_server, idms, idms_delayed, idms_audit))
|
||||
}
|
||||
|
||||
async fn setup_qs(
|
||||
|
@ -297,7 +297,7 @@ pub async fn restore_server_core(config: &Configuration, dst_path: &str) {
|
|||
|
||||
info!("Attempting to init query server ...");
|
||||
|
||||
let (qs, _idms, _idms_delayed) = match setup_qs_idms(be, schema, config).await {
|
||||
let (qs, _idms, _idms_delayed, _idms_audit) = match setup_qs_idms(be, schema, config).await {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
error!("Unable to setup query server or idm server -> {:?}", e);
|
||||
|
@ -354,7 +354,7 @@ pub async fn reindex_server_core(config: &Configuration) {
|
|||
|
||||
eprintln!("Attempting to init query server ...");
|
||||
|
||||
let (qs, _idms, _idms_delayed) = match setup_qs_idms(be, schema, config).await {
|
||||
let (qs, _idms, _idms_delayed, _idms_audit) = match setup_qs_idms(be, schema, config).await {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
error!("Unable to setup query server or idm server -> {:?}", e);
|
||||
|
@ -515,7 +515,7 @@ pub async fn recover_account_core(config: &Configuration, name: &str) {
|
|||
}
|
||||
};
|
||||
// setup the qs - *with* init of the migrations and schema.
|
||||
let (_qs, idms, _idms_delayed) = match setup_qs_idms(be, schema, config).await {
|
||||
let (_qs, idms, _idms_delayed, _idms_audit) = match setup_qs_idms(be, schema, config).await {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
error!("Unable to setup query server or idm server -> {:?}", e);
|
||||
|
@ -651,13 +651,14 @@ pub async fn create_server_core(
|
|||
}
|
||||
};
|
||||
// Start the IDM server.
|
||||
let (_qs, idms, mut idms_delayed) = match setup_qs_idms(be, schema, &config).await {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
error!("Unable to setup query server or idm server -> {:?}", e);
|
||||
return Err(());
|
||||
}
|
||||
};
|
||||
let (_qs, idms, mut idms_delayed, mut idms_audit) =
|
||||
match setup_qs_idms(be, schema, &config).await {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
error!("Unable to setup query server or idm server -> {:?}", e);
|
||||
return Err(());
|
||||
}
|
||||
};
|
||||
|
||||
// Extract any configuration from the IDMS that we may need.
|
||||
// For now we just do this per run, but we need to extract this from the db later.
|
||||
|
@ -735,6 +736,33 @@ pub async fn create_server_core(
|
|||
info!("Stopped DelayedActionActor");
|
||||
});
|
||||
|
||||
let mut broadcast_rx = broadcast_tx.subscribe();
|
||||
|
||||
let auditd_handle = tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::select! {
|
||||
Ok(action) = broadcast_rx.recv() => {
|
||||
match action {
|
||||
CoreAction::Shutdown => break,
|
||||
}
|
||||
}
|
||||
audit_event = idms_audit.audit_rx().recv() => {
|
||||
match serde_json::to_string(&audit_event) {
|
||||
Ok(audit_event) => {
|
||||
warn!(%audit_event);
|
||||
}
|
||||
Err(e) => {
|
||||
error!(err=?e, "Unable to process audit event to json.");
|
||||
warn!(?audit_event, json=false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
info!("Stopped AuditdActor");
|
||||
});
|
||||
|
||||
// Setup timed events associated to the write thread
|
||||
let interval_handle = IntervalActor::start(server_write_ref, broadcast_tx.subscribe());
|
||||
// Setup timed events associated to the read thread
|
||||
|
@ -811,7 +839,7 @@ pub async fn create_server_core(
|
|||
Some(h)
|
||||
};
|
||||
|
||||
let mut handles = vec![interval_handle, delayed_handle];
|
||||
let mut handles = vec![interval_handle, delayed_handle, auditd_handle];
|
||||
|
||||
if let Some(backup_handle) = maybe_backup_handle {
|
||||
handles.push(backup_handle)
|
||||
|
|
|
@ -192,7 +192,9 @@ pub(crate) fn qs_pair_test(_args: &TokenStream, item: TokenStream, with_init: bo
|
|||
result.into()
|
||||
}
|
||||
|
||||
pub(crate) fn idm_test(_args: &TokenStream, item: TokenStream) -> TokenStream {
|
||||
pub(crate) fn idm_test(args: &TokenStream, item: TokenStream) -> TokenStream {
|
||||
let audit = args.to_string() == "audit";
|
||||
|
||||
let input: syn::ItemFn = match syn::parse(item.clone()) {
|
||||
Ok(it) => it,
|
||||
Err(e) => return token_stream_with_error(item, e),
|
||||
|
@ -237,6 +239,16 @@ pub(crate) fn idm_test(_args: &TokenStream, item: TokenStream) -> TokenStream {
|
|||
let test_fn = &input.sig.ident;
|
||||
let test_driver = Ident::new(&format!("idm_{}", test_fn), input.sig.span());
|
||||
|
||||
let test_fn_args = if audit {
|
||||
quote! {
|
||||
&test_server, &mut idms_delayed, &mut idms_audit
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
&test_server, &mut idms_delayed
|
||||
}
|
||||
};
|
||||
|
||||
// Effectively we are just injecting a real test function around this which we will
|
||||
// call.
|
||||
|
||||
|
@ -246,9 +258,9 @@ pub(crate) fn idm_test(_args: &TokenStream, item: TokenStream) -> TokenStream {
|
|||
#header
|
||||
fn #test_driver() {
|
||||
let body = async {
|
||||
let (test_server, mut idms_delayed) = crate::testkit::setup_idm_test().await;
|
||||
let (test_server, mut idms_delayed, mut idms_audit) = crate::testkit::setup_idm_test().await;
|
||||
|
||||
#test_fn(&test_server, &mut idms_delayed).await;
|
||||
#test_fn(#test_fn_args).await;
|
||||
|
||||
// Any needed teardown?
|
||||
// Make sure there are no errors.
|
||||
|
@ -258,6 +270,7 @@ pub(crate) fn idm_test(_args: &TokenStream, item: TokenStream) -> TokenStream {
|
|||
assert!(verifications.len() == 0);
|
||||
|
||||
idms_delayed.check_is_empty_or_panic();
|
||||
idms_audit.check_is_empty_or_panic();
|
||||
};
|
||||
#[allow(clippy::expect_used, clippy::diverging_sub_expression)]
|
||||
{
|
||||
|
|
30
server/lib/src/idm/audit.rs
Normal file
30
server/lib/src/idm/audit.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
use crate::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::net::IpAddr;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
pub enum AuditSource {
|
||||
Internal,
|
||||
Https(IpAddr),
|
||||
}
|
||||
|
||||
impl From<Source> for AuditSource {
|
||||
fn from(value: Source) -> Self {
|
||||
match value {
|
||||
Source::Internal => AuditSource::Internal,
|
||||
Source::Https(ip) => AuditSource::Https(ip),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
pub enum AuditEvent {
|
||||
AuthenticationDenied {
|
||||
source: AuditSource,
|
||||
uuid: Uuid,
|
||||
spn: String,
|
||||
#[serde(with = "time::serde::timestamp")]
|
||||
time: OffsetDateTime,
|
||||
},
|
||||
}
|
|
@ -28,6 +28,7 @@ use webauthn_rs::prelude::{
|
|||
use crate::credential::totp::Totp;
|
||||
use crate::credential::{BackupCodes, Credential, CredentialType, Password};
|
||||
use crate::idm::account::Account;
|
||||
use crate::idm::audit::AuditEvent;
|
||||
use crate::idm::delayed::{
|
||||
AuthSessionRecord, BackupCodeRemoval, DelayedAction, PasswordUpgrade, WebauthnCounterIncrement,
|
||||
};
|
||||
|
@ -737,6 +738,9 @@ pub(crate) struct AuthSession {
|
|||
// What is the "intent" behind this auth session? Are we doing an initial auth? Or a re-auth
|
||||
// for a privilege grant?
|
||||
intent: AuthIntent,
|
||||
|
||||
// Where did the event come from?
|
||||
source: Source,
|
||||
}
|
||||
|
||||
impl AuthSession {
|
||||
|
@ -748,6 +752,7 @@ impl AuthSession {
|
|||
issue: AuthIssueSession,
|
||||
webauthn: &Webauthn,
|
||||
ct: Duration,
|
||||
source: Source,
|
||||
) -> (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
|
||||
|
@ -803,6 +808,7 @@ impl AuthSession {
|
|||
state,
|
||||
issue,
|
||||
intent: AuthIntent::InitialAuth,
|
||||
source,
|
||||
};
|
||||
// Get the set of mechanisms that can proceed. This is tied
|
||||
// to the session so that it can mutate state and have progression
|
||||
|
@ -827,6 +833,7 @@ impl AuthSession {
|
|||
issue: AuthIssueSession,
|
||||
webauthn: &Webauthn,
|
||||
ct: Duration,
|
||||
source: Source,
|
||||
) -> (Option<Self>, AuthState) {
|
||||
/// An inner enum to allow us to more easily define state within this fn
|
||||
enum State {
|
||||
|
@ -893,6 +900,7 @@ impl AuthSession {
|
|||
session_id,
|
||||
session_expiry: session.expiry,
|
||||
},
|
||||
source,
|
||||
};
|
||||
|
||||
let as_state = AuthState::Continue(allow);
|
||||
|
@ -995,6 +1003,7 @@ impl AuthSession {
|
|||
cred: &AuthCredential,
|
||||
time: Duration,
|
||||
async_tx: &Sender<DelayedAction>,
|
||||
audit_tx: &Sender<AuditEvent>,
|
||||
webauthn: &Webauthn,
|
||||
pw_badlist_set: Option<&HashSet<String>>,
|
||||
uat_jwt_signer: &JwsSigner,
|
||||
|
@ -1041,6 +1050,17 @@ impl AuthSession {
|
|||
(None, Ok(AuthState::Continue(allowed.into_iter().collect())))
|
||||
}
|
||||
CredState::Denied(reason) => {
|
||||
if audit_tx
|
||||
.send(AuditEvent::AuthenticationDenied {
|
||||
source: self.source.clone().into(),
|
||||
spn: self.account.spn.clone(),
|
||||
uuid: self.account.uuid,
|
||||
time: OffsetDateTime::UNIX_EPOCH + time,
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
error!("Unable to submit audit event to queue");
|
||||
}
|
||||
security_info!(%reason, "Credentials denied");
|
||||
(
|
||||
Some(AuthSessionState::Denied(reason)),
|
||||
|
@ -1204,6 +1224,7 @@ mod tests {
|
|||
|
||||
use crate::credential::totp::{Totp, TOTP_DEFAULT_STEP};
|
||||
use crate::credential::{BackupCodes, Credential};
|
||||
use crate::idm::audit::AuditEvent;
|
||||
use crate::idm::authsession::{
|
||||
AuthSession, BAD_AUTH_TYPE_MSG, BAD_BACKUPCODE_MSG, BAD_PASSWORD_MSG, BAD_TOTP_MSG,
|
||||
BAD_WEBAUTHN_MSG, PW_BADLIST_MSG,
|
||||
|
@ -1246,6 +1267,7 @@ mod tests {
|
|||
AuthIssueSession::Token,
|
||||
&webauthn,
|
||||
duration_from_epoch_now(),
|
||||
Source::Internal,
|
||||
);
|
||||
|
||||
if let AuthState::Choose(auth_mechs) = state {
|
||||
|
@ -1279,6 +1301,7 @@ mod tests {
|
|||
AuthIssueSession::Token,
|
||||
$webauthn,
|
||||
duration_from_epoch_now(),
|
||||
Source::Internal,
|
||||
);
|
||||
let mut session = session.unwrap();
|
||||
|
||||
|
@ -1316,6 +1339,7 @@ mod tests {
|
|||
account.primary = Some(cred);
|
||||
|
||||
let (async_tx, mut async_rx) = unbounded();
|
||||
let (audit_tx, mut audit_rx) = unbounded();
|
||||
|
||||
// now check
|
||||
let (mut session, pw_badlist_cache) =
|
||||
|
@ -1327,6 +1351,7 @@ mod tests {
|
|||
&attempt,
|
||||
Duration::from_secs(0),
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -1335,6 +1360,11 @@ mod tests {
|
|||
_ => panic!(),
|
||||
};
|
||||
|
||||
match audit_rx.try_recv() {
|
||||
Ok(AuditEvent::AuthenticationDenied { .. }) => {}
|
||||
_ => assert!(false),
|
||||
}
|
||||
|
||||
// === Now begin a new session, and use a good pw.
|
||||
|
||||
let (mut session, pw_badlist_cache) =
|
||||
|
@ -1345,6 +1375,7 @@ mod tests {
|
|||
&attempt,
|
||||
Duration::from_secs(0),
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -1360,6 +1391,8 @@ mod tests {
|
|||
|
||||
drop(async_tx);
|
||||
assert!(async_rx.blocking_recv().is_none());
|
||||
drop(audit_tx);
|
||||
assert!(audit_rx.blocking_recv().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1375,6 +1408,7 @@ mod tests {
|
|||
account.primary = Some(cred);
|
||||
|
||||
let (async_tx, mut async_rx) = unbounded();
|
||||
let (audit_tx, mut audit_rx) = unbounded();
|
||||
|
||||
// now check, even though the password is correct, Auth should be denied since it is in badlist
|
||||
let (mut session, pw_badlist_cache) =
|
||||
|
@ -1385,6 +1419,7 @@ mod tests {
|
|||
&attempt,
|
||||
Duration::from_secs(0),
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -1393,8 +1428,15 @@ mod tests {
|
|||
_ => panic!(),
|
||||
};
|
||||
|
||||
match audit_rx.try_recv() {
|
||||
Ok(AuditEvent::AuthenticationDenied { .. }) => {}
|
||||
_ => assert!(false),
|
||||
}
|
||||
|
||||
drop(async_tx);
|
||||
assert!(async_rx.blocking_recv().is_none());
|
||||
drop(audit_tx);
|
||||
assert!(audit_rx.blocking_recv().is_none());
|
||||
}
|
||||
|
||||
macro_rules! start_password_mfa_session {
|
||||
|
@ -1407,6 +1449,7 @@ mod tests {
|
|||
AuthIssueSession::Token,
|
||||
$webauthn,
|
||||
duration_from_epoch_now(),
|
||||
Source::Internal,
|
||||
);
|
||||
let mut session = session.expect("Session was unable to be created.");
|
||||
|
||||
|
@ -1487,6 +1530,7 @@ mod tests {
|
|||
account.primary = Some(cred);
|
||||
|
||||
let (async_tx, mut async_rx) = unbounded();
|
||||
let (audit_tx, mut audit_rx) = unbounded();
|
||||
|
||||
// now check
|
||||
|
||||
|
@ -1499,6 +1543,7 @@ mod tests {
|
|||
&AuthCredential::Anonymous,
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -1506,6 +1551,11 @@ mod tests {
|
|||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
match audit_rx.try_recv() {
|
||||
Ok(AuditEvent::AuthenticationDenied { .. }) => {}
|
||||
_ => assert!(false),
|
||||
}
|
||||
}
|
||||
|
||||
// == two step checks
|
||||
|
@ -1519,6 +1569,7 @@ mod tests {
|
|||
&AuthCredential::Password(pw_bad.to_string()),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -1526,6 +1577,11 @@ mod tests {
|
|||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
match audit_rx.try_recv() {
|
||||
Ok(AuditEvent::AuthenticationDenied { .. }) => {}
|
||||
_ => assert!(false),
|
||||
}
|
||||
}
|
||||
// check send bad totp, should fail immediate
|
||||
{
|
||||
|
@ -1536,6 +1592,7 @@ mod tests {
|
|||
&AuthCredential::Totp(totp_bad),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -1543,6 +1600,11 @@ mod tests {
|
|||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_TOTP_MSG),
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
match audit_rx.try_recv() {
|
||||
Ok(AuditEvent::AuthenticationDenied { .. }) => {}
|
||||
_ => assert!(false),
|
||||
}
|
||||
}
|
||||
|
||||
// check send good totp, should continue
|
||||
|
@ -1555,6 +1617,7 @@ mod tests {
|
|||
&AuthCredential::Totp(totp_good),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -1566,6 +1629,7 @@ mod tests {
|
|||
&AuthCredential::Password(pw_bad.to_string()),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -1573,6 +1637,11 @@ mod tests {
|
|||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG),
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
match audit_rx.try_recv() {
|
||||
Ok(AuditEvent::AuthenticationDenied { .. }) => {}
|
||||
_ => assert!(false),
|
||||
}
|
||||
}
|
||||
|
||||
// check send good totp, should continue
|
||||
|
@ -1585,6 +1654,7 @@ mod tests {
|
|||
&AuthCredential::Totp(totp_good),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -1596,6 +1666,7 @@ mod tests {
|
|||
&AuthCredential::Password(pw_good.to_string()),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -1612,6 +1683,8 @@ mod tests {
|
|||
|
||||
drop(async_tx);
|
||||
assert!(async_rx.blocking_recv().is_none());
|
||||
drop(audit_tx);
|
||||
assert!(audit_rx.blocking_recv().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1642,6 +1715,7 @@ mod tests {
|
|||
account.primary = Some(cred);
|
||||
|
||||
let (async_tx, mut async_rx) = unbounded();
|
||||
let (audit_tx, mut audit_rx) = unbounded();
|
||||
|
||||
// now check
|
||||
|
||||
|
@ -1657,6 +1731,7 @@ mod tests {
|
|||
&AuthCredential::Totp(totp_good),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -1668,6 +1743,7 @@ mod tests {
|
|||
&AuthCredential::Password(pw_badlist.to_string()),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -1675,10 +1751,17 @@ mod tests {
|
|||
Ok(AuthState::Denied(msg)) => assert!(msg == PW_BADLIST_MSG),
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
match audit_rx.try_recv() {
|
||||
Ok(AuditEvent::AuthenticationDenied { .. }) => {}
|
||||
_ => assert!(false),
|
||||
}
|
||||
}
|
||||
|
||||
drop(async_tx);
|
||||
assert!(async_rx.blocking_recv().is_none());
|
||||
drop(audit_tx);
|
||||
assert!(audit_rx.blocking_recv().is_none());
|
||||
}
|
||||
|
||||
macro_rules! start_webauthn_only_session {
|
||||
|
@ -1692,6 +1775,7 @@ mod tests {
|
|||
AuthIssueSession::Token,
|
||||
$webauthn,
|
||||
duration_from_epoch_now(),
|
||||
Source::Internal,
|
||||
);
|
||||
let mut session = session.unwrap();
|
||||
|
||||
|
@ -1782,6 +1866,7 @@ mod tests {
|
|||
fn test_idm_authsession_webauthn_only_mech() {
|
||||
sketching::test_init();
|
||||
let (async_tx, mut async_rx) = unbounded();
|
||||
let (audit_tx, mut audit_rx) = unbounded();
|
||||
let ts = duration_from_epoch_now();
|
||||
// create the ent
|
||||
let mut account = entry_to_account!(E_ADMIN_V1.clone());
|
||||
|
@ -1803,6 +1888,7 @@ mod tests {
|
|||
&AuthCredential::Anonymous,
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
None,
|
||||
&jws_signer,
|
||||
|
@ -1810,6 +1896,11 @@ mod tests {
|
|||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
match audit_rx.try_recv() {
|
||||
Ok(AuditEvent::AuthenticationDenied { .. }) => {}
|
||||
_ => assert!(false),
|
||||
}
|
||||
}
|
||||
|
||||
// Check good challenge
|
||||
|
@ -1825,6 +1916,7 @@ mod tests {
|
|||
&AuthCredential::Passkey(resp),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
None,
|
||||
&jws_signer,
|
||||
|
@ -1859,6 +1951,7 @@ mod tests {
|
|||
&AuthCredential::Passkey(resp),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
None,
|
||||
&jws_signer,
|
||||
|
@ -1866,6 +1959,11 @@ mod tests {
|
|||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_WEBAUTHN_MSG),
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
match audit_rx.try_recv() {
|
||||
Ok(AuditEvent::AuthenticationDenied { .. }) => {}
|
||||
_ => assert!(false),
|
||||
}
|
||||
}
|
||||
|
||||
// Use an incorrect softtoken.
|
||||
|
@ -1902,6 +2000,7 @@ mod tests {
|
|||
&AuthCredential::Passkey(resp),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
None,
|
||||
&jws_signer,
|
||||
|
@ -1909,16 +2008,24 @@ mod tests {
|
|||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_WEBAUTHN_MSG),
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
match audit_rx.try_recv() {
|
||||
Ok(AuditEvent::AuthenticationDenied { .. }) => {}
|
||||
_ => assert!(false),
|
||||
}
|
||||
}
|
||||
|
||||
drop(async_tx);
|
||||
assert!(async_rx.blocking_recv().is_none());
|
||||
drop(audit_tx);
|
||||
assert!(audit_rx.blocking_recv().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_idm_authsession_webauthn_password_mech() {
|
||||
sketching::test_init();
|
||||
let (async_tx, mut async_rx) = unbounded();
|
||||
let (audit_tx, mut audit_rx) = unbounded();
|
||||
let ts = duration_from_epoch_now();
|
||||
// create the ent
|
||||
let mut account = entry_to_account!(E_ADMIN_V1);
|
||||
|
@ -1946,6 +2053,7 @@ mod tests {
|
|||
&AuthCredential::Password(pw_bad.to_string()),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -1953,6 +2061,11 @@ mod tests {
|
|||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
match audit_rx.try_recv() {
|
||||
Ok(AuditEvent::AuthenticationDenied { .. }) => {}
|
||||
_ => assert!(false),
|
||||
}
|
||||
}
|
||||
|
||||
// Check totp first attempt fails.
|
||||
|
@ -1964,6 +2077,7 @@ mod tests {
|
|||
&AuthCredential::Totp(0),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -1971,6 +2085,11 @@ mod tests {
|
|||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
match audit_rx.try_recv() {
|
||||
Ok(AuditEvent::AuthenticationDenied { .. }) => {}
|
||||
_ => assert!(false),
|
||||
}
|
||||
}
|
||||
|
||||
// check bad webauthn (fail)
|
||||
|
@ -1993,6 +2112,7 @@ mod tests {
|
|||
&AuthCredential::SecurityKey(resp),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -2000,6 +2120,11 @@ mod tests {
|
|||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_WEBAUTHN_MSG),
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
match audit_rx.try_recv() {
|
||||
Ok(AuditEvent::AuthenticationDenied { .. }) => {}
|
||||
_ => assert!(false),
|
||||
}
|
||||
}
|
||||
|
||||
// check good webauthn/bad pw (fail)
|
||||
|
@ -2017,6 +2142,7 @@ mod tests {
|
|||
&AuthCredential::SecurityKey(resp),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -2028,6 +2154,7 @@ mod tests {
|
|||
&AuthCredential::Password(pw_bad.to_string()),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -2036,6 +2163,11 @@ mod tests {
|
|||
_ => panic!(),
|
||||
};
|
||||
|
||||
match audit_rx.try_recv() {
|
||||
Ok(AuditEvent::AuthenticationDenied { .. }) => {}
|
||||
_ => assert!(false),
|
||||
}
|
||||
|
||||
// Check the async counter update was sent.
|
||||
match async_rx.blocking_recv() {
|
||||
Some(DelayedAction::WebauthnCounterIncrement(_)) => {}
|
||||
|
@ -2058,6 +2190,7 @@ mod tests {
|
|||
&AuthCredential::SecurityKey(resp),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -2069,6 +2202,7 @@ mod tests {
|
|||
&AuthCredential::Password(pw_good.to_string()),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -2090,12 +2224,15 @@ mod tests {
|
|||
|
||||
drop(async_tx);
|
||||
assert!(async_rx.blocking_recv().is_none());
|
||||
drop(audit_tx);
|
||||
assert!(audit_rx.blocking_recv().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_idm_authsession_webauthn_password_totp_mech() {
|
||||
sketching::test_init();
|
||||
let (async_tx, mut async_rx) = unbounded();
|
||||
let (audit_tx, mut audit_rx) = unbounded();
|
||||
let ts = duration_from_epoch_now();
|
||||
// create the ent
|
||||
let mut account = entry_to_account!(E_ADMIN_V1);
|
||||
|
@ -2134,6 +2271,7 @@ mod tests {
|
|||
&AuthCredential::Password(pw_bad.to_string()),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -2141,6 +2279,11 @@ mod tests {
|
|||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
match audit_rx.try_recv() {
|
||||
Ok(AuditEvent::AuthenticationDenied { .. }) => {}
|
||||
_ => assert!(false),
|
||||
}
|
||||
}
|
||||
|
||||
// Check bad totp (fail)
|
||||
|
@ -2152,6 +2295,7 @@ mod tests {
|
|||
&AuthCredential::Totp(totp_bad),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -2159,6 +2303,11 @@ mod tests {
|
|||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_TOTP_MSG),
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
match audit_rx.try_recv() {
|
||||
Ok(AuditEvent::AuthenticationDenied { .. }) => {}
|
||||
_ => assert!(false),
|
||||
}
|
||||
}
|
||||
|
||||
// check bad webauthn (fail)
|
||||
|
@ -2179,6 +2328,7 @@ mod tests {
|
|||
&AuthCredential::SecurityKey(resp),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -2186,6 +2336,11 @@ mod tests {
|
|||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_WEBAUTHN_MSG),
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
match audit_rx.try_recv() {
|
||||
Ok(AuditEvent::AuthenticationDenied { .. }) => {}
|
||||
_ => assert!(false),
|
||||
}
|
||||
}
|
||||
|
||||
// check good webauthn/bad pw (fail)
|
||||
|
@ -2203,6 +2358,7 @@ mod tests {
|
|||
&AuthCredential::SecurityKey(resp),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -2214,6 +2370,7 @@ mod tests {
|
|||
&AuthCredential::Password(pw_bad.to_string()),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -2222,6 +2379,11 @@ mod tests {
|
|||
_ => panic!(),
|
||||
};
|
||||
|
||||
match audit_rx.try_recv() {
|
||||
Ok(AuditEvent::AuthenticationDenied { .. }) => {}
|
||||
_ => assert!(false),
|
||||
}
|
||||
|
||||
// Check the async counter update was sent.
|
||||
match async_rx.blocking_recv() {
|
||||
Some(DelayedAction::WebauthnCounterIncrement(_)) => {}
|
||||
|
@ -2238,6 +2400,7 @@ mod tests {
|
|||
&AuthCredential::Totp(totp_good),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -2249,6 +2412,7 @@ mod tests {
|
|||
&AuthCredential::Password(pw_bad.to_string()),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -2256,6 +2420,11 @@ mod tests {
|
|||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG),
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
match audit_rx.try_recv() {
|
||||
Ok(AuditEvent::AuthenticationDenied { .. }) => {}
|
||||
_ => assert!(false),
|
||||
}
|
||||
}
|
||||
|
||||
// check good totp/good pw (pass)
|
||||
|
@ -2267,6 +2436,7 @@ mod tests {
|
|||
&AuthCredential::Totp(totp_good),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -2278,6 +2448,7 @@ mod tests {
|
|||
&AuthCredential::Password(pw_good.to_string()),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -2307,6 +2478,7 @@ mod tests {
|
|||
&AuthCredential::SecurityKey(resp),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -2318,6 +2490,7 @@ mod tests {
|
|||
&AuthCredential::Password(pw_good.to_string()),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -2339,6 +2512,8 @@ mod tests {
|
|||
|
||||
drop(async_tx);
|
||||
assert!(async_rx.blocking_recv().is_none());
|
||||
drop(audit_tx);
|
||||
assert!(audit_rx.blocking_recv().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -2381,6 +2556,7 @@ mod tests {
|
|||
account.primary = Some(cred);
|
||||
|
||||
let (async_tx, mut async_rx) = unbounded();
|
||||
let (audit_tx, mut audit_rx) = unbounded();
|
||||
|
||||
// now check
|
||||
// == two step checks
|
||||
|
@ -2394,6 +2570,7 @@ mod tests {
|
|||
&AuthCredential::Password(pw_bad.to_string()),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -2401,6 +2578,11 @@ mod tests {
|
|||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
match audit_rx.try_recv() {
|
||||
Ok(AuditEvent::AuthenticationDenied { .. }) => {}
|
||||
_ => assert!(false),
|
||||
}
|
||||
}
|
||||
// check send wrong backup code, should fail immediate
|
||||
{
|
||||
|
@ -2411,6 +2593,7 @@ mod tests {
|
|||
&AuthCredential::BackupCode(backup_code_bad),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -2418,6 +2601,11 @@ mod tests {
|
|||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_BACKUPCODE_MSG),
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
match audit_rx.try_recv() {
|
||||
Ok(AuditEvent::AuthenticationDenied { .. }) => {}
|
||||
_ => assert!(false),
|
||||
}
|
||||
}
|
||||
// check send good backup code, should continue
|
||||
// then bad pw, fail pw
|
||||
|
@ -2429,6 +2617,7 @@ mod tests {
|
|||
&AuthCredential::BackupCode(backup_code_good.clone()),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -2440,6 +2629,7 @@ mod tests {
|
|||
&AuthCredential::Password(pw_bad.to_string()),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -2447,6 +2637,11 @@ mod tests {
|
|||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG),
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
match audit_rx.try_recv() {
|
||||
Ok(AuditEvent::AuthenticationDenied { .. }) => {}
|
||||
_ => assert!(false),
|
||||
}
|
||||
}
|
||||
// Can't process BackupCodeRemoval without the server instance
|
||||
match async_rx.blocking_recv() {
|
||||
|
@ -2464,6 +2659,7 @@ mod tests {
|
|||
&AuthCredential::BackupCode(backup_code_good),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -2475,6 +2671,7 @@ mod tests {
|
|||
&AuthCredential::Password(pw_good.to_string()),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -2506,6 +2703,7 @@ mod tests {
|
|||
&AuthCredential::Totp(totp_good),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -2517,6 +2715,7 @@ mod tests {
|
|||
&AuthCredential::Password(pw_good.to_string()),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -2534,6 +2733,8 @@ mod tests {
|
|||
|
||||
drop(async_tx);
|
||||
assert!(async_rx.blocking_recv().is_none());
|
||||
drop(audit_tx);
|
||||
assert!(audit_rx.blocking_recv().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -2574,6 +2775,7 @@ mod tests {
|
|||
account.primary = Some(cred);
|
||||
|
||||
let (async_tx, mut async_rx) = unbounded();
|
||||
let (audit_tx, mut audit_rx) = unbounded();
|
||||
|
||||
// Test totp_a
|
||||
{
|
||||
|
@ -2584,6 +2786,7 @@ mod tests {
|
|||
&AuthCredential::Totp(totp_good_a),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -2595,6 +2798,7 @@ mod tests {
|
|||
&AuthCredential::Password(pw_good.to_string()),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -2618,6 +2822,7 @@ mod tests {
|
|||
&AuthCredential::Totp(totp_good_b),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -2629,6 +2834,7 @@ mod tests {
|
|||
&AuthCredential::Password(pw_good.to_string()),
|
||||
ts,
|
||||
&async_tx,
|
||||
&audit_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
|
@ -2645,5 +2851,7 @@ mod tests {
|
|||
|
||||
drop(async_tx);
|
||||
assert!(async_rx.blocking_recv().is_none());
|
||||
drop(audit_tx);
|
||||
assert!(audit_rx.blocking_recv().is_none());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1762,7 +1762,7 @@ mod tests {
|
|||
|
||||
let auth_init = AuthEvent::named_init("testperson");
|
||||
|
||||
let r1 = idms_auth.auth(&auth_init, ct).await;
|
||||
let r1 = idms_auth.auth(&auth_init, ct, Source::Internal).await;
|
||||
let ar = r1.unwrap();
|
||||
let AuthResult { sessionid, state } = ar;
|
||||
|
||||
|
@ -1773,7 +1773,7 @@ mod tests {
|
|||
|
||||
let auth_begin = AuthEvent::begin_mech(sessionid, AuthMech::Password);
|
||||
|
||||
let r2 = idms_auth.auth(&auth_begin, ct).await;
|
||||
let r2 = idms_auth.auth(&auth_begin, ct, Source::Internal).await;
|
||||
let ar = r2.unwrap();
|
||||
let AuthResult { sessionid, state } = ar;
|
||||
|
||||
|
@ -1782,7 +1782,7 @@ mod tests {
|
|||
let pw_step = AuthEvent::cred_step_password(sessionid, pw);
|
||||
|
||||
// Expect success
|
||||
let r2 = idms_auth.auth(&pw_step, ct).await;
|
||||
let r2 = idms_auth.auth(&pw_step, ct, Source::Internal).await;
|
||||
debug!("r2 ==> {:?}", r2);
|
||||
idms_auth.commit().expect("Must not fail");
|
||||
|
||||
|
@ -1812,7 +1812,7 @@ mod tests {
|
|||
|
||||
let auth_init = AuthEvent::named_init("testperson");
|
||||
|
||||
let r1 = idms_auth.auth(&auth_init, ct).await;
|
||||
let r1 = idms_auth.auth(&auth_init, ct, Source::Internal).await;
|
||||
let ar = r1.unwrap();
|
||||
let AuthResult { sessionid, state } = ar;
|
||||
|
||||
|
@ -1823,7 +1823,7 @@ mod tests {
|
|||
|
||||
let auth_begin = AuthEvent::begin_mech(sessionid, AuthMech::PasswordMfa);
|
||||
|
||||
let r2 = idms_auth.auth(&auth_begin, ct).await;
|
||||
let r2 = idms_auth.auth(&auth_begin, ct, Source::Internal).await;
|
||||
let ar = r2.unwrap();
|
||||
let AuthResult { sessionid, state } = ar;
|
||||
|
||||
|
@ -1834,7 +1834,7 @@ mod tests {
|
|||
.expect("Failed to perform totp step");
|
||||
|
||||
let totp_step = AuthEvent::cred_step_totp(sessionid, totp);
|
||||
let r2 = idms_auth.auth(&totp_step, ct).await;
|
||||
let r2 = idms_auth.auth(&totp_step, ct, Source::Internal).await;
|
||||
let ar = r2.unwrap();
|
||||
let AuthResult { sessionid, state } = ar;
|
||||
|
||||
|
@ -1843,7 +1843,7 @@ mod tests {
|
|||
let pw_step = AuthEvent::cred_step_password(sessionid, pw);
|
||||
|
||||
// Expect success
|
||||
let r3 = idms_auth.auth(&pw_step, ct).await;
|
||||
let r3 = idms_auth.auth(&pw_step, ct, Source::Internal).await;
|
||||
debug!("r3 ==> {:?}", r3);
|
||||
idms_auth.commit().expect("Must not fail");
|
||||
|
||||
|
@ -1872,7 +1872,7 @@ mod tests {
|
|||
|
||||
let auth_init = AuthEvent::named_init("testperson");
|
||||
|
||||
let r1 = idms_auth.auth(&auth_init, ct).await;
|
||||
let r1 = idms_auth.auth(&auth_init, ct, Source::Internal).await;
|
||||
let ar = r1.unwrap();
|
||||
let AuthResult { sessionid, state } = ar;
|
||||
|
||||
|
@ -1883,14 +1883,14 @@ mod tests {
|
|||
|
||||
let auth_begin = AuthEvent::begin_mech(sessionid, AuthMech::PasswordMfa);
|
||||
|
||||
let r2 = idms_auth.auth(&auth_begin, ct).await;
|
||||
let r2 = idms_auth.auth(&auth_begin, ct, Source::Internal).await;
|
||||
let ar = r2.unwrap();
|
||||
let AuthResult { sessionid, state } = ar;
|
||||
|
||||
assert!(matches!(state, AuthState::Continue(_)));
|
||||
|
||||
let code_step = AuthEvent::cred_step_backup_code(sessionid, code);
|
||||
let r2 = idms_auth.auth(&code_step, ct).await;
|
||||
let r2 = idms_auth.auth(&code_step, ct, Source::Internal).await;
|
||||
let ar = r2.unwrap();
|
||||
let AuthResult { sessionid, state } = ar;
|
||||
|
||||
|
@ -1899,7 +1899,7 @@ mod tests {
|
|||
let pw_step = AuthEvent::cred_step_password(sessionid, pw);
|
||||
|
||||
// Expect success
|
||||
let r3 = idms_auth.auth(&pw_step, ct).await;
|
||||
let r3 = idms_auth.auth(&pw_step, ct, Source::Internal).await;
|
||||
debug!("r3 ==> {:?}", r3);
|
||||
idms_auth.commit().expect("Must not fail");
|
||||
|
||||
|
@ -1934,7 +1934,7 @@ mod tests {
|
|||
|
||||
let auth_init = AuthEvent::named_init("testperson");
|
||||
|
||||
let r1 = idms_auth.auth(&auth_init, ct).await;
|
||||
let r1 = idms_auth.auth(&auth_init, ct, Source::Internal).await;
|
||||
let ar = r1.unwrap();
|
||||
let AuthResult { sessionid, state } = ar;
|
||||
|
||||
|
@ -1945,7 +1945,7 @@ mod tests {
|
|||
|
||||
let auth_begin = AuthEvent::begin_mech(sessionid, AuthMech::Passkey);
|
||||
|
||||
let r2 = idms_auth.auth(&auth_begin, ct).await;
|
||||
let r2 = idms_auth.auth(&auth_begin, ct, Source::Internal).await;
|
||||
let ar = r2.unwrap();
|
||||
let AuthResult { sessionid, state } = ar;
|
||||
|
||||
|
@ -1967,7 +1967,7 @@ mod tests {
|
|||
|
||||
let passkey_step = AuthEvent::cred_step_passkey(sessionid, resp);
|
||||
|
||||
let r3 = idms_auth.auth(&passkey_step, ct).await;
|
||||
let r3 = idms_auth.auth(&passkey_step, ct, Source::Internal).await;
|
||||
debug!("r3 ==> {:?}", r3);
|
||||
idms_auth.commit().expect("Must not fail");
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
pub mod account;
|
||||
pub mod applinks;
|
||||
pub mod audit;
|
||||
pub mod authsession;
|
||||
pub mod credupdatesession;
|
||||
pub mod delayed;
|
||||
|
|
|
@ -23,6 +23,7 @@ impl<'a> IdmServerAuthTransaction<'a> {
|
|||
ident: Identity,
|
||||
issue: AuthIssueSession,
|
||||
ct: Duration,
|
||||
source: Source,
|
||||
) -> Result<AuthResult, OperationError> {
|
||||
// re-auth only works on users, so lets get the user account.
|
||||
// hint - it's in the ident!
|
||||
|
@ -138,6 +139,7 @@ impl<'a> IdmServerAuthTransaction<'a> {
|
|||
issue,
|
||||
self.webauthn,
|
||||
ct,
|
||||
source,
|
||||
);
|
||||
|
||||
// Push the re-auth session to the session maps.
|
||||
|
@ -168,6 +170,7 @@ impl<'a> IdmServerAuthTransaction<'a> {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::credential::totp::Totp;
|
||||
use crate::idm::audit::AuditEvent;
|
||||
use crate::idm::credupdatesession::{InitCredentialUpdateEvent, MfaRegStateStatus};
|
||||
use crate::idm::delayed::DelayedAction;
|
||||
use crate::idm::event::{AuthEvent, AuthResult};
|
||||
|
@ -331,7 +334,7 @@ mod tests {
|
|||
|
||||
let auth_init = AuthEvent::named_init("testperson");
|
||||
|
||||
let r1 = idms_auth.auth(&auth_init, ct).await;
|
||||
let r1 = idms_auth.auth(&auth_init, ct, Source::Internal).await;
|
||||
let ar = r1.unwrap();
|
||||
let AuthResult { sessionid, state } = ar;
|
||||
|
||||
|
@ -342,7 +345,7 @@ mod tests {
|
|||
|
||||
let auth_begin = AuthEvent::begin_mech(sessionid, AuthMech::Passkey);
|
||||
|
||||
let r2 = idms_auth.auth(&auth_begin, ct).await;
|
||||
let r2 = idms_auth.auth(&auth_begin, ct, Source::Internal).await;
|
||||
let ar = r2.unwrap();
|
||||
let AuthResult { sessionid, state } = ar;
|
||||
|
||||
|
@ -364,7 +367,7 @@ mod tests {
|
|||
|
||||
let passkey_step = AuthEvent::cred_step_passkey(sessionid, resp);
|
||||
|
||||
let r3 = idms_auth.auth(&passkey_step, ct).await;
|
||||
let r3 = idms_auth.auth(&passkey_step, ct, Source::Internal).await;
|
||||
debug!("r3 ==> {:?}", r3);
|
||||
idms_auth.commit().expect("Must not fail");
|
||||
|
||||
|
@ -404,7 +407,7 @@ mod tests {
|
|||
|
||||
let auth_init = AuthEvent::named_init("testperson");
|
||||
|
||||
let r1 = idms_auth.auth(&auth_init, ct).await;
|
||||
let r1 = idms_auth.auth(&auth_init, ct, Source::Internal).await;
|
||||
let ar = r1.unwrap();
|
||||
let AuthResult { sessionid, state } = ar;
|
||||
|
||||
|
@ -415,7 +418,7 @@ mod tests {
|
|||
|
||||
let auth_begin = AuthEvent::begin_mech(sessionid, AuthMech::PasswordMfa);
|
||||
|
||||
let r2 = idms_auth.auth(&auth_begin, ct).await;
|
||||
let r2 = idms_auth.auth(&auth_begin, ct, Source::Internal).await;
|
||||
let ar = r2.unwrap();
|
||||
let AuthResult { sessionid, state } = ar;
|
||||
|
||||
|
@ -426,7 +429,7 @@ mod tests {
|
|||
.expect("Failed to perform totp step");
|
||||
|
||||
let totp_step = AuthEvent::cred_step_totp(sessionid, totp);
|
||||
let r2 = idms_auth.auth(&totp_step, ct).await;
|
||||
let r2 = idms_auth.auth(&totp_step, ct, Source::Internal).await;
|
||||
let ar = r2.unwrap();
|
||||
let AuthResult { sessionid, state } = ar;
|
||||
|
||||
|
@ -435,7 +438,7 @@ mod tests {
|
|||
let pw_step = AuthEvent::cred_step_password(sessionid, pw);
|
||||
|
||||
// Expect success
|
||||
let r3 = idms_auth.auth(&pw_step, ct).await;
|
||||
let r3 = idms_auth.auth(&pw_step, ct, Source::Internal).await;
|
||||
debug!("r3 ==> {:?}", r3);
|
||||
idms_auth.commit().expect("Must not fail");
|
||||
|
||||
|
@ -477,7 +480,7 @@ mod tests {
|
|||
let origin = idms_auth.get_origin().clone();
|
||||
|
||||
let auth_allowed = idms_auth
|
||||
.reauth_init(ident.clone(), AuthIssueSession::Token, ct)
|
||||
.reauth_init(ident.clone(), AuthIssueSession::Token, ct, Source::Internal)
|
||||
.await
|
||||
.expect("Failed to start reauth.");
|
||||
|
||||
|
@ -501,7 +504,7 @@ mod tests {
|
|||
|
||||
let passkey_step = AuthEvent::cred_step_passkey(sessionid, resp);
|
||||
|
||||
let r3 = idms_auth.auth(&passkey_step, ct).await;
|
||||
let r3 = idms_auth.auth(&passkey_step, ct, Source::Internal).await;
|
||||
debug!("r3 ==> {:?}", r3);
|
||||
idms_auth.commit().expect("Must not fail");
|
||||
|
||||
|
@ -536,7 +539,7 @@ mod tests {
|
|||
let mut idms_auth = idms.auth().await;
|
||||
|
||||
let auth_allowed = idms_auth
|
||||
.reauth_init(ident.clone(), AuthIssueSession::Token, ct)
|
||||
.reauth_init(ident.clone(), AuthIssueSession::Token, ct, Source::Internal)
|
||||
.await
|
||||
.expect("Failed to start reauth.");
|
||||
|
||||
|
@ -561,7 +564,7 @@ mod tests {
|
|||
.expect("Failed to perform totp step");
|
||||
|
||||
let totp_step = AuthEvent::cred_step_totp(sessionid, totp);
|
||||
let r2 = idms_auth.auth(&totp_step, ct).await;
|
||||
let r2 = idms_auth.auth(&totp_step, ct, Source::Internal).await;
|
||||
let ar = r2.unwrap();
|
||||
let AuthResult { sessionid, state } = ar;
|
||||
|
||||
|
@ -570,7 +573,7 @@ mod tests {
|
|||
let pw_step = AuthEvent::cred_step_password(sessionid, pw);
|
||||
|
||||
// Expect success
|
||||
let r3 = idms_auth.auth(&pw_step, ct).await;
|
||||
let r3 = idms_auth.auth(&pw_step, ct, Source::Internal).await;
|
||||
debug!("r3 ==> {:?}", r3);
|
||||
idms_auth.commit().expect("Must not fail");
|
||||
|
||||
|
@ -628,8 +631,12 @@ mod tests {
|
|||
assert!(matches!(ident.access_scope(), AccessScope::ReadWrite));
|
||||
}
|
||||
|
||||
#[idm_test]
|
||||
async fn test_idm_reauth_softlocked_pw(idms: &IdmServer, idms_delayed: &mut IdmServerDelayed) {
|
||||
#[idm_test(audit)]
|
||||
async fn test_idm_reauth_softlocked_pw(
|
||||
idms: &IdmServer,
|
||||
idms_delayed: &mut IdmServerDelayed,
|
||||
idms_audit: &mut IdmServerAudit,
|
||||
) {
|
||||
// This test is to enforce that an account in a soft lock state can't proceed
|
||||
// we a re-auth.
|
||||
let ct = duration_from_epoch_now();
|
||||
|
@ -668,6 +675,12 @@ mod tests {
|
|||
.await
|
||||
.is_none());
|
||||
|
||||
// There should be a queued audit event
|
||||
match idms_audit.audit_rx().try_recv() {
|
||||
Ok(AuditEvent::AuthenticationDenied { .. }) => {}
|
||||
_ => assert!(false),
|
||||
}
|
||||
|
||||
// Start the re-auth - MUST FAIL!
|
||||
assert!(
|
||||
reauth_password_totp(idms, ct, &ident, &pw, &totp, idms_delayed)
|
||||
|
|
|
@ -29,6 +29,7 @@ use super::event::ReadBackupCodeEvent;
|
|||
use super::ldap::{LdapBoundToken, LdapSession};
|
||||
use crate::credential::{softlock::CredSoftLock, Credential};
|
||||
use crate::idm::account::Account;
|
||||
use crate::idm::audit::AuditEvent;
|
||||
use crate::idm::authsession::AuthSession;
|
||||
use crate::idm::credupdatesession::CredentialUpdateSessionMutex;
|
||||
use crate::idm::delayed::{
|
||||
|
@ -82,6 +83,7 @@ pub struct IdmServer {
|
|||
/// The configured crypto policy for the IDM server. Later this could be transactional and loaded from the db similar to access. But today it's just to allow dynamic pbkdf2rounds
|
||||
crypto_policy: CryptoPolicy,
|
||||
async_tx: Sender<DelayedAction>,
|
||||
audit_tx: Sender<AuditEvent>,
|
||||
/// [Webauthn] verifier/config
|
||||
webauthn: Webauthn,
|
||||
pw_badlist_cache: Arc<CowCell<HashSet<String>>>,
|
||||
|
@ -100,6 +102,7 @@ pub struct IdmServerAuthTransaction<'a> {
|
|||
pub(crate) sid: Sid,
|
||||
// For flagging eventual actions.
|
||||
pub(crate) async_tx: Sender<DelayedAction>,
|
||||
pub(crate) audit_tx: Sender<AuditEvent>,
|
||||
pub(crate) webauthn: &'a Webauthn,
|
||||
pub(crate) pw_badlist_cache: CowCellReadTxn<HashSet<String>>,
|
||||
pub(crate) domain_keys: CowCellReadTxn<DomainKeys>,
|
||||
|
@ -120,7 +123,6 @@ pub struct IdmServerProxyReadTransaction<'a> {
|
|||
pub qs_read: QueryServerReadTransaction<'a>,
|
||||
pub(crate) domain_keys: CowCellReadTxn<DomainKeys>,
|
||||
pub(crate) oauth2rs: Oauth2ResourceServersReadTransaction,
|
||||
// pub(crate) async_tx: Sender<DelayedAction>,
|
||||
}
|
||||
|
||||
pub struct IdmServerProxyWriteTransaction<'a> {
|
||||
|
@ -141,12 +143,15 @@ pub struct IdmServerDelayed {
|
|||
pub(crate) async_rx: Receiver<DelayedAction>,
|
||||
}
|
||||
|
||||
pub struct IdmServerAudit {
|
||||
pub(crate) audit_rx: Receiver<AuditEvent>,
|
||||
}
|
||||
|
||||
impl IdmServer {
|
||||
// TODO: Make number of authsessions configurable!!!
|
||||
pub async fn new(
|
||||
qs: QueryServer,
|
||||
origin: &str,
|
||||
) -> Result<(IdmServer, IdmServerDelayed), OperationError> {
|
||||
) -> Result<(IdmServer, IdmServerDelayed, IdmServerAudit), OperationError> {
|
||||
// This is calculated back from:
|
||||
// 500 auths / thread -> 0.002 sec per op
|
||||
// we can then spend up to ~0.001s hashing
|
||||
|
@ -156,6 +161,7 @@ impl IdmServer {
|
|||
// improves.
|
||||
let crypto_policy = CryptoPolicy::time_target(Duration::from_millis(1));
|
||||
let (async_tx, async_rx) = unbounded();
|
||||
let (audit_tx, audit_rx) = unbounded();
|
||||
|
||||
// Get the domain name, as the relying party id.
|
||||
let (
|
||||
|
@ -249,12 +255,14 @@ impl IdmServer {
|
|||
qs,
|
||||
crypto_policy,
|
||||
async_tx,
|
||||
audit_tx,
|
||||
webauthn,
|
||||
pw_badlist_cache: Arc::new(CowCell::new(pw_badlist_set)),
|
||||
domain_keys,
|
||||
oauth2rs: Arc::new(oauth2rs),
|
||||
},
|
||||
IdmServerDelayed { async_rx },
|
||||
IdmServerAudit { audit_rx },
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -277,6 +285,7 @@ impl IdmServer {
|
|||
qs_read,
|
||||
sid,
|
||||
async_tx: self.async_tx.clone(),
|
||||
audit_tx: self.audit_tx.clone(),
|
||||
webauthn: &self.webauthn,
|
||||
pw_badlist_cache: self.pw_badlist_cache.read(),
|
||||
domain_keys: self.domain_keys.read(),
|
||||
|
@ -339,30 +348,44 @@ impl IdmServer {
|
|||
}
|
||||
}
|
||||
|
||||
impl IdmServerDelayed {
|
||||
// I think we can just make this async in the future?
|
||||
impl IdmServerAudit {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn check_is_empty_or_panic(&mut self) {
|
||||
use core::task::{Context, Poll};
|
||||
use futures::task as futures_task;
|
||||
use tokio::sync::mpsc::error::TryRecvError;
|
||||
|
||||
let waker = futures_task::noop_waker();
|
||||
let mut cx = Context::from_waker(&waker);
|
||||
match self.async_rx.poll_recv(&mut cx) {
|
||||
Poll::Pending | Poll::Ready(None) => {}
|
||||
Poll::Ready(Some(m)) => {
|
||||
match self.audit_rx.try_recv() {
|
||||
Err(TryRecvError::Empty) => {}
|
||||
Err(TryRecvError::Disconnected) => {
|
||||
panic!("Task queue disconnected");
|
||||
}
|
||||
Ok(m) => {
|
||||
trace!(?m);
|
||||
panic!("Task queue not empty")
|
||||
panic!("Task queue not empty");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
#[cfg(test)]
|
||||
pub(crate) fn blocking_recv(&mut self) -> Option<DelayedAction> {
|
||||
self.async_rx.blocking_recv()
|
||||
pub fn audit_rx(&mut self) -> &mut Receiver<AuditEvent> {
|
||||
&mut self.audit_rx
|
||||
}
|
||||
}
|
||||
|
||||
impl IdmServerDelayed {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn check_is_empty_or_panic(&mut self) {
|
||||
use tokio::sync::mpsc::error::TryRecvError;
|
||||
|
||||
match self.async_rx.try_recv() {
|
||||
Err(TryRecvError::Empty) => {}
|
||||
Err(TryRecvError::Disconnected) => {
|
||||
panic!("Task queue disconnected");
|
||||
}
|
||||
Ok(m) => {
|
||||
trace!(?m);
|
||||
panic!("Task queue not empty");
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn try_recv(&mut self) -> Result<DelayedAction, OperationError> {
|
||||
|
@ -909,6 +932,7 @@ impl<'a> IdmServerAuthTransaction<'a> {
|
|||
&mut self,
|
||||
ae: &AuthEvent,
|
||||
ct: Duration,
|
||||
source: Source,
|
||||
) -> Result<AuthResult, OperationError> {
|
||||
// Match on the auth event, to see what we need to do.
|
||||
match &ae.step {
|
||||
|
@ -983,7 +1007,7 @@ impl<'a> IdmServerAuthTransaction<'a> {
|
|||
});
|
||||
|
||||
let (auth_session, state) =
|
||||
AuthSession::new(account, init.issue, self.webauthn, ct);
|
||||
AuthSession::new(account, init.issue, self.webauthn, ct, source);
|
||||
|
||||
match auth_session {
|
||||
Some(auth_session) => {
|
||||
|
@ -1106,6 +1130,7 @@ impl<'a> IdmServerAuthTransaction<'a> {
|
|||
&creds.cred,
|
||||
ct,
|
||||
&self.async_tx,
|
||||
&self.audit_tx,
|
||||
self.webauthn,
|
||||
pw_badlist_cache,
|
||||
&self.domain_keys.uat_jwt_signer,
|
||||
|
@ -2050,6 +2075,7 @@ mod tests {
|
|||
|
||||
use crate::credential::{Credential, Password};
|
||||
use crate::idm::account::DestroySessionTokenEvent;
|
||||
use crate::idm::audit::AuditEvent;
|
||||
use crate::idm::delayed::{AuthSessionRecord, DelayedAction};
|
||||
use crate::idm::event::{AuthEvent, AuthResult};
|
||||
use crate::idm::event::{
|
||||
|
@ -2075,7 +2101,11 @@ mod tests {
|
|||
let anon_init = AuthEvent::anonymous_init();
|
||||
// Expect success
|
||||
let r1 = idms_auth
|
||||
.auth(&anon_init, Duration::from_secs(TEST_CURRENT_TIME))
|
||||
.auth(
|
||||
&anon_init,
|
||||
Duration::from_secs(TEST_CURRENT_TIME),
|
||||
Source::Internal,
|
||||
)
|
||||
.await;
|
||||
/* Some weird lifetime things happen here ... */
|
||||
|
||||
|
@ -2113,7 +2143,11 @@ mod tests {
|
|||
let anon_begin = AuthEvent::begin_mech(sid, AuthMech::Anonymous);
|
||||
|
||||
let r2 = idms_auth
|
||||
.auth(&anon_begin, Duration::from_secs(TEST_CURRENT_TIME))
|
||||
.auth(
|
||||
&anon_begin,
|
||||
Duration::from_secs(TEST_CURRENT_TIME),
|
||||
Source::Internal,
|
||||
)
|
||||
.await;
|
||||
debug!("r2 ==> {:?}", r2);
|
||||
|
||||
|
@ -2151,7 +2185,11 @@ mod tests {
|
|||
|
||||
// Expect success
|
||||
let r2 = idms_auth
|
||||
.auth(&anon_step, Duration::from_secs(TEST_CURRENT_TIME))
|
||||
.auth(
|
||||
&anon_step,
|
||||
Duration::from_secs(TEST_CURRENT_TIME),
|
||||
Source::Internal,
|
||||
)
|
||||
.await;
|
||||
debug!("r2 ==> {:?}", r2);
|
||||
|
||||
|
@ -2195,7 +2233,11 @@ mod tests {
|
|||
|
||||
// Expect failure
|
||||
let r2 = idms_auth
|
||||
.auth(&anon_step, Duration::from_secs(TEST_CURRENT_TIME))
|
||||
.auth(
|
||||
&anon_step,
|
||||
Duration::from_secs(TEST_CURRENT_TIME),
|
||||
Source::Internal,
|
||||
)
|
||||
.await;
|
||||
debug!("r2 ==> {:?}", r2);
|
||||
|
||||
|
@ -2239,7 +2281,7 @@ mod tests {
|
|||
let mut idms_auth = idms.auth().await;
|
||||
let admin_init = AuthEvent::named_init(name);
|
||||
|
||||
let r1 = idms_auth.auth(&admin_init, ct).await;
|
||||
let r1 = idms_auth.auth(&admin_init, ct, Source::Internal).await;
|
||||
let ar = r1.unwrap();
|
||||
let AuthResult { sessionid, state } = ar;
|
||||
|
||||
|
@ -2248,7 +2290,7 @@ mod tests {
|
|||
// Now push that we want the Password Mech.
|
||||
let admin_begin = AuthEvent::begin_mech(sessionid, AuthMech::Password);
|
||||
|
||||
let r2 = idms_auth.auth(&admin_begin, ct).await;
|
||||
let r2 = idms_auth.auth(&admin_begin, ct, Source::Internal).await;
|
||||
let ar = r2.unwrap();
|
||||
let AuthResult { sessionid, state } = ar;
|
||||
|
||||
|
@ -2274,7 +2316,11 @@ mod tests {
|
|||
|
||||
// Expect success
|
||||
let r2 = idms_auth
|
||||
.auth(&anon_step, Duration::from_secs(TEST_CURRENT_TIME))
|
||||
.auth(
|
||||
&anon_step,
|
||||
Duration::from_secs(TEST_CURRENT_TIME),
|
||||
Source::Internal,
|
||||
)
|
||||
.await;
|
||||
debug!("r2 ==> {:?}", r2);
|
||||
|
||||
|
@ -2342,7 +2388,11 @@ mod tests {
|
|||
|
||||
// Expect success
|
||||
let r2 = idms_auth
|
||||
.auth(&anon_step, Duration::from_secs(TEST_CURRENT_TIME))
|
||||
.auth(
|
||||
&anon_step,
|
||||
Duration::from_secs(TEST_CURRENT_TIME),
|
||||
Source::Internal,
|
||||
)
|
||||
.await;
|
||||
debug!("r2 ==> {:?}", r2);
|
||||
|
||||
|
@ -2377,8 +2427,12 @@ mod tests {
|
|||
idms_auth.commit().expect("Must not fail");
|
||||
}
|
||||
|
||||
#[idm_test]
|
||||
async fn test_idm_simple_password_invalid(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
|
||||
#[idm_test(audit)]
|
||||
async fn test_idm_simple_password_invalid(
|
||||
idms: &IdmServer,
|
||||
_idms_delayed: &IdmServerDelayed,
|
||||
idms_audit: &mut IdmServerAudit,
|
||||
) {
|
||||
init_admin_w_password(idms, TEST_PASSWORD)
|
||||
.await
|
||||
.expect("Failed to setup admin account");
|
||||
|
@ -2389,7 +2443,11 @@ mod tests {
|
|||
|
||||
// Expect success
|
||||
let r2 = idms_auth
|
||||
.auth(&anon_step, Duration::from_secs(TEST_CURRENT_TIME))
|
||||
.auth(
|
||||
&anon_step,
|
||||
Duration::from_secs(TEST_CURRENT_TIME),
|
||||
Source::Internal,
|
||||
)
|
||||
.await;
|
||||
debug!("r2 ==> {:?}", r2);
|
||||
|
||||
|
@ -2416,6 +2474,12 @@ mod tests {
|
|||
}
|
||||
};
|
||||
|
||||
// There should be a queued audit event
|
||||
match idms_audit.audit_rx().try_recv() {
|
||||
Ok(AuditEvent::AuthenticationDenied { .. }) => {}
|
||||
_ => assert!(false),
|
||||
}
|
||||
|
||||
idms_auth.commit().expect("Must not fail");
|
||||
}
|
||||
|
||||
|
@ -2838,7 +2902,9 @@ mod tests {
|
|||
|
||||
let mut idms_auth = idms.auth().await;
|
||||
let admin_init = AuthEvent::named_init("admin");
|
||||
let r1 = idms_auth.auth(&admin_init, time_low).await;
|
||||
let r1 = idms_auth
|
||||
.auth(&admin_init, time_low, Source::Internal)
|
||||
.await;
|
||||
|
||||
let ar = r1.unwrap();
|
||||
let AuthResult {
|
||||
|
@ -2858,7 +2924,9 @@ mod tests {
|
|||
// And here!
|
||||
let mut idms_auth = idms.auth().await;
|
||||
let admin_init = AuthEvent::named_init("admin");
|
||||
let r1 = idms_auth.auth(&admin_init, time_high).await;
|
||||
let r1 = idms_auth
|
||||
.auth(&admin_init, time_high, Source::Internal)
|
||||
.await;
|
||||
|
||||
let ar = r1.unwrap();
|
||||
let AuthResult {
|
||||
|
@ -2987,8 +3055,12 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[idm_test]
|
||||
async fn test_idm_account_softlocking(idms: &IdmServer, idms_delayed: &mut IdmServerDelayed) {
|
||||
#[idm_test(audit)]
|
||||
async fn test_idm_account_softlocking(
|
||||
idms: &IdmServer,
|
||||
idms_delayed: &mut IdmServerDelayed,
|
||||
idms_audit: &mut IdmServerAudit,
|
||||
) {
|
||||
init_admin_w_password(idms, TEST_PASSWORD)
|
||||
.await
|
||||
.expect("Failed to setup admin account");
|
||||
|
@ -3000,7 +3072,11 @@ mod tests {
|
|||
let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD_INC);
|
||||
|
||||
let r2 = idms_auth
|
||||
.auth(&anon_step, Duration::from_secs(TEST_CURRENT_TIME))
|
||||
.auth(
|
||||
&anon_step,
|
||||
Duration::from_secs(TEST_CURRENT_TIME),
|
||||
Source::Internal,
|
||||
)
|
||||
.await;
|
||||
debug!("r2 ==> {:?}", r2);
|
||||
|
||||
|
@ -3025,6 +3101,13 @@ mod tests {
|
|||
panic!();
|
||||
}
|
||||
};
|
||||
|
||||
// There should be a queued audit event
|
||||
match idms_audit.audit_rx().try_recv() {
|
||||
Ok(AuditEvent::AuthenticationDenied { .. }) => {}
|
||||
_ => assert!(false),
|
||||
}
|
||||
|
||||
idms_auth.commit().expect("Must not fail");
|
||||
|
||||
// Auth init, softlock present, count == 1, same time (so before unlock_at)
|
||||
|
@ -3034,7 +3117,11 @@ mod tests {
|
|||
let admin_init = AuthEvent::named_init("admin");
|
||||
|
||||
let r1 = idms_auth
|
||||
.auth(&admin_init, Duration::from_secs(TEST_CURRENT_TIME))
|
||||
.auth(
|
||||
&admin_init,
|
||||
Duration::from_secs(TEST_CURRENT_TIME),
|
||||
Source::Internal,
|
||||
)
|
||||
.await;
|
||||
let ar = r1.unwrap();
|
||||
let AuthResult { sessionid, state } = ar;
|
||||
|
@ -3044,7 +3131,11 @@ mod tests {
|
|||
let admin_begin = AuthEvent::begin_mech(sessionid, AuthMech::Password);
|
||||
|
||||
let r2 = idms_auth
|
||||
.auth(&admin_begin, Duration::from_secs(TEST_CURRENT_TIME))
|
||||
.auth(
|
||||
&admin_begin,
|
||||
Duration::from_secs(TEST_CURRENT_TIME),
|
||||
Source::Internal,
|
||||
)
|
||||
.await;
|
||||
let ar = r2.unwrap();
|
||||
let AuthResult {
|
||||
|
@ -3077,7 +3168,11 @@ mod tests {
|
|||
|
||||
// Expect success
|
||||
let r2 = idms_auth
|
||||
.auth(&anon_step, Duration::from_secs(TEST_CURRENT_TIME + 2))
|
||||
.auth(
|
||||
&anon_step,
|
||||
Duration::from_secs(TEST_CURRENT_TIME + 2),
|
||||
Source::Internal,
|
||||
)
|
||||
.await;
|
||||
debug!("r2 ==> {:?}", r2);
|
||||
|
||||
|
@ -3119,10 +3214,11 @@ mod tests {
|
|||
// Tested in the softlock state machine.
|
||||
}
|
||||
|
||||
#[idm_test]
|
||||
#[idm_test(audit)]
|
||||
async fn test_idm_account_softlocking_interleaved(
|
||||
idms: &IdmServer,
|
||||
_idms_delayed: &mut IdmServerDelayed,
|
||||
idms_audit: &mut IdmServerAudit,
|
||||
) {
|
||||
init_admin_w_password(idms, TEST_PASSWORD)
|
||||
.await
|
||||
|
@ -3140,7 +3236,11 @@ mod tests {
|
|||
let anon_step = AuthEvent::cred_step_password(sid_later, TEST_PASSWORD_INC);
|
||||
|
||||
let r2 = idms_auth
|
||||
.auth(&anon_step, Duration::from_secs(TEST_CURRENT_TIME))
|
||||
.auth(
|
||||
&anon_step,
|
||||
Duration::from_secs(TEST_CURRENT_TIME),
|
||||
Source::Internal,
|
||||
)
|
||||
.await;
|
||||
debug!("r2 ==> {:?}", r2);
|
||||
|
||||
|
@ -3165,6 +3265,12 @@ mod tests {
|
|||
panic!();
|
||||
}
|
||||
};
|
||||
|
||||
match idms_audit.audit_rx().try_recv() {
|
||||
Ok(AuditEvent::AuthenticationDenied { .. }) => {}
|
||||
_ => assert!(false),
|
||||
}
|
||||
|
||||
idms_auth.commit().expect("Must not fail");
|
||||
|
||||
// Now check that sid_early is denied due to softlock.
|
||||
|
@ -3173,7 +3279,11 @@ mod tests {
|
|||
|
||||
// Expect success
|
||||
let r2 = idms_auth
|
||||
.auth(&anon_step, Duration::from_secs(TEST_CURRENT_TIME))
|
||||
.auth(
|
||||
&anon_step,
|
||||
Duration::from_secs(TEST_CURRENT_TIME),
|
||||
Source::Internal,
|
||||
)
|
||||
.await;
|
||||
debug!("r2 ==> {:?}", r2);
|
||||
match r2 {
|
||||
|
|
|
@ -78,13 +78,15 @@ pub mod prelude {
|
|||
f_and, f_andnot, f_eq, f_id, f_inc, f_lt, f_or, f_pres, f_self, f_spn_name, f_sub, Filter,
|
||||
FilterInvalid, FilterValid, FC,
|
||||
};
|
||||
pub use crate::idm::server::{IdmServer, IdmServerDelayed};
|
||||
pub use crate::idm::server::{IdmServer, IdmServerAudit, IdmServerDelayed};
|
||||
pub use crate::modify::{
|
||||
m_assert, m_pres, m_purge, m_remove, Modify, ModifyInvalid, ModifyList, ModifyValid,
|
||||
};
|
||||
pub use crate::server::access::AccessControlsTransaction;
|
||||
pub use crate::server::batch_modify::BatchModifyEvent;
|
||||
pub use crate::server::identity::{AccessScope, IdentType, IdentUser, Identity, IdentityId};
|
||||
pub use crate::server::identity::{
|
||||
AccessScope, IdentType, IdentUser, Identity, IdentityId, Source,
|
||||
};
|
||||
pub use crate::server::{
|
||||
QueryServer, QueryServerReadTransaction, QueryServerTransaction,
|
||||
QueryServerWriteTransaction,
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
use crate::be::Limits;
|
||||
use std::collections::BTreeSet;
|
||||
use std::hash::Hash;
|
||||
use std::net::IpAddr;
|
||||
use std::sync::Arc;
|
||||
use uuid::uuid;
|
||||
|
||||
|
@ -16,6 +17,13 @@ use serde::{Deserialize, Serialize};
|
|||
use crate::prelude::*;
|
||||
use crate::value::Session;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Source {
|
||||
Internal,
|
||||
Https(IpAddr),
|
||||
// Ldaps,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum AccessScope {
|
||||
ReadOnly,
|
||||
|
|
|
@ -1037,8 +1037,6 @@ impl QueryServer {
|
|||
|
||||
let phase = Arc::new(CowCell::new(ServerPhase::Bootstrap));
|
||||
|
||||
// log_event!(log, "Starting query worker ...");
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
QueryServer {
|
||||
phase,
|
||||
|
|
|
@ -57,7 +57,7 @@ pub async fn setup_pair_test() -> (QueryServer, QueryServer) {
|
|||
}
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
pub async fn setup_idm_test() -> (IdmServer, IdmServerDelayed) {
|
||||
pub async fn setup_idm_test() -> (IdmServer, IdmServerDelayed, IdmServerAudit) {
|
||||
let qs = setup_test().await;
|
||||
|
||||
qs.initialise_helper(duration_from_epoch_now())
|
||||
|
|
Loading…
Reference in a new issue