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