1use super::ldap::{LdapBoundToken, LdapSession};
2use crate::credential::{softlock::CredSoftLock, Credential};
3use crate::idm::account::Account;
4use crate::idm::application::{
5 LdapApplications, LdapApplicationsReadTransaction, LdapApplicationsWriteTransaction,
6};
7use crate::idm::audit::AuditEvent;
8use crate::idm::authentication::{AuthState, PreValidatedTokenStatus};
9use crate::idm::authsession::{AuthSession, AuthSessionData};
10use crate::idm::credupdatesession::CredentialUpdateSessionMutex;
11use crate::idm::delayed::{
12 AuthSessionRecord, BackupCodeRemoval, DelayedAction, PasswordUpgrade, UnixPasswordUpgrade,
13 WebauthnCounterIncrement,
14};
15use crate::idm::event::{
16 AuthEvent, AuthEventStep, AuthResult, CredentialStatusEvent, LdapAuthEvent, LdapTokenAuthEvent,
17 RadiusAuthTokenEvent, RegenerateRadiusSecretEvent, UnixGroupTokenEvent,
18 UnixPasswordChangeEvent, UnixUserAuthEvent, UnixUserTokenEvent,
19};
20use crate::idm::group::{Group, Unix};
21use crate::idm::oauth2::{
22 Oauth2ResourceServers, Oauth2ResourceServersReadTransaction,
23 Oauth2ResourceServersWriteTransaction,
24};
25use crate::idm::oauth2_client::OAuth2ClientProvider;
26use crate::idm::radius::RadiusAccount;
27use crate::idm::scim::SyncAccount;
28use crate::idm::serviceaccount::ServiceAccount;
29use crate::prelude::*;
30use crate::server::keys::KeyProvidersTransaction;
31use crate::server::DomainInfo;
32use crate::utils::{password_from_random, readable_password_from_random, uuid_from_duration, Sid};
33use crate::value::{Session, SessionState};
34use compact_jwt::{Jwk, JwsCompact};
35use concread::bptree::{BptreeMap, BptreeMapReadTxn, BptreeMapWriteTxn};
36use concread::cowcell::CowCellReadTxn;
37use concread::hashmap::{HashMap, HashMapReadTxn, HashMapWriteTxn};
38use kanidm_lib_crypto::CryptoPolicy;
39use kanidm_proto::internal::{
40 ApiToken, CredentialStatus, PasswordFeedback, RadiusAuthToken, ScimSyncToken, UatPurpose,
41 UserAuthToken,
42};
43use kanidm_proto::v1::{UnixGroupToken, UnixUserToken};
44use rand::prelude::*;
45use std::convert::TryFrom;
46use std::sync::Arc;
47use std::time::Duration;
48use time::OffsetDateTime;
49use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
50use tokio::sync::{Mutex, Semaphore};
51use tracing::trace;
52use url::Url;
53use webauthn_rs::prelude::{Webauthn, WebauthnBuilder};
54use zxcvbn::{zxcvbn, Score};
55
56#[cfg(test)]
57use crate::idm::event::PasswordChangeEvent;
58
59pub(crate) type AuthSessionMutex = Arc<Mutex<AuthSession>>;
60pub(crate) type CredSoftLockMutex = Arc<Mutex<CredSoftLock>>;
61
62pub type DomainInfoRead = CowCellReadTxn<DomainInfo>;
63
64pub struct IdmServer {
65 session_ticket: Semaphore,
70 sessions: BptreeMap<Uuid, AuthSessionMutex>,
71 softlocks: HashMap<Uuid, CredSoftLockMutex>,
72 cred_update_sessions: BptreeMap<Uuid, CredentialUpdateSessionMutex>,
74 qs: QueryServer,
76 crypto_policy: CryptoPolicy,
78 async_tx: UnboundedSender<DelayedAction>,
79 audit_tx: UnboundedSender<AuditEvent>,
80 webauthn: Webauthn,
82 oauth2rs: Arc<Oauth2ResourceServers>,
83 applications: Arc<LdapApplications>,
84
85 origin: Url,
87 oauth2_client_providers: HashMap<Uuid, OAuth2ClientProvider>,
88}
89
90pub struct IdmServerAuthTransaction<'a> {
92 pub(crate) session_ticket: &'a Semaphore,
93 pub(crate) sessions: &'a BptreeMap<Uuid, AuthSessionMutex>,
94 pub(crate) softlocks: &'a HashMap<Uuid, CredSoftLockMutex>,
95 pub(crate) oauth2_client_providers: HashMapReadTxn<'a, Uuid, OAuth2ClientProvider>,
96
97 pub qs_read: QueryServerReadTransaction<'a>,
98 pub(crate) sid: Sid,
100 pub(crate) async_tx: UnboundedSender<DelayedAction>,
102 pub(crate) audit_tx: UnboundedSender<AuditEvent>,
103 pub(crate) webauthn: &'a Webauthn,
104 pub(crate) applications: LdapApplicationsReadTransaction,
105}
106
107pub struct IdmServerCredUpdateTransaction<'a> {
108 pub(crate) qs_read: QueryServerReadTransaction<'a>,
109 pub(crate) webauthn: &'a Webauthn,
111 pub(crate) cred_update_sessions: BptreeMapReadTxn<'a, Uuid, CredentialUpdateSessionMutex>,
112 pub(crate) crypto_policy: &'a CryptoPolicy,
113}
114
115pub struct IdmServerProxyReadTransaction<'a> {
117 pub qs_read: QueryServerReadTransaction<'a>,
118 pub(crate) oauth2rs: Oauth2ResourceServersReadTransaction,
119}
120
121pub struct IdmServerProxyWriteTransaction<'a> {
122 pub qs_write: QueryServerWriteTransaction<'a>,
125 pub(crate) cred_update_sessions: BptreeMapWriteTxn<'a, Uuid, CredentialUpdateSessionMutex>,
127 pub(crate) sid: Sid,
128 crypto_policy: &'a CryptoPolicy,
129 webauthn: &'a Webauthn,
130 pub(crate) oauth2rs: Oauth2ResourceServersWriteTransaction<'a>,
131 pub(crate) applications: LdapApplicationsWriteTransaction<'a>,
132
133 pub(crate) origin: &'a Url,
134 pub(crate) oauth2_client_providers: HashMapWriteTxn<'a, Uuid, OAuth2ClientProvider>,
135}
136
137pub struct IdmServerDelayed {
138 pub(crate) async_rx: UnboundedReceiver<DelayedAction>,
139}
140
141pub struct IdmServerAudit {
142 pub(crate) audit_rx: UnboundedReceiver<AuditEvent>,
143}
144
145impl IdmServer {
146 pub async fn new(
147 qs: QueryServer,
148 origin: &Url,
149 is_integration_test: bool,
150 current_time: Duration,
151 ) -> Result<(IdmServer, IdmServerDelayed, IdmServerAudit), OperationError> {
152 let crypto_policy = if cfg!(test) || is_integration_test {
153 CryptoPolicy::danger_test_minimum()
154 } else {
155 CryptoPolicy::time_target(Duration::from_millis(10))
158 };
159
160 let (async_tx, async_rx) = unbounded_channel();
161 let (audit_tx, audit_rx) = unbounded_channel();
162
163 let (rp_id, rp_name, application_set) = {
165 let mut qs_read = qs.read().await?;
166 (
167 qs_read.get_domain_name().to_string(),
168 qs_read.get_domain_display_name().to_string(),
169 qs_read.get_applications_set()?,
171 )
172 };
173
174 let valid = origin
176 .domain()
177 .map(|effective_domain| {
178 effective_domain.ends_with(&format!(".{rp_id}")) || effective_domain == rp_id
181 })
182 .unwrap_or(false);
183
184 if !valid {
185 admin_error!(
186 "Effective domain (ed) is not a descendent of server domain name (rp_id)."
187 );
188 admin_error!(
189 "You must change origin or domain name to be consistent. ed: {:?} - rp_id: {:?}",
190 origin,
191 rp_id
192 );
193 admin_error!("To change the origin or domain name see: https://kanidm.github.io/kanidm/master/server_configuration.html");
194 return Err(OperationError::InvalidState);
195 };
196
197 let webauthn = WebauthnBuilder::new(&rp_id, origin)
198 .and_then(|builder| builder.allow_subdomains(true).rp_name(&rp_name).build())
199 .map_err(|e| {
200 admin_error!("Invalid Webauthn Configuration - {:?}", e);
201 OperationError::InvalidState
202 })?;
203
204 let oauth2rs = Oauth2ResourceServers::new(origin.to_owned()).map_err(|err| {
205 error!(?err, "Failed to load oauth2 resource servers");
206 err
207 })?;
208
209 let applications = LdapApplications::try_from(application_set).map_err(|e| {
210 admin_error!("Failed to load ldap applications - {:?}", e);
211 e
212 })?;
213
214 let idm_server = IdmServer {
215 session_ticket: Semaphore::new(1),
216 sessions: BptreeMap::new(),
217 softlocks: HashMap::new(),
218 cred_update_sessions: BptreeMap::new(),
219 qs,
220 crypto_policy,
221 async_tx,
222 audit_tx,
223 webauthn,
224 oauth2rs: Arc::new(oauth2rs),
225 applications: Arc::new(applications),
226 origin: origin.clone(),
227 oauth2_client_providers: HashMap::new(),
228 };
229 let idm_server_delayed = IdmServerDelayed { async_rx };
230 let idm_server_audit = IdmServerAudit { audit_rx };
231
232 let mut idm_write_txn = idm_server.proxy_write(current_time).await?;
233
234 idm_write_txn.reload_applications()?;
235 idm_write_txn.reload_oauth2()?;
236 idm_write_txn.reload_oauth2_client_providers()?;
237
238 idm_write_txn.commit()?;
239
240 Ok((idm_server, idm_server_delayed, idm_server_audit))
241 }
242
243 pub async fn auth(&self) -> Result<IdmServerAuthTransaction<'_>, OperationError> {
245 let qs_read = self.qs.read().await?;
246
247 let mut sid = [0; 4];
248 let mut rng = rand::rng();
249 rng.fill(&mut sid);
250
251 Ok(IdmServerAuthTransaction {
252 session_ticket: &self.session_ticket,
253 sessions: &self.sessions,
254 softlocks: &self.softlocks,
255 qs_read,
256 sid,
257 async_tx: self.async_tx.clone(),
258 audit_tx: self.audit_tx.clone(),
259 webauthn: &self.webauthn,
260 applications: self.applications.read(),
261 oauth2_client_providers: self.oauth2_client_providers.read(),
262 })
263 }
264
265 #[instrument(level = "debug", skip_all)]
269 pub fn domain_read(&self) -> DomainInfoRead {
270 self.qs.d_info.read()
271 }
272
273 #[instrument(level = "debug", skip_all)]
275 pub async fn proxy_read(&self) -> Result<IdmServerProxyReadTransaction<'_>, OperationError> {
276 let qs_read = self.qs.read().await?;
277 Ok(IdmServerProxyReadTransaction {
278 qs_read,
279 oauth2rs: self.oauth2rs.read(),
280 })
282 }
283
284 #[instrument(level = "debug", skip_all)]
285 pub async fn proxy_write(
286 &self,
287 ts: Duration,
288 ) -> Result<IdmServerProxyWriteTransaction<'_>, OperationError> {
289 let qs_write = self.qs.write(ts).await?;
290
291 let mut sid = [0; 4];
292 let mut rng = rand::rng();
293 rng.fill(&mut sid);
294
295 Ok(IdmServerProxyWriteTransaction {
296 cred_update_sessions: self.cred_update_sessions.write(),
297 qs_write,
298 sid,
299 crypto_policy: &self.crypto_policy,
300 webauthn: &self.webauthn,
301 oauth2rs: self.oauth2rs.write(),
302 applications: self.applications.write(),
303 origin: &self.origin,
304 oauth2_client_providers: self.oauth2_client_providers.write(),
305 })
306 }
307
308 pub async fn cred_update_transaction(
309 &self,
310 ) -> Result<IdmServerCredUpdateTransaction<'_>, OperationError> {
311 let qs_read = self.qs.read().await?;
312 Ok(IdmServerCredUpdateTransaction {
313 qs_read,
314 webauthn: &self.webauthn,
316 cred_update_sessions: self.cred_update_sessions.read(),
317 crypto_policy: &self.crypto_policy,
318 })
319 }
320
321 #[cfg(test)]
322 pub(crate) async fn delayed_action(
323 &self,
324 ct: Duration,
325 da: DelayedAction,
326 ) -> Result<bool, OperationError> {
327 let mut pw = self.proxy_write(ct).await?;
328 pw.process_delayedaction(&da, ct)
329 .and_then(|_| pw.commit())
330 .map(|()| true)
331 }
332}
333
334impl IdmServerAudit {
335 #[cfg(test)]
336 pub(crate) fn check_is_empty_or_panic(&mut self) {
337 use tokio::sync::mpsc::error::TryRecvError;
338
339 match self.audit_rx.try_recv() {
340 Err(TryRecvError::Empty) => {}
341 Err(TryRecvError::Disconnected) => {
342 panic!("Task queue disconnected");
343 }
344 Ok(m) => {
345 trace!(?m);
346 panic!("Task queue not empty");
347 }
348 }
349 }
350
351 pub fn audit_rx(&mut self) -> &mut UnboundedReceiver<AuditEvent> {
352 &mut self.audit_rx
353 }
354}
355
356impl IdmServerDelayed {
357 #[cfg(test)]
358 pub(crate) fn check_is_empty_or_panic(&mut self) {
359 use tokio::sync::mpsc::error::TryRecvError;
360
361 match self.async_rx.try_recv() {
362 Err(TryRecvError::Empty) => {}
363 Err(TryRecvError::Disconnected) => {
364 panic!("Task queue disconnected");
365 }
366 #[allow(clippy::panic)]
367 Ok(m) => {
368 trace!(?m);
369 panic!("Task queue not empty");
370 }
371 }
372 }
373
374 #[cfg(test)]
375 pub(crate) fn try_recv(&mut self) -> Result<DelayedAction, OperationError> {
376 use core::task::{Context, Poll};
377
378 let waker = futures::task::noop_waker();
379 let mut cx = Context::from_waker(&waker);
380 match self.async_rx.poll_recv(&mut cx) {
381 Poll::Pending => Err(OperationError::InvalidState),
382 Poll::Ready(None) => Err(OperationError::QueueDisconnected),
383 Poll::Ready(Some(m)) => Ok(m),
384 }
385 }
386
387 pub async fn recv_many(&mut self, buffer: &mut Vec<DelayedAction>) -> usize {
388 debug_assert!(buffer.is_empty());
389 let limit = buffer.capacity();
390 self.async_rx.recv_many(buffer, limit).await
391 }
392}
393
394pub enum Token {
395 UserAuthToken(UserAuthToken),
396 ApiToken(ApiToken, Arc<EntrySealedCommitted>),
397}
398
399pub trait IdmServerTransaction<'a> {
400 type QsTransactionType: QueryServerTransaction<'a>;
401
402 fn get_qs_txn(&mut self) -> &mut Self::QsTransactionType;
403
404 #[instrument(level = "debug", skip_all)]
413 fn validate_client_auth_info_to_ident(
414 &mut self,
415 client_auth_info: ClientAuthInfo,
416 ct: Duration,
417 ) -> Result<Identity, OperationError> {
418 let ClientAuthInfo {
419 source,
420 client_cert,
421 bearer_token,
422 basic_authz: _,
423 pre_validated_token,
424 } = client_auth_info;
425
426 match pre_validated_token {
430 PreValidatedTokenStatus::Valid(uat) => {
431 return self.process_uat_to_identity(&uat, ct, source)
432 }
433 PreValidatedTokenStatus::SessionExpired => return Err(OperationError::SessionExpired),
434 PreValidatedTokenStatus::NotAuthenticated | PreValidatedTokenStatus::None => {
435 }
441 }
442
443 match (client_cert, bearer_token) {
444 (Some(client_cert_info), _) => {
445 self.client_certificate_to_identity(&client_cert_info, ct, source)
446 }
447 (None, Some(token)) => {
448 match self.validate_and_parse_token_to_identity_token(&token, ct)? {
449 Token::UserAuthToken(uat) => self.process_uat_to_identity(&uat, ct, source),
450 Token::ApiToken(apit, entry) => {
451 self.process_apit_to_identity(&apit, source, entry, ct)
452 }
453 }
454 }
455 (None, None) => {
456 debug!("No client certificate or bearer tokens were supplied");
457 Err(OperationError::NotAuthenticated)
458 }
459 }
460 }
461
462 #[instrument(level = "debug", skip_all)]
468 fn pre_validate_client_auth_info(
469 &mut self,
470 client_auth_info: &mut ClientAuthInfo,
471 ct: Duration,
472 ) -> Result<(), OperationError> {
473 let (result, status) = match self.validate_client_auth_info_to_uat(client_auth_info, ct) {
474 Ok(uat) => (Ok(()), PreValidatedTokenStatus::Valid(Box::new(uat))),
475 Err(OperationError::NotAuthenticated) => {
476 (Ok(()), PreValidatedTokenStatus::NotAuthenticated)
477 }
478 Err(OperationError::SessionExpired) => {
479 (Ok(()), PreValidatedTokenStatus::SessionExpired)
480 }
481 Err(err) => (Err(err), PreValidatedTokenStatus::None),
482 };
483
484 client_auth_info.set_pre_validated_uat(status);
485
486 result
488 }
489
490 #[instrument(level = "debug", skip_all)]
494 fn validate_client_auth_info_to_uat(
495 &mut self,
496 client_auth_info: &ClientAuthInfo,
497 ct: Duration,
498 ) -> Result<UserAuthToken, OperationError> {
499 match (
502 client_auth_info.client_cert.as_ref(),
503 client_auth_info.bearer_token.as_ref(),
504 ) {
505 (Some(client_cert_info), _) => {
506 self.client_certificate_to_user_auth_token(client_cert_info, ct)
507 }
508 (None, Some(token)) => {
509 match self.validate_and_parse_token_to_identity_token(token, ct)? {
510 Token::UserAuthToken(uat) => Ok(uat),
511 Token::ApiToken(_apit, _entry) => {
512 debug!("Unable to process non user auth token");
513 Err(OperationError::NotAuthenticated)
514 }
515 }
516 }
517 (None, None) => {
518 debug!("No client certificate or bearer tokens were supplied");
519 Err(OperationError::NotAuthenticated)
520 }
521 }
522 }
523
524 fn validate_and_parse_token_to_identity_token(
525 &mut self,
526 jwsu: &JwsCompact,
527 ct: Duration,
528 ) -> Result<Token, OperationError> {
529 let jws_inner = self
532 .get_qs_txn()
533 .get_domain_key_object_handle()?
534 .jws_verify(jwsu)
535 .map_err(|err| {
536 security_info!(?err, "Unable to verify token");
537 OperationError::NotAuthenticated
538 })?;
539
540 if let Ok(uat) = jws_inner.from_json::<UserAuthToken>() {
542 if let Some(exp) = uat.expiry {
543 let ct_odt = time::OffsetDateTime::UNIX_EPOCH + ct;
544 if exp < ct_odt {
545 security_info!(?ct_odt, ?exp, "Session expired");
546 return Err(OperationError::SessionExpired);
547 } else {
548 trace!(?ct_odt, ?exp, "Session not yet expired");
549 return Ok(Token::UserAuthToken(uat));
550 }
551 } else {
552 debug!("Session has no expiry");
553 return Ok(Token::UserAuthToken(uat));
554 }
555 };
556
557 if let Ok(apit) = jws_inner.from_json::<ApiToken>() {
560 if let Some(expiry) = apit.expiry {
561 if time::OffsetDateTime::UNIX_EPOCH + ct >= expiry {
562 security_info!("Session expired");
563 return Err(OperationError::SessionExpired);
564 }
565 }
566
567 let entry = self
568 .get_qs_txn()
569 .internal_search_uuid(apit.account_id)
570 .map_err(|err| {
571 security_info!(?err, "Account associated with api token no longer exists.");
572 OperationError::NotAuthenticated
573 })?;
574
575 return Ok(Token::ApiToken(apit, entry));
576 };
577
578 if let Ok(session_id) = Uuid::from_slice(jws_inner.payload()) {
580 let filter = filter!(f_eq(
583 Attribute::ApiTokenSession,
584 PartialValue::Refer(session_id)
585 ));
586
587 let mut entry = self.get_qs_txn().internal_search(filter).map_err(|err| {
588 security_info!(
589 ?err,
590 "Account or session associated with session token no longer exists."
591 );
592 OperationError::NotAuthenticated
593 })?;
594
595 let entry = entry.pop().ok_or_else(|| {
596 security_info!("Search result failed to return a valid entry.");
597 OperationError::NotAuthenticated
598 })?;
599
600 let api_token_map = entry.get_ava_as_apitoken_map(Attribute::ApiTokenSession)
601 .ok_or_else(|| {
602 security_info!(entry_id = %entry.get_display_id(), "Account does not contain any valid api token sessions.");
603 OperationError::NotAuthenticated
604 })?;
605
606 let api_token_internal = api_token_map.get(&session_id)
607 .ok_or_else(|| {
608 security_info!(entry_id = %entry.get_display_id(), "Account does not contain a valid api token for the session.");
609 OperationError::NotAuthenticated
610 })?;
611
612 let purpose = api_token_internal.scope.try_into().map_err(|_| {
613 security_info!(entry_id = %entry.get_display_id(), "Account scope is not valid.");
614 OperationError::NotAuthenticated
615 })?;
616
617 let apit = kanidm_proto::internal::ApiToken {
618 account_id: entry.get_uuid(),
619 token_id: session_id,
620 label: api_token_internal.label.clone(),
621 expiry: api_token_internal.expiry,
622 issued_at: api_token_internal.issued_at,
623 purpose,
624 };
625
626 if let Some(expiry) = apit.expiry {
627 if time::OffsetDateTime::UNIX_EPOCH + ct >= expiry {
628 security_info!(entry_id = %entry.get_display_id(), "Session expired");
629 return Err(OperationError::SessionExpired);
630 }
631 }
632
633 return Ok(Token::ApiToken(apit, entry));
634 }
635
636 security_info!("Unable to verify token, invalid inner JSON");
637 Err(OperationError::NotAuthenticated)
638 }
639
640 fn check_oauth2_account_uuid_valid(
641 &mut self,
642 uuid: Uuid,
643 session_id: Uuid,
644 parent_session_id: Option<Uuid>,
645 iat: i64,
646 ct: Duration,
647 ) -> Result<Option<Arc<Entry<EntrySealed, EntryCommitted>>>, OperationError> {
648 let entry = self.get_qs_txn().internal_search_uuid(uuid).map_err(|e| {
649 admin_error!(?e, "check_oauth2_account_uuid_valid failed");
650 e
651 })?;
652
653 let within_valid_window = Account::check_within_valid_time(
654 ct,
655 entry
656 .get_ava_single_datetime(Attribute::AccountValidFrom)
657 .as_ref(),
658 entry
659 .get_ava_single_datetime(Attribute::AccountExpire)
660 .as_ref(),
661 );
662
663 if !within_valid_window {
664 security_info!("Account has expired or is not yet valid, not allowing to proceed");
665 return Ok(None);
666 }
667
668 let grace_valid = ct < (Duration::from_secs(iat as u64) + AUTH_TOKEN_GRACE_WINDOW);
673
674 let oauth2_session = entry
675 .get_ava_as_oauth2session_map(Attribute::OAuth2Session)
676 .and_then(|sessions| sessions.get(&session_id));
677
678 if let Some(oauth2_session) = oauth2_session {
679 let oauth2_session_valid = !matches!(oauth2_session.state, SessionState::RevokedAt(_));
681
682 if !oauth2_session_valid {
683 security_info!("The oauth2 session associated to this token is revoked.");
684 return Ok(None);
685 }
686
687 if let Some(parent_session_id) = parent_session_id {
689 let uat_session = entry
690 .get_ava_as_session_map(Attribute::UserAuthTokenSession)
691 .and_then(|sessions| sessions.get(&parent_session_id));
692
693 if let Some(uat_session) = uat_session {
694 let parent_session_valid =
695 !matches!(uat_session.state, SessionState::RevokedAt(_));
696 if parent_session_valid {
697 security_info!(
698 "A valid parent and oauth2 session value exists for this token"
699 );
700 } else {
701 security_info!(
702 "The parent oauth2 session associated to this token is revoked."
703 );
704 return Ok(None);
705 }
706 } else {
707 let api_session = entry
708 .get_ava_as_apitoken_map(Attribute::ApiTokenSession)
709 .and_then(|sessions| sessions.get(&parent_session_id));
710 if api_session.is_some() {
711 security_info!("A valid api token session value exists for this token");
712 } else if grace_valid {
713 security_info!(
714 "The token grace window is in effect. Assuming parent session valid."
715 );
716 } else {
717 security_info!("The token grace window has passed and no entry parent sessions exist. Assuming invalid.");
718 return Ok(None);
719 }
720 }
721 }
722 } else if grace_valid {
724 security_info!("The token grace window is in effect. Assuming valid.");
725 } else {
726 security_info!(
727 "The token grace window has passed and no entry sessions exist. Assuming invalid."
728 );
729 return Ok(None);
730 }
731
732 Ok(Some(entry))
733 }
734
735 #[instrument(level = "debug", skip_all)]
748 fn process_uat_to_identity(
749 &mut self,
750 uat: &UserAuthToken,
751 ct: Duration,
752 source: Source,
753 ) -> Result<Identity, OperationError> {
754 let entry = self
756 .get_qs_txn()
757 .internal_search_uuid(uat.uuid)
758 .map_err(|err| {
759 error!(?err, "from_ro_uat failed");
760 match err {
764 OperationError::NoMatchingEntries => OperationError::SessionExpired,
765 err => err,
766 }
767 })?;
768
769 let valid = Account::check_user_auth_token_valid(ct, uat, &entry);
770
771 if !valid {
772 return Err(OperationError::SessionExpired);
773 }
774
775 let scope = match uat.purpose {
778 UatPurpose::ReadOnly => AccessScope::ReadOnly,
779 UatPurpose::ReadWrite { expiry: None } => AccessScope::ReadOnly,
780 UatPurpose::ReadWrite {
781 expiry: Some(expiry),
782 } => {
783 let cot = time::OffsetDateTime::UNIX_EPOCH + ct;
784 if cot < expiry {
785 AccessScope::ReadWrite
786 } else {
787 AccessScope::ReadOnly
788 }
789 }
790 };
791
792 let mut limits = Limits::default();
793 if let Some(lim) = uat.limit_search_max_results.and_then(|v| v.try_into().ok()) {
795 limits.search_max_results = lim;
796 }
797 if let Some(lim) = uat
798 .limit_search_max_filter_test
799 .and_then(|v| v.try_into().ok())
800 {
801 limits.search_max_filter_test = lim;
802 }
803
804 Ok(Identity::new(
820 IdentType::User(IdentUser { entry }),
821 source,
822 uat.session_id,
823 scope,
824 limits,
825 Some(uat.issued_at),
828 ))
829 }
830
831 #[instrument(level = "debug", skip_all)]
832 fn process_apit_to_identity(
833 &mut self,
834 apit: &ApiToken,
835 source: Source,
836 entry: Arc<EntrySealedCommitted>,
837 ct: Duration,
838 ) -> Result<Identity, OperationError> {
839 let valid = ServiceAccount::check_api_token_valid(ct, apit, &entry);
840
841 if !valid {
842 return Err(OperationError::SessionExpired);
844 }
845
846 let scope = (&apit.purpose).into();
847 let last_verified_at = None;
850
851 let limits = Limits::api_token();
852
853 Ok(Identity::new(
854 IdentType::User(IdentUser { entry }),
855 source,
856 apit.token_id,
857 scope,
858 limits,
859 last_verified_at,
860 ))
861 }
862
863 fn client_cert_info_entry(
864 &mut self,
865 client_cert_info: &ClientCertInfo,
866 ) -> Result<Arc<EntrySealedCommitted>, OperationError> {
867 let pks256 = hex::encode(client_cert_info.public_key_s256);
868 let mut maybe_cert_entries = self.get_qs_txn().internal_search(filter!(f_eq(
870 Attribute::Certificate,
871 PartialValue::HexString(pks256.clone())
872 )))?;
873
874 let maybe_cert_entry = maybe_cert_entries.pop();
875
876 if let Some(cert_entry) = maybe_cert_entry {
877 if maybe_cert_entries.is_empty() {
878 Ok(cert_entry)
879 } else {
880 debug!(?pks256, "Multiple certificates matched, unable to proceed.");
881 Err(OperationError::NotAuthenticated)
882 }
883 } else {
884 debug!(?pks256, "No certificates were able to be mapped.");
885 Err(OperationError::NotAuthenticated)
886 }
887 }
888
889 #[instrument(level = "debug", skip_all)]
898 fn client_certificate_to_identity(
899 &mut self,
900 client_cert_info: &ClientCertInfo,
901 ct: Duration,
902 source: Source,
903 ) -> Result<Identity, OperationError> {
904 let cert_entry = self.client_cert_info_entry(client_cert_info)?;
905
906 let refers_uuid = cert_entry
908 .get_ava_single_refer(Attribute::Refers)
909 .ok_or_else(|| {
910 warn!("Invalid certificate entry, missing refers");
911 OperationError::InvalidState
912 })?;
913
914 let entry = self.get_qs_txn().internal_search_uuid(refers_uuid)?;
916
917 let (account, account_policy) =
918 Account::try_from_entry_with_policy(entry.as_ref(), self.get_qs_txn())?;
919
920 if !account.is_within_valid_time(ct) {
922 return Err(OperationError::SessionExpired);
924 };
925
926 let scope = AccessScope::ReadOnly;
928
929 let mut limits = Limits::default();
930 if let Some(lim) = account_policy
932 .limit_search_max_results()
933 .and_then(|v| v.try_into().ok())
934 {
935 limits.search_max_results = lim;
936 }
937 if let Some(lim) = account_policy
938 .limit_search_max_filter_test()
939 .and_then(|v| v.try_into().ok())
940 {
941 limits.search_max_filter_test = lim;
942 }
943
944 let certificate_uuid = cert_entry.get_uuid();
945 let odt_ct = OffsetDateTime::UNIX_EPOCH + ct;
949 let last_verified_at = Some(odt_ct);
950
951 Ok(Identity::new(
952 IdentType::User(IdentUser { entry }),
953 source,
954 certificate_uuid,
956 scope,
957 limits,
958 last_verified_at,
959 ))
960 }
961
962 #[instrument(level = "debug", skip_all)]
963 fn client_certificate_to_user_auth_token(
964 &mut self,
965 client_cert_info: &ClientCertInfo,
966 ct: Duration,
967 ) -> Result<UserAuthToken, OperationError> {
968 let cert_entry = self.client_cert_info_entry(client_cert_info)?;
969
970 let refers_uuid = cert_entry
972 .get_ava_single_refer(Attribute::Refers)
973 .ok_or_else(|| {
974 warn!("Invalid certificate entry, missing refers");
975 OperationError::InvalidState
976 })?;
977
978 let entry = self.get_qs_txn().internal_search_uuid(refers_uuid)?;
980
981 let (account, account_policy) =
982 Account::try_from_entry_with_policy(entry.as_ref(), self.get_qs_txn())?;
983
984 if !account.is_within_valid_time(ct) {
986 return Err(OperationError::SessionExpired);
988 };
989
990 let certificate_uuid = cert_entry.get_uuid();
991 let session_is_rw = false;
992
993 account
994 .client_cert_info_to_userauthtoken(certificate_uuid, session_is_rw, ct, &account_policy)
995 .ok_or(OperationError::InvalidState)
996 }
997
998 fn process_ldap_uuid_to_identity(
999 &mut self,
1000 uuid: &Uuid,
1001 ct: Duration,
1002 source: Source,
1003 ) -> Result<Identity, OperationError> {
1004 let entry = self
1005 .get_qs_txn()
1006 .internal_search_uuid(*uuid)
1007 .map_err(|err| {
1008 error!(?err, ?uuid, "Failed to search user by uuid");
1009 err
1010 })?;
1011
1012 let (account, account_policy) =
1013 Account::try_from_entry_with_policy(entry.as_ref(), self.get_qs_txn())?;
1014
1015 if !account.is_within_valid_time(ct) {
1016 info!("Account is expired or not yet valid.");
1017 return Err(OperationError::SessionExpired);
1018 }
1019
1020 let anon_entry = if *uuid == UUID_ANONYMOUS {
1022 entry
1024 } else {
1025 self.get_qs_txn()
1027 .internal_search_uuid(UUID_ANONYMOUS)
1028 .map_err(|err| {
1029 error!(
1030 ?err,
1031 "Unable to search anonymous user for privilege bounding."
1032 );
1033 err
1034 })?
1035 };
1036
1037 let mut limits = Limits::default();
1038 let session_id = Uuid::new_v4();
1039
1040 if let Some(max_results) = account_policy.limit_search_max_results() {
1042 limits.search_max_results = max_results as usize;
1043 }
1044 if let Some(max_filter) = account_policy.limit_search_max_filter_test() {
1045 limits.search_max_filter_test = max_filter as usize;
1046 }
1047
1048 let last_verified_at = None;
1050
1051 Ok(Identity::new(
1054 IdentType::User(IdentUser { entry: anon_entry }),
1055 source,
1056 session_id,
1057 AccessScope::ReadOnly,
1058 limits,
1059 last_verified_at,
1060 ))
1061 }
1062
1063 #[instrument(level = "debug", skip_all)]
1064 fn validate_ldap_session(
1065 &mut self,
1066 session: &LdapSession,
1067 source: Source,
1068 ct: Duration,
1069 ) -> Result<Identity, OperationError> {
1070 match session {
1071 LdapSession::UnixBind(uuid) | LdapSession::ApplicationPasswordBind(_, uuid) => {
1072 self.process_ldap_uuid_to_identity(uuid, ct, source)
1073 }
1074 LdapSession::UserAuthToken(uat) => self.process_uat_to_identity(uat, ct, source),
1075 LdapSession::ApiToken(apit) => {
1076 let entry = self
1077 .get_qs_txn()
1078 .internal_search_uuid(apit.account_id)
1079 .map_err(|e| {
1080 admin_error!("Failed to validate ldap session -> {:?}", e);
1081 e
1082 })?;
1083
1084 self.process_apit_to_identity(apit, source, entry, ct)
1085 }
1086 }
1087 }
1088
1089 #[instrument(level = "info", skip_all)]
1090 fn validate_sync_client_auth_info_to_ident(
1091 &mut self,
1092 client_auth_info: ClientAuthInfo,
1093 ct: Duration,
1094 ) -> Result<Identity, OperationError> {
1095 let jwsu = client_auth_info.bearer_token.ok_or_else(|| {
1098 security_info!("No token provided");
1099 OperationError::NotAuthenticated
1100 })?;
1101
1102 let jws_inner = self
1103 .get_qs_txn()
1104 .get_domain_key_object_handle()?
1105 .jws_verify(&jwsu)
1106 .map_err(|err| {
1107 security_info!(?err, "Unable to verify token");
1108 OperationError::NotAuthenticated
1109 })?;
1110
1111 let sync_token = jws_inner.from_json::<ScimSyncToken>().map_err(|err| {
1112 error!(?err, "Unable to deserialise JWS");
1113 OperationError::SerdeJsonError
1114 })?;
1115
1116 let entry = self
1117 .get_qs_txn()
1118 .internal_search(filter!(f_eq(
1119 Attribute::SyncTokenSession,
1120 PartialValue::Refer(sync_token.token_id)
1121 )))
1122 .and_then(|mut vs| match vs.pop() {
1123 Some(entry) if vs.is_empty() => Ok(entry),
1124 _ => {
1125 admin_error!(
1126 token_id = ?sync_token.token_id,
1127 "entries was empty, or matched multiple results for token id"
1128 );
1129 Err(OperationError::NotAuthenticated)
1130 }
1131 })?;
1132
1133 let valid = SyncAccount::check_sync_token_valid(ct, &sync_token, &entry);
1134
1135 if !valid {
1136 security_info!("Unable to proceed with invalid sync token");
1137 return Err(OperationError::NotAuthenticated);
1138 }
1139
1140 let scope = (&sync_token.purpose).into();
1142 let last_verified_at = None;
1143
1144 let limits = Limits::unlimited();
1145 Ok(Identity::new(
1146 IdentType::Synch(entry.get_uuid()),
1147 client_auth_info.source,
1148 sync_token.token_id,
1149 scope,
1150 limits,
1151 last_verified_at,
1152 ))
1153 }
1154}
1155
1156impl<'a> IdmServerTransaction<'a> for IdmServerAuthTransaction<'a> {
1157 type QsTransactionType = QueryServerReadTransaction<'a>;
1158
1159 fn get_qs_txn(&mut self) -> &mut Self::QsTransactionType {
1160 &mut self.qs_read
1161 }
1162}
1163
1164impl IdmServerAuthTransaction<'_> {
1165 #[cfg(test)]
1166 pub fn is_sessionid_present(&self, sessionid: Uuid) -> bool {
1167 let session_read = self.sessions.read();
1168 session_read.contains_key(&sessionid)
1169 }
1170
1171 pub fn get_origin(&self) -> &Url {
1172 #[allow(clippy::unwrap_used)]
1173 self.webauthn.get_allowed_origins().first().unwrap()
1174 }
1175
1176 #[instrument(level = "trace", skip(self))]
1177 pub async fn expire_auth_sessions(&mut self, ct: Duration) {
1178 let expire = ct - Duration::from_secs(AUTH_SESSION_TIMEOUT);
1180 let split_at = uuid_from_duration(expire, self.sid);
1181 let _session_ticket = self.session_ticket.acquire().await;
1183 let mut session_write = self.sessions.write();
1184 session_write.split_off_lt(&split_at);
1185 session_write.commit();
1187 }
1188
1189 pub async fn auth(
1190 &mut self,
1191 ae: &AuthEvent,
1192 ct: Duration,
1193 client_auth_info: ClientAuthInfo,
1194 ) -> Result<AuthResult, OperationError> {
1195 match &ae.step {
1197 AuthEventStep::Init(init) => {
1198 let sessionid = uuid_from_duration(ct, self.sid);
1201
1202 let euuid = self.qs_read.name_to_uuid(init.username.as_str())?;
1218
1219 let entry = self.qs_read.internal_search_uuid(euuid)?;
1221
1222 info!(
1223 username = %init.username,
1224 issue = ?init.issue,
1225 privileged = ?init.privileged,
1226 uuid = %euuid,
1227 "Initiating Authentication Session",
1228 );
1229
1230 let (account, account_policy) =
1235 Account::try_from_entry_with_policy(entry.as_ref(), &mut self.qs_read)?;
1236
1237 trace!(?account.primary);
1238
1239 let _session_ticket = self.session_ticket.acquire().await;
1241
1242 let _maybe_slock_ref =
1248 account
1249 .primary_cred_uuid_and_policy()
1250 .map(|(cred_uuid, policy)| {
1251 let mut softlock_write = self.softlocks.write();
1256 let slock_ref: CredSoftLockMutex =
1257 if let Some(slock_ref) = softlock_write.get(&cred_uuid) {
1258 slock_ref.clone()
1259 } else {
1260 let slock = Arc::new(Mutex::new(CredSoftLock::new(policy)));
1262 softlock_write.insert(cred_uuid, slock.clone());
1263 slock
1264 };
1265 softlock_write.commit();
1266 slock_ref
1267 });
1268
1269 let oauth2_client_provider =
1271 account.oauth2_client_provider().and_then(|trust_provider| {
1272 debug!(?trust_provider);
1273 self.oauth2_client_providers.get(&trust_provider.provider)
1275 });
1276
1277 debug!(?oauth2_client_provider);
1278
1279 let asd: AuthSessionData = AuthSessionData {
1280 account,
1281 account_policy,
1282 issue: init.issue,
1283 webauthn: self.webauthn,
1284 ct,
1285 client_auth_info,
1286 oauth2_client_provider,
1287 };
1288
1289 let domain_keys = self.qs_read.get_domain_key_object_handle()?;
1290
1291 let (auth_session, state) = AuthSession::new(asd, init.privileged, domain_keys);
1292
1293 match auth_session {
1294 Some(auth_session) => {
1295 let mut session_write = self.sessions.write();
1296 if session_write.contains_key(&sessionid) {
1297 Err(OperationError::InvalidSessionState)
1300 } else {
1301 session_write.insert(sessionid, Arc::new(Mutex::new(auth_session)));
1302 debug_assert!(session_write.get(&sessionid).is_some());
1304 Ok(())
1305 }?;
1306 session_write.commit();
1307 }
1308 None => {
1309 security_info!("Authentication Session Unable to begin");
1310 }
1311 };
1312
1313 Ok(AuthResult { sessionid, state })
1314 } AuthEventStep::Begin(mech) => {
1316 let session_read = self.sessions.read();
1317 let auth_session_ref = session_read
1319 .get(&mech.sessionid)
1321 .cloned()
1322 .ok_or_else(|| {
1323 admin_error!("Invalid Session State (no present session uuid)");
1324 OperationError::InvalidSessionState
1325 })?;
1326
1327 let mut auth_session = auth_session_ref.lock().await;
1328
1329 let auth_result = auth_session.start_session(&mech.mech);
1331
1332 let is_valid = match auth_session.get_credential_uuid()? {
1333 Some(cred_uuid) => {
1334 let softlock_read = self.softlocks.read();
1337 if let Some(slock_ref) = softlock_read.get(&cred_uuid) {
1338 let mut slock = slock_ref.lock().await;
1339
1340 let softlock_expire_odt = auth_session.account().softlock_expire();
1341
1342 let softlock_expire = softlock_expire_odt
1343 .map(|odt| odt.unix_timestamp() as u64)
1344 .map(Duration::from_secs);
1345
1346 slock.apply_time_step(ct, softlock_expire);
1348 slock.is_valid()
1350 } else {
1351 trace!("slock not found");
1352 false
1353 }
1354 }
1355 None => true,
1356 };
1357
1358 if is_valid {
1359 auth_result
1360 } else {
1361 trace!("lock step begin");
1363 auth_session.end_session("Account is temporarily locked")
1364 }
1365 .map(|aus| AuthResult {
1366 sessionid: mech.sessionid,
1367 state: aus,
1368 })
1369 } AuthEventStep::Cred(creds) => {
1371 let session_read = self.sessions.read();
1375 let auth_session_ref = session_read
1377 .get(&creds.sessionid)
1379 .cloned()
1380 .ok_or_else(|| {
1381 admin_error!("Invalid Session State (no present session uuid)");
1382 OperationError::InvalidSessionState
1383 })?;
1384
1385 let mut auth_session = auth_session_ref.lock().await;
1386
1387 let maybe_slock_ref = match auth_session.get_credential_uuid()? {
1388 Some(cred_uuid) => {
1389 let softlock_read = self.softlocks.read();
1390 softlock_read.get(&cred_uuid).cloned()
1391 }
1392 None => None,
1393 };
1394
1395 let mut maybe_slock = if let Some(s) = maybe_slock_ref.as_ref() {
1398 Some(s.lock().await)
1399 } else {
1400 None
1401 };
1402
1403 let is_valid = if let Some(ref mut slock) = maybe_slock {
1404 slock.apply_time_step(ct, None);
1406 slock.is_valid()
1408 } else {
1409 true
1411 };
1412
1413 if is_valid {
1414 auth_session
1418 .validate_creds(
1419 &creds.cred,
1420 ct,
1421 &self.async_tx,
1422 &self.audit_tx,
1423 self.webauthn,
1424 self.qs_read.pw_badlist(),
1425 )
1426 .inspect(|aus| {
1427 if let AuthState::Denied(_) = aus {
1430 if let Some(ref mut slock) = maybe_slock {
1432 slock.record_failure(ct);
1433 }
1434 };
1435 })
1436 } else {
1437 trace!("lock step cred");
1439 auth_session.end_session("Account is temporarily locked")
1440 }
1441 .map(|aus| AuthResult {
1442 sessionid: creds.sessionid,
1443 state: aus,
1444 })
1445 } }
1447 }
1448
1449 async fn auth_with_unix_pass(
1450 &mut self,
1451 id: Uuid,
1452 cleartext: &str,
1453 ct: Duration,
1454 ) -> Result<Option<Account>, OperationError> {
1455 let entry = match self.qs_read.internal_search_uuid(id) {
1456 Ok(entry) => entry,
1457 Err(e) => {
1458 admin_error!("Failed to start auth unix -> {:?}", e);
1459 return Err(e);
1460 }
1461 };
1462
1463 let (account, acp) =
1464 Account::try_from_entry_with_policy(entry.as_ref(), &mut self.qs_read)?;
1465
1466 if !account.is_within_valid_time(ct) {
1467 security_info!("Account is expired or not yet valid.");
1468 return Ok(None);
1469 }
1470
1471 let softlock_expire_odt = account.softlock_expire();
1472
1473 let softlock_expire = softlock_expire_odt
1474 .map(|odt| odt.unix_timestamp() as u64)
1475 .map(Duration::from_secs);
1476
1477 let cred = if acp.allow_primary_cred_fallback() == Some(true) {
1478 account
1479 .unix_extn()
1480 .and_then(|extn| extn.ucred())
1481 .or_else(|| account.primary())
1482 } else {
1483 account.unix_extn().and_then(|extn| extn.ucred())
1484 };
1485
1486 let (cred, cred_id, cred_slock_policy) = match cred {
1487 None => {
1488 if acp.allow_primary_cred_fallback() == Some(true) {
1489 security_info!("Account does not have a POSIX or primary password configured.");
1490 } else {
1491 security_info!("Account does not have a POSIX password configured.");
1492 }
1493 return Ok(None);
1494 }
1495 Some(cred) => (cred, cred.uuid, cred.softlock_policy()),
1496 };
1497
1498 let Ok(password) = cred.password_ref() else {
1500 error!("User's UNIX or primary credential is not a password, can't authenticate!");
1501 return Err(OperationError::InvalidState);
1502 };
1503
1504 let slock_ref = {
1505 let softlock_read = self.softlocks.read();
1506 if let Some(slock_ref) = softlock_read.get(&cred_id) {
1507 slock_ref.clone()
1508 } else {
1509 let _session_ticket = self.session_ticket.acquire().await;
1510 let mut softlock_write = self.softlocks.write();
1511 let slock = Arc::new(Mutex::new(CredSoftLock::new(cred_slock_policy)));
1512 softlock_write.insert(cred_id, slock.clone());
1513 softlock_write.commit();
1514 slock
1515 }
1516 };
1517
1518 let mut slock = slock_ref.lock().await;
1519
1520 slock.apply_time_step(ct, softlock_expire);
1521
1522 if !slock.is_valid() {
1523 security_info!("Account is softlocked.");
1524 return Ok(None);
1525 }
1526
1527 let valid = password.verify(cleartext).map_err(|e| {
1529 error!(crypto_err = ?e);
1530 OperationError::CryptographyError
1531 })?;
1532
1533 if !valid {
1534 slock.record_failure(ct);
1536
1537 return Ok(None);
1538 }
1539
1540 security_info!("Successfully authenticated with unix (or primary) password");
1541 if password.requires_upgrade() {
1542 self.async_tx
1543 .send(DelayedAction::UnixPwUpgrade(UnixPasswordUpgrade {
1544 target_uuid: id,
1545 existing_password: cleartext.to_string(),
1546 }))
1547 .map_err(|_| {
1548 admin_error!("failed to queue delayed action - unix password upgrade");
1549 OperationError::InvalidState
1550 })?;
1551 }
1552
1553 Ok(Some(account))
1554 }
1555
1556 pub async fn auth_unix(
1557 &mut self,
1558 uae: &UnixUserAuthEvent,
1559 ct: Duration,
1560 ) -> Result<Option<UnixUserToken>, OperationError> {
1561 Ok(self
1562 .auth_with_unix_pass(uae.target, &uae.cleartext, ct)
1563 .await?
1564 .and_then(|acc| acc.to_unixusertoken(ct).ok()))
1565 }
1566
1567 pub async fn auth_ldap(
1568 &mut self,
1569 lae: &LdapAuthEvent,
1570 ct: Duration,
1571 ) -> Result<Option<LdapBoundToken>, OperationError> {
1572 if lae.target == UUID_ANONYMOUS {
1573 let account_entry = self.qs_read.internal_search_uuid(lae.target).map_err(|e| {
1574 admin_error!("Failed to start auth ldap -> {:?}", e);
1575 e
1576 })?;
1577
1578 let account = Account::try_from_entry_ro(account_entry.as_ref(), &mut self.qs_read)?;
1579
1580 if !account.is_within_valid_time(ct) {
1582 security_info!("Account is not within valid time period");
1583 return Ok(None);
1584 }
1585
1586 let session_id = Uuid::new_v4();
1587 security_info!(
1588 "Starting session {} for {} {}",
1589 session_id,
1590 account.spn(),
1591 account.uuid
1592 );
1593
1594 Ok(Some(LdapBoundToken {
1596 session_id,
1597 spn: account.spn().into(),
1598 effective_session: LdapSession::UnixBind(UUID_ANONYMOUS),
1599 }))
1600 } else {
1601 if !self.qs_read.d_info.d_ldap_allow_unix_pw_bind {
1602 security_info!("Bind not allowed through Unix passwords.");
1603 return Ok(None);
1604 }
1605
1606 let auth = self
1607 .auth_with_unix_pass(lae.target, &lae.cleartext, ct)
1608 .await?;
1609
1610 match auth {
1611 Some(account) => {
1612 let session_id = Uuid::new_v4();
1613 security_info!(
1614 "Starting session {} for {} {}",
1615 session_id,
1616 account.spn(),
1617 account.uuid
1618 );
1619
1620 Ok(Some(LdapBoundToken {
1621 spn: account.spn().into(),
1622 session_id,
1623 effective_session: LdapSession::UnixBind(account.uuid),
1624 }))
1625 }
1626 None => Ok(None),
1627 }
1628 }
1629 }
1630
1631 pub async fn token_auth_ldap(
1632 &mut self,
1633 lae: &LdapTokenAuthEvent,
1634 ct: Duration,
1635 ) -> Result<Option<LdapBoundToken>, OperationError> {
1636 match self.validate_and_parse_token_to_identity_token(&lae.token, ct)? {
1637 Token::UserAuthToken(uat) => {
1638 let spn = uat.spn.clone();
1639 Ok(Some(LdapBoundToken {
1640 session_id: uat.session_id,
1641 spn,
1642 effective_session: LdapSession::UserAuthToken(uat),
1643 }))
1644 }
1645 Token::ApiToken(apit, entry) => {
1646 let spn = entry
1647 .get_ava_single_proto_string(Attribute::Spn)
1648 .ok_or_else(|| OperationError::MissingAttribute(Attribute::Spn))?;
1649
1650 Ok(Some(LdapBoundToken {
1651 session_id: apit.token_id,
1652 spn,
1653 effective_session: LdapSession::ApiToken(apit),
1654 }))
1655 }
1656 }
1657 }
1658
1659 pub fn commit(self) -> Result<(), OperationError> {
1660 Ok(())
1661 }
1662}
1663
1664impl<'a> IdmServerTransaction<'a> for IdmServerProxyReadTransaction<'a> {
1665 type QsTransactionType = QueryServerReadTransaction<'a>;
1666
1667 fn get_qs_txn(&mut self) -> &mut Self::QsTransactionType {
1668 &mut self.qs_read
1669 }
1670}
1671
1672fn gen_password_mod(
1673 cleartext: &str,
1674 crypto_policy: &CryptoPolicy,
1675 timestamp: OffsetDateTime,
1676) -> Result<ModifyList<ModifyInvalid>, OperationError> {
1677 let new_cred = Credential::new_password_only(crypto_policy, cleartext, timestamp)?;
1678 let cred_value = Value::new_credential("unix", new_cred);
1679 Ok(ModifyList::new_purge_and_set(
1680 Attribute::UnixPassword,
1681 cred_value,
1682 ))
1683}
1684
1685fn gen_password_upgrade_mod(
1686 unix_cred: &Credential,
1687 cleartext: &str,
1688 crypto_policy: &CryptoPolicy,
1689) -> Result<Option<ModifyList<ModifyInvalid>>, OperationError> {
1690 if let Some(new_cred) = unix_cred.upgrade_password(crypto_policy, cleartext)? {
1691 let cred_value = Value::new_credential("primary", new_cred);
1692 Ok(Some(ModifyList::new_purge_and_set(
1693 Attribute::UnixPassword,
1694 cred_value,
1695 )))
1696 } else {
1697 Ok(None)
1699 }
1700}
1701
1702impl IdmServerProxyReadTransaction<'_> {
1703 pub fn jws_public_jwk(&mut self, key_id: &str) -> Result<Jwk, OperationError> {
1704 self.qs_read
1705 .get_key_providers()
1706 .get_key_object_handle(UUID_DOMAIN_INFO)
1707 .ok_or(OperationError::NoMatchingEntries)
1709 .and_then(|key_object| key_object.jws_public_jwk(key_id))
1710 .and_then(|maybe_key: Option<Jwk>| maybe_key.ok_or(OperationError::NoMatchingEntries))
1711 }
1712
1713 pub fn get_radiusauthtoken(
1714 &mut self,
1715 rate: &RadiusAuthTokenEvent,
1716 ct: Duration,
1717 ) -> Result<RadiusAuthToken, OperationError> {
1718 let account = self
1719 .qs_read
1720 .impersonate_search_ext_uuid(rate.target, &rate.ident)
1721 .and_then(|account_entry| {
1722 RadiusAccount::try_from_entry_reduced(&account_entry, &mut self.qs_read)
1723 })
1724 .map_err(|e| {
1725 admin_error!("Failed to start radius auth token {:?}", e);
1726 e
1727 })?;
1728
1729 account.to_radiusauthtoken(ct)
1730 }
1731
1732 pub fn get_unixusertoken(
1733 &mut self,
1734 uute: &UnixUserTokenEvent,
1735 ct: Duration,
1736 ) -> Result<UnixUserToken, OperationError> {
1737 let account = self
1738 .qs_read
1739 .impersonate_search_uuid(uute.target, &uute.ident)
1740 .and_then(|account_entry| Account::try_from_entry_ro(&account_entry, &mut self.qs_read))
1741 .map_err(|e| {
1742 admin_error!("Failed to start unix user token -> {:?}", e);
1743 e
1744 })?;
1745
1746 account.to_unixusertoken(ct)
1747 }
1748
1749 pub fn get_unixgrouptoken(
1750 &mut self,
1751 uute: &UnixGroupTokenEvent,
1752 ) -> Result<UnixGroupToken, OperationError> {
1753 let group = self
1754 .qs_read
1755 .impersonate_search_ext_uuid(uute.target, &uute.ident)
1756 .and_then(|e| Group::<Unix>::try_from_entry(&e))
1757 .map_err(|e| {
1758 admin_error!("Failed to start unix group token {:?}", e);
1759 e
1760 })?;
1761 Ok(group.to_unixgrouptoken())
1762 }
1763
1764 pub fn get_credentialstatus(
1765 &mut self,
1766 cse: &CredentialStatusEvent,
1767 ) -> Result<CredentialStatus, OperationError> {
1768 let account = self
1769 .qs_read
1770 .impersonate_search_ext_uuid(cse.target, &cse.ident)
1771 .and_then(|account_entry| {
1772 Account::try_from_entry_reduced(&account_entry, &mut self.qs_read)
1773 })
1774 .map_err(|e| {
1775 admin_error!("Failed to search account {:?}", e);
1776 e
1777 })?;
1778
1779 account.to_credentialstatus()
1780 }
1781}
1782
1783impl<'a> IdmServerTransaction<'a> for IdmServerProxyWriteTransaction<'a> {
1784 type QsTransactionType = QueryServerWriteTransaction<'a>;
1785
1786 fn get_qs_txn(&mut self) -> &mut Self::QsTransactionType {
1787 &mut self.qs_write
1788 }
1789}
1790
1791impl IdmServerProxyWriteTransaction<'_> {
1792 pub(crate) fn crypto_policy(&self) -> &CryptoPolicy {
1793 self.crypto_policy
1794 }
1795
1796 pub fn get_origin(&self) -> &Url {
1797 #[allow(clippy::unwrap_used)]
1798 self.webauthn.get_allowed_origins().first().unwrap()
1799 }
1800
1801 fn check_password_quality(
1802 &mut self,
1803 cleartext: &str,
1804 related_inputs: &[&str],
1805 ) -> Result<(), OperationError> {
1806 if cleartext.len() < PW_MIN_LENGTH as usize {
1812 return Err(OperationError::PasswordQuality(vec![
1813 PasswordFeedback::TooShort(PW_MIN_LENGTH),
1814 ]));
1815 }
1816
1817 let entropy = zxcvbn(cleartext, related_inputs);
1820
1821 if entropy.score() < Score::Four {
1823 let feedback: zxcvbn::feedback::Feedback = entropy
1826 .feedback()
1827 .ok_or(OperationError::InvalidState)
1828 .cloned()
1829 .inspect_err(|err| {
1830 security_info!(?err, "zxcvbn returned no feedback when score < 3");
1831 })?;
1832
1833 security_info!(?feedback, "pw quality feedback");
1834
1835 return Err(OperationError::PasswordQuality(vec![
1838 PasswordFeedback::BadListed,
1839 ]));
1840 }
1841
1842 if self
1846 .qs_write
1847 .pw_badlist()
1848 .contains(&cleartext.to_lowercase())
1849 {
1850 security_info!("Password found in badlist, rejecting");
1851 Err(OperationError::PasswordQuality(vec![
1852 PasswordFeedback::BadListed,
1853 ]))
1854 } else {
1855 Ok(())
1856 }
1857 }
1858
1859 pub(crate) fn target_to_account(&mut self, target: Uuid) -> Result<Account, OperationError> {
1860 let account = self
1862 .qs_write
1863 .internal_search_uuid(target)
1864 .and_then(|account_entry| {
1865 Account::try_from_entry_rw(&account_entry, &mut self.qs_write)
1866 })
1867 .map_err(|e| {
1868 admin_error!("Failed to search account {:?}", e);
1869 e
1870 })?;
1871 if account.is_anonymous() {
1875 admin_warn!("Unable to convert anonymous to account during write txn");
1876 Err(OperationError::SystemProtectedObject)
1877 } else {
1878 Ok(account)
1879 }
1880 }
1881
1882 #[cfg(test)]
1883 pub(crate) fn set_account_password(
1884 &mut self,
1885 pce: &PasswordChangeEvent,
1886 ct: OffsetDateTime,
1887 ) -> Result<(), OperationError> {
1888 let account = self.target_to_account(pce.target)?;
1889
1890 let modlist = account
1892 .gen_password_mod(pce.cleartext.as_str(), self.crypto_policy, ct)
1893 .map_err(|e| {
1894 admin_error!("Failed to generate password mod {:?}", e);
1895 e
1896 })?;
1897 trace!(?modlist, "processing change");
1898
1899 let me = self
1902 .qs_write
1903 .impersonate_modify_gen_event(
1904 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(pce.target))),
1906 &filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(pce.target))),
1908 &modlist,
1909 &pce.ident,
1910 )
1911 .map_err(|e| {
1912 request_error!(error = ?e);
1913 e
1914 })?;
1915
1916 let mp = self
1917 .qs_write
1918 .modify_pre_apply(&me)
1919 .and_then(|opt_mp| opt_mp.ok_or(OperationError::NoMatchingEntries))
1920 .map_err(|e| {
1921 request_error!(error = ?e);
1922 e
1923 })?;
1924
1925 self.qs_write.modify_apply(mp).map_err(|e| {
1930 request_error!(error = ?e);
1931 e
1932 })?;
1933
1934 Ok(())
1935 }
1936
1937 pub fn set_unix_account_password(
1938 &mut self,
1939 pce: &UnixPasswordChangeEvent,
1940 ) -> Result<(), OperationError> {
1941 let account = self
1943 .qs_write
1944 .internal_search_uuid(pce.target)
1945 .and_then(|account_entry| {
1946 Account::try_from_entry_rw(&account_entry, &mut self.qs_write)
1948 })
1949 .map_err(|e| {
1950 admin_error!("Failed to start set unix account password {:?}", e);
1951 e
1952 })?;
1953
1954 if account.unix_extn().is_none() {
1956 return Err(OperationError::MissingClass(
1957 ENTRYCLASS_POSIX_ACCOUNT.into(),
1958 ));
1959 }
1960
1961 if account.is_anonymous() {
1963 trace!("Unable to use anonymous to change UNIX account password");
1964 return Err(OperationError::SystemProtectedObject);
1965 }
1966
1967 let timestamp = self.qs_write.get_curtime_odt();
1968
1969 let modlist = gen_password_mod(pce.cleartext.as_str(), self.crypto_policy, timestamp)
1970 .map_err(|e| {
1971 admin_error!(?e, "Unable to generate password change modlist");
1972 e
1973 })?;
1974 trace!(?modlist, "processing change");
1975
1976 let me = self
1979 .qs_write
1980 .impersonate_modify_gen_event(
1981 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(pce.target))),
1983 &filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(pce.target))),
1985 &modlist,
1986 &pce.ident,
1987 )
1988 .map_err(|e| {
1989 request_error!(error = ?e);
1990 e
1991 })?;
1992
1993 let mp = self
1994 .qs_write
1995 .modify_pre_apply(&me)
1996 .and_then(|opt_mp| opt_mp.ok_or(OperationError::NoMatchingEntries))
1997 .map_err(|e| {
1998 request_error!(error = ?e);
1999 e
2000 })?;
2001
2002 self.check_password_quality(pce.cleartext.as_str(), account.related_inputs().as_slice())
2006 .map_err(|e| {
2007 admin_error!(?e, "Failed to checked password quality");
2008 e
2009 })?;
2010
2011 self.qs_write.modify_apply(mp).map_err(|e| {
2013 request_error!(error = ?e);
2014 e
2015 })?;
2016
2017 Ok(())
2018 }
2019
2020 #[instrument(level = "debug", skip_all)]
2021 pub fn recover_account(
2022 &mut self,
2023 name: &str,
2024 cleartext: Option<&str>,
2025 ) -> Result<String, OperationError> {
2026 let target = self.qs_write.name_to_uuid(name).inspect_err(|err| {
2028 error!(?err, "name to uuid failed");
2029 })?;
2030
2031 let cleartext = cleartext
2032 .map(|s| s.to_string())
2033 .unwrap_or_else(password_from_random);
2034
2035 let timestamp = self.qs_write.get_curtime_odt();
2036 let ncred =
2037 Credential::new_generatedpassword_only(self.crypto_policy, &cleartext, timestamp)
2038 .inspect_err(|err| {
2039 error!(?err, "unable to generate password modification");
2040 })?;
2041 let vcred = Value::new_credential("primary", ncred);
2042 let v_valid_from = Value::new_datetime_epoch(self.qs_write.get_curtime());
2043
2044 let modlist = ModifyList::new_list(vec![
2045 m_purge(Attribute::AccountExpire),
2047 m_purge(Attribute::AccountValidFrom),
2048 Modify::Present(Attribute::AccountValidFrom, v_valid_from),
2049 m_purge(Attribute::PassKeys),
2051 m_purge(Attribute::PrimaryCredential),
2052 Modify::Present(Attribute::PrimaryCredential, vcred),
2053 ]);
2054
2055 trace!(?modlist, "processing change");
2056
2057 self.qs_write
2058 .internal_modify(
2059 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(target))),
2061 &modlist,
2062 )
2063 .inspect_err(|err| {
2064 error!(?err);
2065 })?;
2066
2067 Ok(cleartext)
2068 }
2069
2070 #[instrument(level = "debug", skip(self))]
2071 pub fn disable_account(&mut self, name: &str) -> Result<(), OperationError> {
2072 let target = self.qs_write.name_to_uuid(name).inspect_err(|err| {
2074 error!(?err, "name to uuid failed");
2075 })?;
2076
2077 let v_expire = Value::new_datetime_epoch(self.qs_write.get_curtime());
2078
2079 let modlist = ModifyList::new_list(vec![
2080 m_purge(Attribute::AccountValidFrom),
2082 m_purge(Attribute::AccountExpire),
2083 Modify::Present(Attribute::AccountExpire, v_expire),
2084 ]);
2085
2086 trace!(?modlist, "processing change");
2087
2088 self.qs_write
2089 .internal_modify(
2090 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(target))),
2092 &modlist,
2093 )
2094 .inspect_err(|err| {
2095 error!(?err);
2096 })?;
2097
2098 Ok(())
2099 }
2100
2101 #[instrument(level = "debug", skip_all)]
2102 pub fn regenerate_radius_secret(
2103 &mut self,
2104 rrse: &RegenerateRadiusSecretEvent,
2105 ) -> Result<String, OperationError> {
2106 let account = self.target_to_account(rrse.target)?;
2107
2108 let cleartext = readable_password_from_random();
2111
2112 let modlist = account
2114 .regenerate_radius_secret_mod(cleartext.as_str())
2115 .map_err(|e| {
2116 admin_error!("Unable to generate radius secret mod {:?}", e);
2117 e
2118 })?;
2119 trace!(?modlist, "processing change");
2120
2121 self.qs_write
2123 .impersonate_modify(
2124 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(rrse.target))),
2126 &filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(rrse.target))),
2128 &modlist,
2129 &rrse.ident,
2131 )
2132 .map_err(|e| {
2133 request_error!(error = ?e);
2134 e
2135 })
2136 .map(|_| cleartext)
2137 }
2138
2139 #[instrument(level = "debug", skip_all)]
2141 fn process_pwupgrade(&mut self, pwu: &PasswordUpgrade) -> Result<(), OperationError> {
2142 let account = self.target_to_account(pwu.target_uuid)?;
2144
2145 info!(session_id = %pwu.target_uuid, "Processing password hash upgrade");
2146
2147 let maybe_modlist = account
2148 .gen_password_upgrade_mod(pwu.existing_password.as_str(), self.crypto_policy)
2149 .map_err(|e| {
2150 admin_error!("Unable to generate password mod {:?}", e);
2151 e
2152 })?;
2153
2154 if let Some(modlist) = maybe_modlist {
2155 self.qs_write.internal_modify(
2156 &filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(pwu.target_uuid))),
2157 &modlist,
2158 )
2159 } else {
2160 Ok(())
2162 }
2163 }
2164
2165 #[instrument(level = "debug", skip_all)]
2166 fn process_unixpwupgrade(&mut self, pwu: &UnixPasswordUpgrade) -> Result<(), OperationError> {
2167 info!(session_id = %pwu.target_uuid, "Processing unix password hash upgrade");
2168
2169 let account = self
2170 .qs_write
2171 .internal_search_uuid(pwu.target_uuid)
2172 .and_then(|account_entry| {
2173 Account::try_from_entry_rw(&account_entry, &mut self.qs_write)
2174 })
2175 .map_err(|e| {
2176 admin_error!("Failed to start unix pw upgrade -> {:?}", e);
2177 e
2178 })?;
2179
2180 let cred = match account.unix_extn() {
2181 Some(ue) => ue.ucred(),
2182 None => {
2183 return Err(OperationError::MissingClass(
2184 ENTRYCLASS_POSIX_ACCOUNT.into(),
2185 ));
2186 }
2187 };
2188
2189 let Some(cred) = cred else {
2191 return Ok(());
2192 };
2193
2194 let maybe_modlist =
2195 gen_password_upgrade_mod(cred, pwu.existing_password.as_str(), self.crypto_policy)?;
2196
2197 match maybe_modlist {
2198 Some(modlist) => self.qs_write.internal_modify(
2199 &filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(pwu.target_uuid))),
2200 &modlist,
2201 ),
2202 None => Ok(()),
2203 }
2204 }
2205
2206 #[instrument(level = "debug", skip_all)]
2207 pub(crate) fn process_webauthncounterinc(
2208 &mut self,
2209 wci: &WebauthnCounterIncrement,
2210 ) -> Result<(), OperationError> {
2211 info!(session_id = %wci.target_uuid, "Processing webauthn counter increment");
2212
2213 let mut account = self.target_to_account(wci.target_uuid)?;
2214
2215 let opt_modlist = account
2217 .gen_webauthn_counter_mod(&wci.auth_result)
2218 .map_err(|e| {
2219 admin_error!("Unable to generate webauthn counter mod {:?}", e);
2220 e
2221 })?;
2222
2223 if let Some(modlist) = opt_modlist {
2224 self.qs_write.internal_modify(
2225 &filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(wci.target_uuid))),
2226 &modlist,
2227 )
2228 } else {
2229 trace!("No modification required");
2231 Ok(())
2232 }
2233 }
2234
2235 #[instrument(level = "debug", skip_all)]
2236 pub(crate) fn process_backupcoderemoval(
2237 &mut self,
2238 bcr: &BackupCodeRemoval,
2239 ) -> Result<(), OperationError> {
2240 info!(session_id = %bcr.target_uuid, "Processing backup code removal");
2241
2242 let account = self.target_to_account(bcr.target_uuid)?;
2243 let modlist = account
2245 .invalidate_backup_code_mod(&bcr.code_to_remove)
2246 .map_err(|e| {
2247 admin_error!("Unable to generate backup code mod {:?}", e);
2248 e
2249 })?;
2250
2251 self.qs_write.internal_modify(
2252 &filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(bcr.target_uuid))),
2253 &modlist,
2254 )
2255 }
2256
2257 #[instrument(level = "debug", skip_all)]
2258 pub(crate) fn process_authsessionrecord(
2259 &mut self,
2260 asr: &AuthSessionRecord,
2261 ) -> Result<(), OperationError> {
2262 let state = match asr.expiry {
2264 Some(e) => SessionState::ExpiresAt(e),
2265 None => SessionState::NeverExpires,
2266 };
2267
2268 let session = Value::Session(
2269 asr.session_id,
2270 Session {
2271 label: asr.label.clone(),
2272 state,
2273 issued_at: asr.issued_at,
2276 issued_by: asr.issued_by.clone(),
2278 cred_id: asr.cred_id,
2280 scope: asr.scope,
2283 type_: asr.type_,
2284 ext_metadata: Default::default(),
2285 },
2286 );
2287
2288 info!(session_id = %asr.session_id, "Persisting auth session");
2289
2290 let modlist = ModifyList::new_append(Attribute::UserAuthTokenSession, session);
2292
2293 self.qs_write
2294 .internal_modify(
2295 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(asr.target_uuid))),
2296 &modlist,
2297 )
2298 .map_err(|e| {
2299 admin_error!("Failed to persist user auth token {:?}", e);
2300 e
2301 })
2302 }
2304
2305 #[instrument(level = "debug", skip_all)]
2306 pub fn process_delayedaction(
2307 &mut self,
2308 da: &DelayedAction,
2309 _ct: Duration,
2310 ) -> Result<(), OperationError> {
2311 match da {
2312 DelayedAction::PwUpgrade(pwu) => self.process_pwupgrade(pwu),
2313 DelayedAction::UnixPwUpgrade(upwu) => self.process_unixpwupgrade(upwu),
2314 DelayedAction::WebauthnCounterIncrement(wci) => self.process_webauthncounterinc(wci),
2315 DelayedAction::BackupCodeRemoval(bcr) => self.process_backupcoderemoval(bcr),
2316 DelayedAction::AuthSessionRecord(asr) => self.process_authsessionrecord(asr),
2317 }
2318 }
2319
2320 fn reload_applications(&mut self) -> Result<(), OperationError> {
2321 self.qs_write
2322 .get_applications_set()
2323 .and_then(|application_set| self.applications.reload(application_set))
2324 }
2325
2326 fn reload_oauth2(&mut self) -> Result<(), OperationError> {
2327 let domain_level = self.qs_write.get_domain_version();
2328 self.qs_write.get_oauth2rs_set().and_then(|oauth2rs_set| {
2329 let key_providers = self.qs_write.get_key_providers();
2330 self.oauth2rs
2331 .reload(oauth2rs_set, key_providers, domain_level)
2332 })?;
2333 self.qs_write.clear_changed_oauth2();
2335 Ok(())
2336 }
2337
2338 #[instrument(level = "debug", skip_all)]
2339 pub fn commit(mut self) -> Result<(), OperationError> {
2340 self.qs_write.reload()?;
2343
2344 if self.qs_write.get_changed_app() {
2346 self.reload_applications()?;
2347 }
2348
2349 if self.qs_write.get_changed_oauth2() {
2350 self.reload_oauth2()?;
2351 }
2352
2353 if self.qs_write.get_changed_oauth2_client() {
2354 self.reload_oauth2_client_providers()?;
2355 }
2356
2357 self.applications.commit();
2359 self.oauth2rs.commit();
2360 self.cred_update_sessions.commit();
2361 self.oauth2_client_providers.commit();
2362
2363 trace!("cred_update_session.commit");
2364 self.qs_write.commit()
2365 }
2366}
2367
2368#[cfg(test)]
2371mod tests {
2372 use std::convert::TryFrom;
2373 use std::time::Duration;
2374
2375 use crate::credential::{Credential, Password};
2376 use crate::idm::account::DestroySessionTokenEvent;
2377 use crate::idm::accountpolicy::ResolvedAccountPolicy;
2378 use crate::idm::audit::AuditEvent;
2379 use crate::idm::authentication::AuthState;
2380 use crate::idm::delayed::{AuthSessionRecord, DelayedAction};
2381 use crate::idm::event::{AuthEvent, AuthResult};
2382 use crate::idm::event::{
2383 LdapAuthEvent, PasswordChangeEvent, RadiusAuthTokenEvent, RegenerateRadiusSecretEvent,
2384 UnixGroupTokenEvent, UnixPasswordChangeEvent, UnixUserAuthEvent, UnixUserTokenEvent,
2385 };
2386 use crate::idm::server::{IdmServer, IdmServerTransaction, Token};
2387 use crate::modify::{Modify, ModifyList};
2388 use crate::prelude::*;
2389 use crate::server::keys::KeyProvidersTransaction;
2390 use crate::value::{AuthType, SessionState};
2391 use compact_jwt::{traits::JwsVerifiable, JwsCompact, JwsEs256Verifier, JwsVerifier};
2392 use kanidm_lib_crypto::CryptoPolicy;
2393 use kanidm_proto::v1::{AuthAllowed, AuthIssueSession, AuthMech};
2394 use time::OffsetDateTime;
2395 use uuid::Uuid;
2396
2397 const TEST_PASSWORD: &str = "ntaoeuntnaoeuhraohuercahu😍";
2398 const TEST_PASSWORD_INC: &str = "ntaoentu nkrcgaeunhibwmwmqj;k wqjbkx ";
2399 const TEST_CURRENT_TIME: u64 = 6000;
2400
2401 #[idm_test]
2402 async fn test_idm_anonymous_auth(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
2403 let mut idms_auth = idms.auth().await.unwrap();
2405 let anon_init = AuthEvent::anonymous_init();
2407 let r1 = idms_auth
2409 .auth(
2410 &anon_init,
2411 Duration::from_secs(TEST_CURRENT_TIME),
2412 Source::Internal.into(),
2413 )
2414 .await;
2415 let sid = match r1 {
2418 Ok(ar) => {
2419 let AuthResult { sessionid, state } = ar;
2420 match state {
2421 AuthState::Choose(mut conts) => {
2422 assert_eq!(conts.len(), 1);
2424 let m = conts.pop().expect("Should not fail");
2426 assert_eq!(m, AuthMech::Anonymous);
2427 }
2428 _ => {
2429 error!("A critical error has occurred! We have a non-continue result!");
2430 panic!();
2431 }
2432 };
2433 sessionid
2435 }
2436 Err(e) => {
2437 error!("A critical error has occurred! {:?}", e);
2439 panic!();
2440 }
2441 };
2442
2443 debug!("sessionid is ==> {:?}", sid);
2444
2445 idms_auth.commit().expect("Must not fail");
2446
2447 let mut idms_auth = idms.auth().await.unwrap();
2448 let anon_begin = AuthEvent::begin_mech(sid, AuthMech::Anonymous);
2449
2450 let r2 = idms_auth
2451 .auth(
2452 &anon_begin,
2453 Duration::from_secs(TEST_CURRENT_TIME),
2454 Source::Internal.into(),
2455 )
2456 .await;
2457 debug!("r2 ==> {:?}", r2);
2458
2459 match r2 {
2460 Ok(ar) => {
2461 let AuthResult {
2462 sessionid: _,
2463 state,
2464 } = ar;
2465
2466 match state {
2467 AuthState::Continue(allowed) => {
2468 assert_eq!(allowed.len(), 1);
2470 assert_eq!(allowed.first(), Some(&AuthAllowed::Anonymous));
2471 }
2472 _ => {
2473 error!("A critical error has occurred! We have a non-continue result!");
2474 panic!();
2475 }
2476 }
2477 }
2478 Err(e) => {
2479 error!("A critical error has occurred! {:?}", e);
2480 panic!();
2482 }
2483 };
2484
2485 idms_auth.commit().expect("Must not fail");
2486
2487 let mut idms_auth = idms.auth().await.unwrap();
2488 let anon_step = AuthEvent::cred_step_anonymous(sid);
2490
2491 let r2 = idms_auth
2493 .auth(
2494 &anon_step,
2495 Duration::from_secs(TEST_CURRENT_TIME),
2496 Source::Internal.into(),
2497 )
2498 .await;
2499 debug!("r2 ==> {:?}", r2);
2500
2501 match r2 {
2502 Ok(ar) => {
2503 let AuthResult {
2504 sessionid: _,
2505 state,
2506 } = ar;
2507
2508 match state {
2509 AuthState::Success(_uat, AuthIssueSession::Token) => {
2510 }
2512 _ => {
2513 error!("A critical error has occurred! We have a non-success result!");
2514 panic!();
2515 }
2516 }
2517 }
2518 Err(e) => {
2519 error!("A critical error has occurred! {:?}", e);
2520 panic!();
2522 }
2523 };
2524
2525 idms_auth.commit().expect("Must not fail");
2526 }
2527
2528 #[idm_test]
2530 async fn test_idm_anonymous_auth_invalid_states(
2531 idms: &IdmServer,
2532 _idms_delayed: &IdmServerDelayed,
2533 ) {
2534 {
2535 let mut idms_auth = idms.auth().await.unwrap();
2536 let sid = Uuid::new_v4();
2537 let anon_step = AuthEvent::cred_step_anonymous(sid);
2538
2539 let r2 = idms_auth
2541 .auth(
2542 &anon_step,
2543 Duration::from_secs(TEST_CURRENT_TIME),
2544 Source::Internal.into(),
2545 )
2546 .await;
2547 debug!("r2 ==> {:?}", r2);
2548
2549 match r2 {
2550 Ok(_) => {
2551 error!("Auth state machine not correctly enforced!");
2552 panic!();
2553 }
2554 Err(e) => match e {
2555 OperationError::InvalidSessionState => {}
2556 _ => panic!(),
2557 },
2558 };
2559 }
2560 }
2561
2562 async fn init_testperson_w_password(
2563 idms: &IdmServer,
2564 pw: &str,
2565 ) -> Result<Uuid, OperationError> {
2566 let p = CryptoPolicy::minimum();
2567 let cred = Credential::new_password_only(&p, pw, OffsetDateTime::UNIX_EPOCH)?;
2568 let cred_id = cred.uuid;
2569 let v_cred = Value::new_credential("primary", cred);
2570 let mut idms_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
2571
2572 idms_write
2573 .qs_write
2574 .internal_create(vec![E_TESTPERSON_1.clone()])
2575 .expect("Failed to create test person");
2576
2577 let me_inv_m = ModifyEvent::new_internal_invalid(
2579 filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_TESTPERSON_1))),
2580 ModifyList::new_list(vec![Modify::Present(Attribute::PrimaryCredential, v_cred)]),
2581 );
2582 assert!(idms_write.qs_write.modify(&me_inv_m).is_ok());
2584
2585 idms_write.commit().map(|()| cred_id)
2586 }
2587
2588 async fn init_authsession_sid(idms: &IdmServer, ct: Duration, name: &str) -> Uuid {
2589 let mut idms_auth = idms.auth().await.unwrap();
2590 let admin_init = AuthEvent::named_init(name);
2591
2592 let r1 = idms_auth
2593 .auth(&admin_init, ct, Source::Internal.into())
2594 .await;
2595 let ar = r1.unwrap();
2596 let AuthResult { sessionid, state } = ar;
2597
2598 assert!(matches!(state, AuthState::Choose(_)));
2599
2600 let admin_begin = AuthEvent::begin_mech(sessionid, AuthMech::Password);
2602
2603 let r2 = idms_auth
2604 .auth(&admin_begin, ct, Source::Internal.into())
2605 .await;
2606 let ar = r2.unwrap();
2607 let AuthResult { sessionid, state } = ar;
2608
2609 match state {
2610 AuthState::Continue(_) => {}
2611 s => {
2612 error!(?s, "Sessions was not initialised");
2613 panic!();
2614 }
2615 };
2616
2617 idms_auth.commit().expect("Must not fail");
2618
2619 sessionid
2620 }
2621
2622 async fn check_testperson_password(idms: &IdmServer, pw: &str, ct: Duration) -> JwsCompact {
2623 let sid = init_authsession_sid(idms, ct, "testperson1").await;
2624
2625 let mut idms_auth = idms.auth().await.unwrap();
2626 let anon_step = AuthEvent::cred_step_password(sid, pw);
2627
2628 let r2 = idms_auth
2630 .auth(&anon_step, ct, Source::Internal.into())
2631 .await;
2632 debug!("r2 ==> {:?}", r2);
2633
2634 let token = match r2 {
2635 Ok(ar) => {
2636 let AuthResult {
2637 sessionid: _,
2638 state,
2639 } = ar;
2640
2641 match state {
2642 AuthState::Success(token, AuthIssueSession::Token) => {
2643 token
2645 }
2646 _ => {
2647 error!("A critical error has occurred! We have a non-success result!");
2648 panic!();
2649 }
2650 }
2651 }
2652 Err(e) => {
2653 error!("A critical error has occurred! {:?}", e);
2654 panic!();
2656 }
2657 };
2658
2659 idms_auth.commit().expect("Must not fail");
2660
2661 *token
2662 }
2663
2664 #[idm_test]
2665 async fn test_idm_simple_password_auth(idms: &IdmServer, idms_delayed: &mut IdmServerDelayed) {
2666 let ct = duration_from_epoch_now();
2667 init_testperson_w_password(idms, TEST_PASSWORD)
2668 .await
2669 .expect("Failed to setup admin account");
2670 check_testperson_password(idms, TEST_PASSWORD, ct).await;
2671
2672 let da = idms_delayed.try_recv().expect("invalid");
2674 assert!(matches!(da, DelayedAction::AuthSessionRecord(_)));
2675 idms_delayed.check_is_empty_or_panic();
2676 }
2677
2678 #[idm_test]
2679 async fn test_idm_simple_password_spn_auth(
2680 idms: &IdmServer,
2681 idms_delayed: &mut IdmServerDelayed,
2682 ) {
2683 init_testperson_w_password(idms, TEST_PASSWORD)
2684 .await
2685 .expect("Failed to setup admin account");
2686
2687 let sid = init_authsession_sid(
2688 idms,
2689 Duration::from_secs(TEST_CURRENT_TIME),
2690 "testperson1@example.com",
2691 )
2692 .await;
2693
2694 let mut idms_auth = idms.auth().await.unwrap();
2695 let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD);
2696
2697 let r2 = idms_auth
2699 .auth(
2700 &anon_step,
2701 Duration::from_secs(TEST_CURRENT_TIME),
2702 Source::Internal.into(),
2703 )
2704 .await;
2705 debug!("r2 ==> {:?}", r2);
2706
2707 match r2 {
2708 Ok(ar) => {
2709 let AuthResult {
2710 sessionid: _,
2711 state,
2712 } = ar;
2713 match state {
2714 AuthState::Success(_uat, AuthIssueSession::Token) => {
2715 }
2717 _ => {
2718 error!("A critical error has occurred! We have a non-success result!");
2719 panic!();
2720 }
2721 }
2722 }
2723 Err(e) => {
2724 error!("A critical error has occurred! {:?}", e);
2725 panic!();
2727 }
2728 };
2729
2730 let da = idms_delayed.try_recv().expect("invalid");
2732 assert!(matches!(da, DelayedAction::AuthSessionRecord(_)));
2733 idms_delayed.check_is_empty_or_panic();
2734
2735 idms_auth.commit().expect("Must not fail");
2736 }
2737
2738 #[idm_test(audit = 1)]
2739 async fn test_idm_simple_password_invalid(
2740 idms: &IdmServer,
2741 _idms_delayed: &IdmServerDelayed,
2742 idms_audit: &mut IdmServerAudit,
2743 ) {
2744 init_testperson_w_password(idms, TEST_PASSWORD)
2745 .await
2746 .expect("Failed to setup admin account");
2747 let sid =
2748 init_authsession_sid(idms, Duration::from_secs(TEST_CURRENT_TIME), "testperson1").await;
2749 let mut idms_auth = idms.auth().await.unwrap();
2750 let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD_INC);
2751
2752 let r2 = idms_auth
2754 .auth(
2755 &anon_step,
2756 Duration::from_secs(TEST_CURRENT_TIME),
2757 Source::Internal.into(),
2758 )
2759 .await;
2760 debug!("r2 ==> {:?}", r2);
2761
2762 match r2 {
2763 Ok(ar) => {
2764 let AuthResult {
2765 sessionid: _,
2766 state,
2767 } = ar;
2768 match state {
2769 AuthState::Denied(_reason) => {
2770 }
2772 _ => {
2773 error!("A critical error has occurred! We have a non-denied result!");
2774 panic!();
2775 }
2776 }
2777 }
2778 Err(e) => {
2779 error!("A critical error has occurred! {:?}", e);
2780 panic!();
2782 }
2783 };
2784
2785 match idms_audit.audit_rx().try_recv() {
2787 Ok(AuditEvent::AuthenticationDenied { .. }) => {}
2788 _ => panic!("Oh no"),
2789 }
2790
2791 idms_auth.commit().expect("Must not fail");
2792 }
2793
2794 #[idm_test]
2795 async fn test_idm_simple_password_reset(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
2796 let pce = PasswordChangeEvent::new_internal(UUID_ADMIN, TEST_PASSWORD);
2797
2798 let ct = duration_from_epoch_now();
2799
2800 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
2801 assert!(idms_prox_write
2802 .set_account_password(&pce, OffsetDateTime::UNIX_EPOCH + ct)
2803 .is_ok());
2804 assert!(idms_prox_write
2805 .set_account_password(&pce, OffsetDateTime::UNIX_EPOCH + ct)
2806 .is_ok());
2807 assert!(idms_prox_write.commit().is_ok());
2808 }
2809
2810 #[idm_test]
2811 async fn test_idm_anonymous_set_password_denied(
2812 idms: &IdmServer,
2813 _idms_delayed: &IdmServerDelayed,
2814 ) {
2815 let pce = PasswordChangeEvent::new_internal(UUID_ANONYMOUS, TEST_PASSWORD);
2816
2817 let ct = duration_from_epoch_now();
2818
2819 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
2820 assert!(idms_prox_write
2821 .set_account_password(&pce, OffsetDateTime::UNIX_EPOCH + ct)
2822 .is_err());
2823 assert!(idms_prox_write.commit().is_ok());
2824 }
2825
2826 #[idm_test]
2827 async fn test_idm_regenerate_radius_secret(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
2828 let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
2829
2830 idms_prox_write
2831 .qs_write
2832 .internal_create(vec![E_TESTPERSON_1.clone()])
2833 .expect("unable to create test person");
2834
2835 let rrse = RegenerateRadiusSecretEvent::new_internal(UUID_TESTPERSON_1);
2836
2837 let r1 = idms_prox_write
2839 .regenerate_radius_secret(&rrse)
2840 .expect("Failed to reset radius credential 1");
2841 let r2 = idms_prox_write
2843 .regenerate_radius_secret(&rrse)
2844 .expect("Failed to reset radius credential 2");
2845 assert!(r1 != r2);
2846 }
2847
2848 #[idm_test]
2849 async fn test_idm_radiusauthtoken(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
2850 let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
2851
2852 idms_prox_write
2853 .qs_write
2854 .internal_create(vec![E_TESTPERSON_1.clone()])
2855 .expect("unable to create test person");
2856
2857 let rrse = RegenerateRadiusSecretEvent::new_internal(UUID_TESTPERSON_1);
2858 let r1 = idms_prox_write
2859 .regenerate_radius_secret(&rrse)
2860 .expect("Failed to reset radius credential 1");
2861 idms_prox_write.commit().expect("failed to commit");
2862
2863 let mut idms_prox_read = idms.proxy_read().await.unwrap();
2864 let person_entry = idms_prox_read
2865 .qs_read
2866 .internal_search_uuid(UUID_TESTPERSON_1)
2867 .expect("Can't access admin entry.");
2868
2869 let rate = RadiusAuthTokenEvent::new_impersonate(person_entry, UUID_TESTPERSON_1);
2870 let tok_r = idms_prox_read
2871 .get_radiusauthtoken(&rate, duration_from_epoch_now())
2872 .expect("Failed to generate radius auth token");
2873
2874 assert_eq!(r1, tok_r.secret);
2876 }
2877
2878 #[idm_test]
2879 async fn test_idm_unixusertoken(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
2880 let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
2881 let me_posix = ModifyEvent::new_internal_invalid(
2883 filter!(f_eq(Attribute::Name, PartialValue::new_iname("admin"))),
2884 ModifyList::new_list(vec![
2885 Modify::Present(Attribute::Class, EntryClass::PosixAccount.into()),
2886 Modify::Present(Attribute::GidNumber, Value::new_uint32(2001)),
2887 ]),
2888 );
2889 assert!(idms_prox_write.qs_write.modify(&me_posix).is_ok());
2890 let e: Entry<EntryInit, EntryNew> = entry_init!(
2892 (Attribute::Class, EntryClass::Object.to_value()),
2893 (Attribute::Class, EntryClass::Group.to_value()),
2894 (Attribute::Class, EntryClass::PosixGroup.to_value()),
2895 (Attribute::Name, Value::new_iname("testgroup")),
2896 (
2897 Attribute::Uuid,
2898 Value::Uuid(uuid::uuid!("01609135-a1c4-43d5-966b-a28227644445"))
2899 ),
2900 (Attribute::Description, Value::new_utf8s("testgroup")),
2901 (
2902 Attribute::Member,
2903 Value::Refer(uuid::uuid!("00000000-0000-0000-0000-000000000000"))
2904 )
2905 );
2906
2907 let ce = CreateEvent::new_internal(vec![e]);
2908
2909 assert!(idms_prox_write.qs_write.create(&ce).is_ok());
2910
2911 idms_prox_write.commit().expect("failed to commit");
2912
2913 let mut idms_prox_read = idms.proxy_read().await.unwrap();
2914
2915 let idm_admin_entry = idms_prox_read
2917 .qs_read
2918 .internal_search_uuid(UUID_IDM_ADMIN)
2919 .expect("Can't access admin entry.");
2920
2921 let ugte = UnixGroupTokenEvent::new_impersonate(
2922 idm_admin_entry.clone(),
2923 uuid!("01609135-a1c4-43d5-966b-a28227644445"),
2924 );
2925 let tok_g = idms_prox_read
2926 .get_unixgrouptoken(&ugte)
2927 .expect("Failed to generate unix group token");
2928
2929 assert_eq!(tok_g.name, "testgroup");
2930 assert_eq!(tok_g.spn, "testgroup@example.com");
2931
2932 let uute = UnixUserTokenEvent::new_internal(UUID_ADMIN);
2933 let tok_r = idms_prox_read
2934 .get_unixusertoken(&uute, duration_from_epoch_now())
2935 .expect("Failed to generate unix user token");
2936
2937 assert_eq!(tok_r.name, "admin");
2938 assert_eq!(tok_r.spn, "admin@example.com");
2939 assert_eq!(tok_r.groups.len(), 2);
2940 assert_eq!(tok_r.groups[0].name, "admin");
2941 assert_eq!(tok_r.groups[1].name, "testgroup");
2942 assert!(tok_r.valid);
2943
2944 let ugte = UnixGroupTokenEvent::new_impersonate(
2946 idm_admin_entry,
2947 uuid!("00000000-0000-0000-0000-000000000000"),
2948 );
2949 let tok_g = idms_prox_read
2950 .get_unixgrouptoken(&ugte)
2951 .expect("Failed to generate unix group token");
2952
2953 assert_eq!(tok_g.name, "admin");
2954 assert_eq!(tok_g.spn, "admin@example.com");
2955 }
2956
2957 #[idm_test]
2958 async fn test_idm_simple_unix_password_reset(
2959 idms: &IdmServer,
2960 _idms_delayed: &IdmServerDelayed,
2961 ) {
2962 let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
2963 let me_posix = ModifyEvent::new_internal_invalid(
2965 filter!(f_eq(Attribute::Name, PartialValue::new_iname("admin"))),
2966 ModifyList::new_list(vec![
2967 Modify::Present(Attribute::Class, EntryClass::PosixAccount.into()),
2968 Modify::Present(Attribute::GidNumber, Value::new_uint32(2001)),
2969 ]),
2970 );
2971 assert!(idms_prox_write.qs_write.modify(&me_posix).is_ok());
2972
2973 let pce = UnixPasswordChangeEvent::new_internal(UUID_ADMIN, TEST_PASSWORD);
2974
2975 assert!(idms_prox_write.set_unix_account_password(&pce).is_ok());
2976 assert!(idms_prox_write.commit().is_ok());
2977
2978 let mut idms_auth = idms.auth().await.unwrap();
2979 let uuae_good = UnixUserAuthEvent::new_internal(UUID_ADMIN, TEST_PASSWORD);
2982 let a1 = idms_auth
2983 .auth_unix(&uuae_good, Duration::from_secs(TEST_CURRENT_TIME))
2984 .await;
2985 match a1 {
2986 Ok(Some(_tok)) => {}
2987 _ => panic!("Oh no"),
2988 };
2989 let uuae_bad = UnixUserAuthEvent::new_internal(UUID_ADMIN, TEST_PASSWORD_INC);
2991 let a2 = idms_auth
2992 .auth_unix(&uuae_bad, Duration::from_secs(TEST_CURRENT_TIME))
2993 .await;
2994 match a2 {
2995 Ok(None) => {}
2996 _ => panic!("Oh no"),
2997 };
2998 assert!(idms_auth.commit().is_ok());
2999
3000 let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
3002 let me_purge_up = ModifyEvent::new_internal_invalid(
3003 filter!(f_eq(Attribute::Name, PartialValue::new_iname("admin"))),
3004 ModifyList::new_list(vec![Modify::Purged(Attribute::UnixPassword)]),
3005 );
3006 assert!(idms_prox_write.qs_write.modify(&me_purge_up).is_ok());
3007 assert!(idms_prox_write.commit().is_ok());
3008
3009 let mut idms_auth = idms.auth().await.unwrap();
3012 let a3 = idms_auth
3013 .auth_unix(&uuae_good, Duration::from_secs(TEST_CURRENT_TIME))
3014 .await;
3015 match a3 {
3016 Ok(None) => {}
3017 _ => panic!("Oh no"),
3018 };
3019 assert!(idms_auth.commit().is_ok());
3020 }
3021
3022 #[idm_test]
3023 async fn test_idm_simple_password_upgrade(
3024 idms: &IdmServer,
3025 idms_delayed: &mut IdmServerDelayed,
3026 ) {
3027 let ct = duration_from_epoch_now();
3028 idms_delayed.check_is_empty_or_panic();
3030 {
3032 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3033 idms_prox_write
3036 .qs_write
3037 .internal_create(vec![E_TESTPERSON_1.clone()])
3038 .expect("Failed to create test person");
3039
3040 let me_inv_m =
3041 ModifyEvent::new_internal_invalid(
3042 filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_TESTPERSON_1))),
3043 ModifyList::new_list(vec![Modify::Present(
3044 Attribute::PasswordImport,
3045 Value::from("{SSHA512}JwrSUHkI7FTAfHRVR6KoFlSN0E3dmaQWARjZ+/UsShYlENOqDtFVU77HJLLrY2MuSp0jve52+pwtdVl2QUAHukQ0XUf5LDtM")
3046 )]),
3047 );
3048 assert!(idms_prox_write.qs_write.modify(&me_inv_m).is_ok());
3050 assert!(idms_prox_write.commit().is_ok());
3051 }
3052 idms_delayed.check_is_empty_or_panic();
3054
3055 let mut idms_prox_read = idms.proxy_read().await.unwrap();
3056 let person_entry = idms_prox_read
3057 .qs_read
3058 .internal_search_uuid(UUID_TESTPERSON_1)
3059 .expect("Can't access admin entry.");
3060 let cred_before = person_entry
3061 .get_ava_single_credential(Attribute::PrimaryCredential)
3062 .expect("No credential present")
3063 .clone();
3064 drop(idms_prox_read);
3065
3066 check_testperson_password(idms, "password", ct).await;
3068
3069 let da = idms_delayed.try_recv().expect("invalid");
3078 assert!(matches!(da, DelayedAction::PwUpgrade(_)));
3080 let r = idms.delayed_action(duration_from_epoch_now(), da).await;
3081 let da = idms_delayed.try_recv().expect("invalid");
3083 assert!(matches!(da, DelayedAction::AuthSessionRecord(_)));
3084 assert_eq!(Ok(true), r);
3085
3086 let mut idms_prox_read = idms.proxy_read().await.unwrap();
3087 let person_entry = idms_prox_read
3088 .qs_read
3089 .internal_search_uuid(UUID_TESTPERSON_1)
3090 .expect("Can't access admin entry.");
3091 let cred_after = person_entry
3092 .get_ava_single_credential(Attribute::PrimaryCredential)
3093 .expect("No credential present")
3094 .clone();
3095 drop(idms_prox_read);
3096
3097 assert_eq!(cred_before.uuid, cred_after.uuid);
3098
3099 check_testperson_password(idms, "password", ct).await;
3101 let da = idms_delayed.try_recv().expect("invalid");
3103 assert!(matches!(da, DelayedAction::AuthSessionRecord(_)));
3104
3105 idms_delayed.check_is_empty_or_panic();
3107 }
3108
3109 #[idm_test]
3110 async fn test_idm_unix_password_upgrade(idms: &IdmServer, idms_delayed: &mut IdmServerDelayed) {
3111 idms_delayed.check_is_empty_or_panic();
3113 let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
3115
3116 let im_pw = "{SSHA512}JwrSUHkI7FTAfHRVR6KoFlSN0E3dmaQWARjZ+/UsShYlENOqDtFVU77HJLLrY2MuSp0jve52+pwtdVl2QUAHukQ0XUf5LDtM";
3117 let pw = Password::try_from(im_pw).expect("failed to parse");
3118 let cred = Credential::new_from_password(pw, OffsetDateTime::UNIX_EPOCH);
3119 let v_cred = Value::new_credential("unix", cred);
3120
3121 let me_posix = ModifyEvent::new_internal_invalid(
3122 filter!(f_eq(Attribute::Name, PartialValue::new_iname("admin"))),
3123 ModifyList::new_list(vec![
3124 Modify::Present(Attribute::Class, EntryClass::PosixAccount.into()),
3125 Modify::Present(Attribute::GidNumber, Value::new_uint32(2001)),
3126 Modify::Present(Attribute::UnixPassword, v_cred),
3127 ]),
3128 );
3129 assert!(idms_prox_write.qs_write.modify(&me_posix).is_ok());
3130 assert!(idms_prox_write.commit().is_ok());
3131 idms_delayed.check_is_empty_or_panic();
3132 let uuae = UnixUserAuthEvent::new_internal(UUID_ADMIN, "password");
3134 let mut idms_auth = idms.auth().await.unwrap();
3135 let a1 = idms_auth
3136 .auth_unix(&uuae, Duration::from_secs(TEST_CURRENT_TIME))
3137 .await;
3138 match a1 {
3139 Ok(Some(_tok)) => {}
3140 _ => panic!("Oh no"),
3141 };
3142 idms_auth.commit().expect("Must not fail");
3143 let da = idms_delayed.try_recv().expect("invalid");
3146 let _r = idms.delayed_action(duration_from_epoch_now(), da).await;
3147 let mut idms_auth = idms.auth().await.unwrap();
3149 let a2 = idms_auth
3150 .auth_unix(&uuae, Duration::from_secs(TEST_CURRENT_TIME))
3151 .await;
3152 match a2 {
3153 Ok(Some(_tok)) => {}
3154 _ => panic!("Oh no"),
3155 };
3156 idms_auth.commit().expect("Must not fail");
3157 idms_delayed.check_is_empty_or_panic();
3159 }
3160
3161 const TEST_NOT_YET_VALID_TIME: u64 = TEST_CURRENT_TIME - 240;
3165 const TEST_VALID_FROM_TIME: u64 = TEST_CURRENT_TIME - 120;
3166 const TEST_EXPIRE_TIME: u64 = TEST_CURRENT_TIME + 120;
3167 const TEST_AFTER_EXPIRY: u64 = TEST_CURRENT_TIME + 240;
3168
3169 async fn set_testperson_valid_time(idms: &IdmServer) {
3170 let mut idms_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
3171
3172 let v_valid_from = Value::new_datetime_epoch(Duration::from_secs(TEST_VALID_FROM_TIME));
3173 let v_expire = Value::new_datetime_epoch(Duration::from_secs(TEST_EXPIRE_TIME));
3174
3175 let me_inv_m = ModifyEvent::new_internal_invalid(
3177 filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_TESTPERSON_1))),
3178 ModifyList::new_list(vec![
3179 Modify::Present(Attribute::AccountExpire, v_expire),
3180 Modify::Present(Attribute::AccountValidFrom, v_valid_from),
3181 ]),
3182 );
3183 assert!(idms_write.qs_write.modify(&me_inv_m).is_ok());
3185
3186 idms_write.commit().expect("Must not fail");
3187 }
3188
3189 #[idm_test]
3190 async fn test_idm_account_valid_from_expire(
3191 idms: &IdmServer,
3192 _idms_delayed: &mut IdmServerDelayed,
3193 ) {
3194 init_testperson_w_password(idms, TEST_PASSWORD)
3197 .await
3198 .expect("Failed to setup admin account");
3199 set_testperson_valid_time(idms).await;
3202
3203 let time_low = Duration::from_secs(TEST_NOT_YET_VALID_TIME);
3204 let time_high = Duration::from_secs(TEST_AFTER_EXPIRY);
3205
3206 let mut idms_auth = idms.auth().await.unwrap();
3207 let admin_init = AuthEvent::named_init("admin");
3208 let r1 = idms_auth
3209 .auth(&admin_init, time_low, Source::Internal.into())
3210 .await;
3211
3212 let ar = r1.unwrap();
3213 let AuthResult {
3214 sessionid: _,
3215 state,
3216 } = ar;
3217
3218 match state {
3219 AuthState::Denied(_) => {}
3220 _ => {
3221 panic!();
3222 }
3223 };
3224
3225 idms_auth.commit().expect("Must not fail");
3226
3227 let mut idms_auth = idms.auth().await.unwrap();
3229 let admin_init = AuthEvent::named_init("admin");
3230 let r1 = idms_auth
3231 .auth(&admin_init, time_high, Source::Internal.into())
3232 .await;
3233
3234 let ar = r1.unwrap();
3235 let AuthResult {
3236 sessionid: _,
3237 state,
3238 } = ar;
3239
3240 match state {
3241 AuthState::Denied(_) => {}
3242 _ => {
3243 panic!();
3244 }
3245 };
3246
3247 idms_auth.commit().expect("Must not fail");
3248 }
3249
3250 #[idm_test]
3251 async fn test_idm_unix_valid_from_expire(
3252 idms: &IdmServer,
3253 _idms_delayed: &mut IdmServerDelayed,
3254 ) {
3255 init_testperson_w_password(idms, TEST_PASSWORD)
3257 .await
3258 .expect("Failed to setup admin account");
3259 set_testperson_valid_time(idms).await;
3260
3261 let time_low = Duration::from_secs(TEST_NOT_YET_VALID_TIME);
3262 let time_high = Duration::from_secs(TEST_AFTER_EXPIRY);
3263
3264 let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
3266 let me_posix = ModifyEvent::new_internal_invalid(
3267 filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_TESTPERSON_1))),
3268 ModifyList::new_list(vec![
3269 Modify::Present(Attribute::Class, EntryClass::PosixAccount.into()),
3270 Modify::Present(Attribute::GidNumber, Value::new_uint32(2001)),
3271 ]),
3272 );
3273 assert!(idms_prox_write.qs_write.modify(&me_posix).is_ok());
3274
3275 let pce = UnixPasswordChangeEvent::new_internal(UUID_TESTPERSON_1, TEST_PASSWORD);
3276
3277 assert!(idms_prox_write.set_unix_account_password(&pce).is_ok());
3278 assert!(idms_prox_write.commit().is_ok());
3279
3280 let mut idms_auth = idms.auth().await.unwrap();
3282 let uuae_good = UnixUserAuthEvent::new_internal(UUID_TESTPERSON_1, TEST_PASSWORD);
3283
3284 let a1 = idms_auth.auth_unix(&uuae_good, time_low).await;
3285 match a1 {
3288 Ok(None) => {}
3289 _ => panic!("Oh no"),
3290 };
3291
3292 let a2 = idms_auth.auth_unix(&uuae_good, time_high).await;
3293 match a2 {
3294 Ok(None) => {}
3295 _ => panic!("Oh no"),
3296 };
3297
3298 idms_auth.commit().expect("Must not fail");
3299 let mut idms_prox_read = idms.proxy_read().await.unwrap();
3301 let uute = UnixUserTokenEvent::new_internal(UUID_TESTPERSON_1);
3302
3303 let tok_r = idms_prox_read
3304 .get_unixusertoken(&uute, time_low)
3305 .expect("Failed to generate unix user token");
3306
3307 assert_eq!(tok_r.name, "testperson1");
3308 assert!(!tok_r.valid);
3309
3310 let tok_r = idms_prox_read
3311 .get_unixusertoken(&uute, time_high)
3312 .expect("Failed to generate unix user token");
3313
3314 assert_eq!(tok_r.name, "testperson1");
3315 assert!(!tok_r.valid);
3316 }
3317
3318 #[idm_test]
3319 async fn test_idm_radius_valid_from_expire(
3320 idms: &IdmServer,
3321 _idms_delayed: &mut IdmServerDelayed,
3322 ) {
3323 init_testperson_w_password(idms, TEST_PASSWORD)
3326 .await
3327 .expect("Failed to setup admin account");
3328 set_testperson_valid_time(idms).await;
3329
3330 let time_low = Duration::from_secs(TEST_NOT_YET_VALID_TIME);
3331 let time_high = Duration::from_secs(TEST_AFTER_EXPIRY);
3332
3333 let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
3334 let rrse = RegenerateRadiusSecretEvent::new_internal(UUID_TESTPERSON_1);
3335 let _r1 = idms_prox_write
3336 .regenerate_radius_secret(&rrse)
3337 .expect("Failed to reset radius credential 1");
3338 idms_prox_write.commit().expect("failed to commit");
3339
3340 let mut idms_prox_read = idms.proxy_read().await.unwrap();
3341 let admin_entry = idms_prox_read
3342 .qs_read
3343 .internal_search_uuid(UUID_ADMIN)
3344 .expect("Can't access admin entry.");
3345
3346 let rate = RadiusAuthTokenEvent::new_impersonate(admin_entry, UUID_ADMIN);
3347 let tok_r = idms_prox_read.get_radiusauthtoken(&rate, time_low);
3348
3349 if tok_r.is_err() {
3350 } else {
3352 debug_assert!(false);
3353 }
3354
3355 let tok_r = idms_prox_read.get_radiusauthtoken(&rate, time_high);
3356
3357 if tok_r.is_err() {
3358 } else {
3360 debug_assert!(false);
3361 }
3362 }
3363
3364 #[idm_test(audit = 1)]
3365 async fn test_idm_account_softlocking(
3366 idms: &IdmServer,
3367 idms_delayed: &mut IdmServerDelayed,
3368 idms_audit: &mut IdmServerAudit,
3369 ) {
3370 init_testperson_w_password(idms, TEST_PASSWORD)
3371 .await
3372 .expect("Failed to setup admin account");
3373
3374 let sid =
3376 init_authsession_sid(idms, Duration::from_secs(TEST_CURRENT_TIME), "testperson1").await;
3377 let mut idms_auth = idms.auth().await.unwrap();
3378 let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD_INC);
3379
3380 let r2 = idms_auth
3381 .auth(
3382 &anon_step,
3383 Duration::from_secs(TEST_CURRENT_TIME),
3384 Source::Internal.into(),
3385 )
3386 .await;
3387 debug!("r2 ==> {:?}", r2);
3388
3389 match r2 {
3390 Ok(ar) => {
3391 let AuthResult {
3392 sessionid: _,
3393 state,
3394 } = ar;
3395 match state {
3396 AuthState::Denied(reason) => {
3397 assert!(reason != "Account is temporarily locked");
3398 }
3399 _ => {
3400 error!("A critical error has occurred! We have a non-denied result!");
3401 panic!();
3402 }
3403 }
3404 }
3405 Err(e) => {
3406 error!("A critical error has occurred! {:?}", e);
3407 panic!();
3408 }
3409 };
3410
3411 match idms_audit.audit_rx().try_recv() {
3413 Ok(AuditEvent::AuthenticationDenied { .. }) => {}
3414 _ => panic!("Oh no"),
3415 }
3416
3417 idms_auth.commit().expect("Must not fail");
3418
3419 let mut idms_auth = idms.auth().await.unwrap();
3423 let admin_init = AuthEvent::named_init("testperson1");
3424
3425 let r1 = idms_auth
3426 .auth(
3427 &admin_init,
3428 Duration::from_secs(TEST_CURRENT_TIME),
3429 Source::Internal.into(),
3430 )
3431 .await;
3432 let ar = r1.unwrap();
3433 let AuthResult { sessionid, state } = ar;
3434 assert!(matches!(state, AuthState::Choose(_)));
3435
3436 let admin_begin = AuthEvent::begin_mech(sessionid, AuthMech::Password);
3438
3439 let r2 = idms_auth
3440 .auth(
3441 &admin_begin,
3442 Duration::from_secs(TEST_CURRENT_TIME),
3443 Source::Internal.into(),
3444 )
3445 .await;
3446 let ar = r2.unwrap();
3447 let AuthResult {
3448 sessionid: _,
3449 state,
3450 } = ar;
3451
3452 match state {
3453 AuthState::Denied(reason) => {
3454 assert_eq!(reason, "Account is temporarily locked");
3455 }
3456 _ => {
3457 error!("Sessions was not denied (softlock)");
3458 panic!();
3459 }
3460 };
3461
3462 idms_auth.commit().expect("Must not fail");
3463
3464 let sid = init_authsession_sid(
3469 idms,
3470 Duration::from_secs(TEST_CURRENT_TIME + 2),
3471 "testperson1",
3472 )
3473 .await;
3474
3475 let mut idms_auth = idms.auth().await.unwrap();
3476 let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD);
3477
3478 let r2 = idms_auth
3480 .auth(
3481 &anon_step,
3482 Duration::from_secs(TEST_CURRENT_TIME + 2),
3483 Source::Internal.into(),
3484 )
3485 .await;
3486 debug!("r2 ==> {:?}", r2);
3487
3488 match r2 {
3489 Ok(ar) => {
3490 let AuthResult {
3491 sessionid: _,
3492 state,
3493 } = ar;
3494 match state {
3495 AuthState::Success(_uat, AuthIssueSession::Token) => {
3496 }
3498 _ => {
3499 error!("A critical error has occurred! We have a non-success result!");
3500 panic!();
3501 }
3502 }
3503 }
3504 Err(e) => {
3505 error!("A critical error has occurred! {:?}", e);
3506 panic!();
3508 }
3509 };
3510
3511 idms_auth.commit().expect("Must not fail");
3512
3513 let da = idms_delayed.try_recv().expect("invalid");
3515 assert!(matches!(da, DelayedAction::AuthSessionRecord(_)));
3516 idms_delayed.check_is_empty_or_panic();
3517
3518 }
3525
3526 #[idm_test(audit = 1)]
3527 async fn test_idm_account_softlocking_interleaved(
3528 idms: &IdmServer,
3529 _idms_delayed: &mut IdmServerDelayed,
3530 idms_audit: &mut IdmServerAudit,
3531 ) {
3532 init_testperson_w_password(idms, TEST_PASSWORD)
3533 .await
3534 .expect("Failed to setup admin account");
3535
3536 let sid_early =
3538 init_authsession_sid(idms, Duration::from_secs(TEST_CURRENT_TIME), "testperson1").await;
3539
3540 let sid_later =
3542 init_authsession_sid(idms, Duration::from_secs(TEST_CURRENT_TIME), "testperson1").await;
3543 let mut idms_auth = idms.auth().await.unwrap();
3545 let anon_step = AuthEvent::cred_step_password(sid_later, TEST_PASSWORD_INC);
3546
3547 let r2 = idms_auth
3548 .auth(
3549 &anon_step,
3550 Duration::from_secs(TEST_CURRENT_TIME),
3551 Source::Internal.into(),
3552 )
3553 .await;
3554 debug!("r2 ==> {:?}", r2);
3555
3556 match r2 {
3557 Ok(ar) => {
3558 let AuthResult {
3559 sessionid: _,
3560 state,
3561 } = ar;
3562 match state {
3563 AuthState::Denied(reason) => {
3564 assert!(reason != "Account is temporarily locked");
3565 }
3566 _ => {
3567 error!("A critical error has occurred! We have a non-denied result!");
3568 panic!();
3569 }
3570 }
3571 }
3572 Err(e) => {
3573 error!("A critical error has occurred! {:?}", e);
3574 panic!();
3575 }
3576 };
3577
3578 match idms_audit.audit_rx().try_recv() {
3579 Ok(AuditEvent::AuthenticationDenied { .. }) => {}
3580 _ => panic!("Oh no"),
3581 }
3582
3583 idms_auth.commit().expect("Must not fail");
3584
3585 let mut idms_auth = idms.auth().await.unwrap();
3587 let anon_step = AuthEvent::cred_step_password(sid_early, TEST_PASSWORD);
3588
3589 let r2 = idms_auth
3591 .auth(
3592 &anon_step,
3593 Duration::from_secs(TEST_CURRENT_TIME),
3594 Source::Internal.into(),
3595 )
3596 .await;
3597 debug!("r2 ==> {:?}", r2);
3598 match r2 {
3599 Ok(ar) => {
3600 let AuthResult {
3601 sessionid: _,
3602 state,
3603 } = ar;
3604 match state {
3605 AuthState::Denied(reason) => {
3606 assert_eq!(reason, "Account is temporarily locked");
3607 }
3608 _ => {
3609 error!("A critical error has occurred! We have a non-denied result!");
3610 panic!();
3611 }
3612 }
3613 }
3614 Err(e) => {
3615 error!("A critical error has occurred! {:?}", e);
3616 panic!();
3617 }
3618 };
3619 idms_auth.commit().expect("Must not fail");
3620 }
3621
3622 #[idm_test]
3623 async fn test_idm_account_unix_softlocking(
3624 idms: &IdmServer,
3625 _idms_delayed: &mut IdmServerDelayed,
3626 ) {
3627 init_testperson_w_password(idms, TEST_PASSWORD)
3628 .await
3629 .expect("Failed to setup admin account");
3630 let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
3632 let me_posix = ModifyEvent::new_internal_invalid(
3633 filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_TESTPERSON_1))),
3634 ModifyList::new_list(vec![
3635 Modify::Present(Attribute::Class, EntryClass::PosixAccount.into()),
3636 Modify::Present(Attribute::GidNumber, Value::new_uint32(2001)),
3637 ]),
3638 );
3639 assert!(idms_prox_write.qs_write.modify(&me_posix).is_ok());
3640
3641 let pce = UnixPasswordChangeEvent::new_internal(UUID_TESTPERSON_1, TEST_PASSWORD);
3642 assert!(idms_prox_write.set_unix_account_password(&pce).is_ok());
3643 assert!(idms_prox_write.commit().is_ok());
3644
3645 let mut idms_auth = idms.auth().await.unwrap();
3646 let uuae_good = UnixUserAuthEvent::new_internal(UUID_TESTPERSON_1, TEST_PASSWORD);
3647 let uuae_bad = UnixUserAuthEvent::new_internal(UUID_TESTPERSON_1, TEST_PASSWORD_INC);
3648
3649 let a2 = idms_auth
3650 .auth_unix(&uuae_bad, Duration::from_secs(TEST_CURRENT_TIME))
3651 .await;
3652 match a2 {
3653 Ok(None) => {}
3654 _ => panic!("Oh no"),
3655 };
3656
3657 let a1 = idms_auth
3659 .auth_unix(&uuae_good, Duration::from_secs(TEST_CURRENT_TIME))
3660 .await;
3661 match a1 {
3662 Ok(None) => {}
3663 _ => panic!("Oh no"),
3664 };
3665
3666 let a1 = idms_auth
3668 .auth_unix(&uuae_good, Duration::from_secs(TEST_CURRENT_TIME + 2))
3669 .await;
3670 match a1 {
3671 Ok(Some(_tok)) => {}
3672 _ => panic!("Oh no"),
3673 };
3674
3675 assert!(idms_auth.commit().is_ok());
3676 }
3677
3678 #[idm_test]
3679 async fn test_idm_jwt_uat_expiry(idms: &IdmServer, idms_delayed: &mut IdmServerDelayed) {
3680 let ct = Duration::from_secs(TEST_CURRENT_TIME);
3681 let expiry = ct + Duration::from_secs((DEFAULT_AUTH_SESSION_EXPIRY + 1).into());
3682 init_testperson_w_password(idms, TEST_PASSWORD)
3684 .await
3685 .expect("Failed to setup admin account");
3686 let token = check_testperson_password(idms, TEST_PASSWORD, ct).await;
3687
3688 let da = idms_delayed.try_recv().expect("invalid");
3690 assert!(matches!(da, DelayedAction::AuthSessionRecord(_)));
3691 let r = idms.delayed_action(ct, da).await;
3693 assert_eq!(Ok(true), r);
3694 idms_delayed.check_is_empty_or_panic();
3695
3696 let mut idms_prox_read = idms.proxy_read().await.unwrap();
3697
3698 idms_prox_read
3700 .validate_client_auth_info_to_ident(token.clone().into(), ct)
3701 .expect("Failed to validate");
3702
3703 match idms_prox_read.validate_client_auth_info_to_ident(token.into(), expiry) {
3705 Err(OperationError::SessionExpired) => {}
3706 _ => panic!("Oh no"),
3707 }
3708 }
3709
3710 #[idm_test]
3711 async fn test_idm_expired_auth_session_cleanup(
3712 idms: &IdmServer,
3713 _idms_delayed: &mut IdmServerDelayed,
3714 ) {
3715 let ct = Duration::from_secs(TEST_CURRENT_TIME);
3716 let expiry_a = ct + Duration::from_secs((DEFAULT_AUTH_SESSION_EXPIRY + 1).into());
3717 let expiry_b = ct + Duration::from_secs(((DEFAULT_AUTH_SESSION_EXPIRY + 1) * 2).into());
3718
3719 let session_a = Uuid::new_v4();
3720 let session_b = Uuid::new_v4();
3721
3722 let cred_id = init_testperson_w_password(idms, TEST_PASSWORD)
3724 .await
3725 .expect("Failed to setup admin account");
3726
3727 let mut idms_prox_read = idms.proxy_read().await.unwrap();
3729 let admin = idms_prox_read
3730 .qs_read
3731 .internal_search_uuid(UUID_TESTPERSON_1)
3732 .expect("failed");
3733 let sessions = admin.get_ava_as_session_map(Attribute::UserAuthTokenSession);
3734 assert!(sessions.is_none());
3735 drop(idms_prox_read);
3736
3737 let da = DelayedAction::AuthSessionRecord(AuthSessionRecord {
3738 target_uuid: UUID_TESTPERSON_1,
3739 session_id: session_a,
3740 cred_id,
3741 label: "Test Session A".to_string(),
3742 expiry: Some(OffsetDateTime::UNIX_EPOCH + expiry_a),
3743 issued_at: OffsetDateTime::UNIX_EPOCH + ct,
3744 issued_by: IdentityId::User(UUID_ADMIN),
3745 scope: SessionScope::ReadOnly,
3746 type_: AuthType::Passkey,
3747 ext_metadata: Default::default(),
3748 });
3749 let r = idms.delayed_action(ct, da).await;
3751 assert_eq!(Ok(true), r);
3752
3753 let mut idms_prox_read = idms.proxy_read().await.unwrap();
3755 let admin = idms_prox_read
3756 .qs_read
3757 .internal_search_uuid(UUID_TESTPERSON_1)
3758 .expect("failed");
3759 let sessions = admin
3760 .get_ava_as_session_map(Attribute::UserAuthTokenSession)
3761 .expect("Sessions must be present!");
3762 assert_eq!(sessions.len(), 1);
3763 let session_data_a = sessions.get(&session_a).expect("Session A is missing!");
3764 assert!(matches!(session_data_a.state, SessionState::ExpiresAt(_)));
3765
3766 drop(idms_prox_read);
3767
3768 let da = DelayedAction::AuthSessionRecord(AuthSessionRecord {
3771 target_uuid: UUID_TESTPERSON_1,
3772 session_id: session_b,
3773 cred_id,
3774 label: "Test Session B".to_string(),
3775 expiry: Some(OffsetDateTime::UNIX_EPOCH + expiry_b),
3776 issued_at: OffsetDateTime::UNIX_EPOCH + ct,
3777 issued_by: IdentityId::User(UUID_ADMIN),
3778 scope: SessionScope::ReadOnly,
3779 type_: AuthType::Passkey,
3780 ext_metadata: Default::default(),
3781 });
3782 let r = idms.delayed_action(expiry_a, da).await;
3784 assert_eq!(Ok(true), r);
3785
3786 let mut idms_prox_read = idms.proxy_read().await.unwrap();
3787 let admin = idms_prox_read
3788 .qs_read
3789 .internal_search_uuid(UUID_TESTPERSON_1)
3790 .expect("failed");
3791 let sessions = admin
3792 .get_ava_as_session_map(Attribute::UserAuthTokenSession)
3793 .expect("Sessions must be present!");
3794 trace!(?sessions);
3795 assert_eq!(sessions.len(), 2);
3796
3797 let session_data_a = sessions.get(&session_a).expect("Session A is missing!");
3798 assert!(matches!(session_data_a.state, SessionState::RevokedAt(_)));
3799
3800 let session_data_b = sessions.get(&session_b).expect("Session B is missing!");
3801 assert!(matches!(session_data_b.state, SessionState::ExpiresAt(_)));
3802 }
3804
3805 #[idm_test]
3806 async fn test_idm_account_session_validation(
3807 idms: &IdmServer,
3808 idms_delayed: &mut IdmServerDelayed,
3809 ) {
3810 use kanidm_proto::internal::UserAuthToken;
3811
3812 let ct = duration_from_epoch_now();
3813
3814 let post_grace = ct + AUTH_TOKEN_GRACE_WINDOW + Duration::from_secs(1);
3815 let expiry = ct + Duration::from_secs(DEFAULT_AUTH_SESSION_EXPIRY as u64 + 1);
3816
3817 assert!(post_grace < expiry);
3820
3821 init_testperson_w_password(idms, TEST_PASSWORD)
3823 .await
3824 .expect("Failed to setup admin account");
3825 let uat_unverified = check_testperson_password(idms, TEST_PASSWORD, ct).await;
3826
3827 let da = idms_delayed.try_recv().expect("invalid");
3829 assert!(matches!(da, DelayedAction::AuthSessionRecord(_)));
3830 let r = idms.delayed_action(ct, da).await;
3831 assert_eq!(Ok(true), r);
3832
3833 let mut idms_prox_read = idms.proxy_read().await.unwrap();
3834
3835 let token_kid = uat_unverified.kid().expect("no key id present");
3836
3837 let uat_jwk = idms_prox_read
3838 .qs_read
3839 .get_key_providers()
3840 .get_key_object(UUID_DOMAIN_INFO)
3841 .and_then(|object| {
3842 object
3843 .jws_public_jwk(token_kid)
3844 .expect("Unable to access uat jwk")
3845 })
3846 .expect("No jwk by this kid");
3847
3848 let jws_validator = JwsEs256Verifier::try_from(&uat_jwk).unwrap();
3849
3850 let uat_inner: UserAuthToken = jws_validator
3851 .verify(&uat_unverified)
3852 .unwrap()
3853 .from_json()
3854 .unwrap();
3855
3856 idms_prox_read
3858 .validate_client_auth_info_to_ident(uat_unverified.clone().into(), ct)
3859 .expect("Failed to validate");
3860
3861 idms_prox_read
3863 .validate_client_auth_info_to_ident(uat_unverified.clone().into(), post_grace)
3864 .expect("Failed to validate");
3865
3866 drop(idms_prox_read);
3867
3868 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3870 let dte = DestroySessionTokenEvent::new_internal(uat_inner.uuid, uat_inner.session_id);
3871 assert!(idms_prox_write.account_destroy_session_token(&dte).is_ok());
3872 assert!(idms_prox_write.commit().is_ok());
3873
3874 let mut idms_prox_read = idms.proxy_read().await.unwrap();
3876
3877 match idms_prox_read
3880 .validate_client_auth_info_to_ident(uat_unverified.clone().into(), post_grace)
3881 {
3882 Err(OperationError::SessionExpired) => {}
3883 _ => panic!("Oh no"),
3884 }
3885 drop(idms_prox_read);
3886
3887 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3889 let filt = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(uat_inner.uuid)));
3890 let mut work_set = idms_prox_write
3891 .qs_write
3892 .internal_search_writeable(&filt)
3893 .expect("Failed to perform internal search writeable");
3894 for (_, entry) in work_set.iter_mut() {
3895 let _ = entry.force_trim_ava(Attribute::UserAuthTokenSession);
3896 }
3897 assert!(idms_prox_write
3898 .qs_write
3899 .internal_apply_writable(work_set)
3900 .is_ok());
3901
3902 assert!(idms_prox_write.commit().is_ok());
3903
3904 let mut idms_prox_read = idms.proxy_read().await.unwrap();
3905 idms_prox_read
3906 .validate_client_auth_info_to_ident(uat_unverified.clone().into(), ct)
3907 .expect("Failed to validate");
3908
3909 match idms_prox_read
3911 .validate_client_auth_info_to_ident(uat_unverified.clone().into(), post_grace)
3912 {
3913 Err(OperationError::SessionExpired) => {}
3914 _ => panic!("Oh no"),
3915 }
3916 }
3917
3918 #[idm_test]
3919 async fn test_idm_account_session_expiry(
3920 idms: &IdmServer,
3921 _idms_delayed: &mut IdmServerDelayed,
3922 ) {
3923 let ct = Duration::from_secs(TEST_CURRENT_TIME);
3924
3925 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3927
3928 let new_authsession_expiry = 1000;
3929
3930 let modlist = ModifyList::new_purge_and_set(
3931 Attribute::AuthSessionExpiry,
3932 Value::Uint32(new_authsession_expiry),
3933 );
3934 idms_prox_write
3935 .qs_write
3936 .internal_modify_uuid(UUID_IDM_ALL_ACCOUNTS, &modlist)
3937 .expect("Unable to change default session exp");
3938
3939 assert!(idms_prox_write.commit().is_ok());
3940
3941 let mut idms_auth = idms.auth().await.unwrap();
3943 let anon_init = AuthEvent::anonymous_init();
3945 let r1 = idms_auth
3947 .auth(&anon_init, ct, Source::Internal.into())
3948 .await;
3949 let sid = match r1 {
3952 Ok(ar) => {
3953 let AuthResult { sessionid, state } = ar;
3954 match state {
3955 AuthState::Choose(mut conts) => {
3956 assert_eq!(conts.len(), 1);
3958 let m = conts.pop().expect("Should not fail");
3960 assert_eq!(m, AuthMech::Anonymous);
3961 }
3962 _ => {
3963 error!("A critical error has occurred! We have a non-continue result!");
3964 panic!();
3965 }
3966 };
3967 sessionid
3969 }
3970 Err(e) => {
3971 error!("A critical error has occurred! {:?}", e);
3973 panic!();
3974 }
3975 };
3976
3977 idms_auth.commit().expect("Must not fail");
3978
3979 let mut idms_auth = idms.auth().await.unwrap();
3980 let anon_begin = AuthEvent::begin_mech(sid, AuthMech::Anonymous);
3981
3982 let r2 = idms_auth
3983 .auth(&anon_begin, ct, Source::Internal.into())
3984 .await;
3985
3986 match r2 {
3987 Ok(ar) => {
3988 let AuthResult {
3989 sessionid: _,
3990 state,
3991 } = ar;
3992
3993 match state {
3994 AuthState::Continue(allowed) => {
3995 assert_eq!(allowed.len(), 1);
3997 assert_eq!(allowed.first(), Some(&AuthAllowed::Anonymous));
3998 }
3999 _ => {
4000 error!("A critical error has occurred! We have a non-continue result!");
4001 panic!();
4002 }
4003 }
4004 }
4005 Err(e) => {
4006 error!("A critical error has occurred! {:?}", e);
4007 panic!();
4009 }
4010 };
4011
4012 idms_auth.commit().expect("Must not fail");
4013
4014 let mut idms_auth = idms.auth().await.unwrap();
4015 let anon_step = AuthEvent::cred_step_anonymous(sid);
4017
4018 let r2 = idms_auth
4020 .auth(&anon_step, ct, Source::Internal.into())
4021 .await;
4022
4023 let token = match r2 {
4024 Ok(ar) => {
4025 let AuthResult {
4026 sessionid: _,
4027 state,
4028 } = ar;
4029
4030 match state {
4031 AuthState::Success(uat, AuthIssueSession::Token) => uat,
4032 _ => {
4033 error!("A critical error has occurred! We have a non-success result!");
4034 panic!();
4035 }
4036 }
4037 }
4038 Err(e) => {
4039 error!("A critical error has occurred! {:?}", e);
4040 panic!("A critical error has occurred! {e:?}");
4042 }
4043 };
4044
4045 idms_auth.commit().expect("Must not fail");
4046
4047 let Token::UserAuthToken(uat) = idms
4050 .proxy_read()
4051 .await
4052 .unwrap()
4053 .validate_and_parse_token_to_identity_token(&token, ct)
4054 .expect("Must not fail")
4055 else {
4056 panic!("Unexpected auth token type for anonymous auth");
4057 };
4058
4059 debug!(?uat);
4060
4061 assert!(
4062 matches!(uat.expiry, Some(exp) if exp == OffsetDateTime::UNIX_EPOCH + ct + Duration::from_secs(new_authsession_expiry as u64))
4063 );
4064 }
4065
4066 #[idm_test]
4067 async fn test_idm_uat_claim_insertion(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) {
4068 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4069 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4070
4071 let account = idms_prox_write
4073 .target_to_account(UUID_ADMIN)
4074 .expect("account must exist");
4075
4076 let session_id = uuid::Uuid::new_v4();
4078
4079 let uat = account
4083 .to_userauthtoken(
4084 session_id,
4085 SessionScope::ReadWrite,
4086 ct,
4087 &ResolvedAccountPolicy::test_policy(),
4088 )
4089 .expect("Unable to create uat");
4090 let ident = idms_prox_write
4091 .process_uat_to_identity(&uat, ct, Source::Internal)
4092 .expect("Unable to process uat");
4093
4094 assert!(!ident.has_claim("authtype_anonymous"));
4095 assert!(!ident.has_claim("authlevel_strong"));
4097 assert!(!ident.has_claim("authclass_single"));
4098 assert!(!ident.has_claim("authclass_mfa"));
4099
4100 let uat = account
4102 .to_userauthtoken(
4103 session_id,
4104 SessionScope::ReadWrite,
4105 ct,
4106 &ResolvedAccountPolicy::test_policy(),
4107 )
4108 .expect("Unable to create uat");
4109 let ident = idms_prox_write
4110 .process_uat_to_identity(&uat, ct, Source::Internal)
4111 .expect("Unable to process uat");
4112
4113 assert!(!ident.has_claim("authtype_unixpassword"));
4114 assert!(!ident.has_claim("authclass_single"));
4115 assert!(!ident.has_claim("authlevel_strong"));
4117 assert!(!ident.has_claim("authclass_mfa"));
4118
4119 let uat = account
4121 .to_userauthtoken(
4122 session_id,
4123 SessionScope::ReadWrite,
4124 ct,
4125 &ResolvedAccountPolicy::test_policy(),
4126 )
4127 .expect("Unable to create uat");
4128 let ident = idms_prox_write
4129 .process_uat_to_identity(&uat, ct, Source::Internal)
4130 .expect("Unable to process uat");
4131
4132 assert!(!ident.has_claim("authtype_password"));
4133 assert!(!ident.has_claim("authclass_single"));
4134 assert!(!ident.has_claim("authlevel_strong"));
4136 assert!(!ident.has_claim("authclass_mfa"));
4137
4138 let uat = account
4140 .to_userauthtoken(
4141 session_id,
4142 SessionScope::ReadWrite,
4143 ct,
4144 &ResolvedAccountPolicy::test_policy(),
4145 )
4146 .expect("Unable to create uat");
4147 let ident = idms_prox_write
4148 .process_uat_to_identity(&uat, ct, Source::Internal)
4149 .expect("Unable to process uat");
4150
4151 assert!(!ident.has_claim("authtype_generatedpassword"));
4152 assert!(!ident.has_claim("authclass_single"));
4153 assert!(!ident.has_claim("authlevel_strong"));
4154 assert!(!ident.has_claim("authclass_mfa"));
4156
4157 let uat = account
4159 .to_userauthtoken(
4160 session_id,
4161 SessionScope::ReadWrite,
4162 ct,
4163 &ResolvedAccountPolicy::test_policy(),
4164 )
4165 .expect("Unable to create uat");
4166 let ident = idms_prox_write
4167 .process_uat_to_identity(&uat, ct, Source::Internal)
4168 .expect("Unable to process uat");
4169
4170 assert!(!ident.has_claim("authtype_webauthn"));
4171 assert!(!ident.has_claim("authclass_single"));
4172 assert!(!ident.has_claim("authlevel_strong"));
4173 assert!(!ident.has_claim("authclass_mfa"));
4175
4176 let uat = account
4178 .to_userauthtoken(
4179 session_id,
4180 SessionScope::ReadWrite,
4181 ct,
4182 &ResolvedAccountPolicy::test_policy(),
4183 )
4184 .expect("Unable to create uat");
4185 let ident = idms_prox_write
4186 .process_uat_to_identity(&uat, ct, Source::Internal)
4187 .expect("Unable to process uat");
4188
4189 assert!(!ident.has_claim("authtype_passwordmfa"));
4190 assert!(!ident.has_claim("authlevel_strong"));
4191 assert!(!ident.has_claim("authclass_mfa"));
4192 assert!(!ident.has_claim("authclass_single"));
4194 }
4195
4196 #[idm_test]
4197 async fn test_idm_uat_limits_account_policy(
4198 idms: &IdmServer,
4199 _idms_delayed: &mut IdmServerDelayed,
4200 ) {
4201 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4202 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4203
4204 idms_prox_write
4205 .qs_write
4206 .internal_create(vec![E_TESTPERSON_1.clone()])
4207 .expect("Failed to create test person");
4208
4209 let account = idms_prox_write
4211 .target_to_account(UUID_TESTPERSON_1)
4212 .expect("account must exist");
4213
4214 let session_id = uuid::Uuid::new_v4();
4216
4217 let uat = account
4218 .to_userauthtoken(
4219 session_id,
4220 SessionScope::ReadWrite,
4221 ct,
4222 &ResolvedAccountPolicy::test_policy(),
4223 )
4224 .expect("Unable to create uat");
4225
4226 let ident = idms_prox_write
4227 .process_uat_to_identity(&uat, ct, Source::Internal)
4228 .expect("Unable to process uat");
4229
4230 assert_eq!(
4231 ident.limits().search_max_results,
4232 DEFAULT_LIMIT_SEARCH_MAX_RESULTS as usize
4233 );
4234 assert_eq!(
4235 ident.limits().search_max_filter_test,
4236 DEFAULT_LIMIT_SEARCH_MAX_FILTER_TEST as usize
4237 );
4238 }
4239
4240 #[idm_test]
4241 async fn test_idm_jwt_uat_token_key_reload(
4242 idms: &IdmServer,
4243 idms_delayed: &mut IdmServerDelayed,
4244 ) {
4245 let ct = duration_from_epoch_now();
4246
4247 init_testperson_w_password(idms, TEST_PASSWORD)
4248 .await
4249 .expect("Failed to setup admin account");
4250 let token = check_testperson_password(idms, TEST_PASSWORD, ct).await;
4251
4252 let da = idms_delayed.try_recv().expect("invalid");
4254 assert!(matches!(da, DelayedAction::AuthSessionRecord(_)));
4255 idms_delayed.check_is_empty_or_panic();
4256
4257 let mut idms_prox_read = idms.proxy_read().await.unwrap();
4258
4259 idms_prox_read
4261 .validate_client_auth_info_to_ident(token.clone().into(), ct)
4262 .expect("Failed to validate");
4263
4264 drop(idms_prox_read);
4265
4266 let revoke_kid = token.kid().expect("token does not contain a key id");
4268
4269 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4271 let me_reset_tokens = ModifyEvent::new_internal_invalid(
4272 filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_DOMAIN_INFO))),
4273 ModifyList::new_append(
4274 Attribute::KeyActionRevoke,
4275 Value::HexString(revoke_kid.to_string()),
4276 ),
4277 );
4278 assert!(idms_prox_write.qs_write.modify(&me_reset_tokens).is_ok());
4279 assert!(idms_prox_write.commit().is_ok());
4280
4281 let new_token = check_testperson_password(idms, TEST_PASSWORD, ct).await;
4282
4283 let da = idms_delayed.try_recv().expect("invalid");
4285 assert!(matches!(da, DelayedAction::AuthSessionRecord(_)));
4286 idms_delayed.check_is_empty_or_panic();
4287
4288 let mut idms_prox_read = idms.proxy_read().await.unwrap();
4289
4290 assert!(idms_prox_read
4292 .validate_client_auth_info_to_ident(token.into(), ct)
4293 .is_err());
4294
4295 idms_prox_read
4297 .validate_client_auth_info_to_ident(new_token.into(), ct)
4298 .expect("Failed to validate");
4299 }
4300
4301 #[idm_test]
4302 async fn test_idm_service_account_to_person(
4303 idms: &IdmServer,
4304 _idms_delayed: &mut IdmServerDelayed,
4305 ) {
4306 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4307 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4308
4309 let ident = Identity::from_internal();
4310 let target_uuid = Uuid::new_v4();
4311
4312 let e = entry_init!(
4314 (Attribute::Class, EntryClass::Object.to_value()),
4315 (Attribute::Class, EntryClass::Account.to_value()),
4316 (Attribute::Class, EntryClass::ServiceAccount.to_value()),
4317 (Attribute::Name, Value::new_iname("testaccount")),
4318 (Attribute::Uuid, Value::Uuid(target_uuid)),
4319 (Attribute::Description, Value::new_utf8s("testaccount")),
4320 (Attribute::DisplayName, Value::new_utf8s("Test Account"))
4321 );
4322
4323 let ce = CreateEvent::new_internal(vec![e]);
4324 let cr = idms_prox_write.qs_write.create(&ce);
4325 assert!(cr.is_ok());
4326
4327 assert!(idms_prox_write
4329 .service_account_into_person(&ident, target_uuid)
4330 .is_ok());
4331
4332 }
4334
4335 async fn idm_fallback_auth_fixture(
4336 idms: &IdmServer,
4337 _idms_delayed: &mut IdmServerDelayed,
4338 has_posix_password: bool,
4339 allow_primary_cred_fallback: Option<bool>,
4340 expected: Option<()>,
4341 ) {
4342 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4343 let target_uuid = Uuid::new_v4();
4344 let p = CryptoPolicy::minimum();
4345
4346 {
4347 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4348
4349 if let Some(allow_primary_cred_fallback) = allow_primary_cred_fallback {
4350 idms_prox_write
4351 .qs_write
4352 .internal_modify_uuid(
4353 UUID_IDM_ALL_ACCOUNTS,
4354 &ModifyList::new_purge_and_set(
4355 Attribute::AllowPrimaryCredFallback,
4356 Value::new_bool(allow_primary_cred_fallback),
4357 ),
4358 )
4359 .expect("Unable to change default session exp");
4360 }
4361
4362 let mut e = entry_init!(
4363 (Attribute::Class, EntryClass::Object.to_value()),
4364 (Attribute::Class, EntryClass::Account.to_value()),
4365 (Attribute::Class, EntryClass::Person.to_value()),
4366 (Attribute::Uuid, Value::Uuid(target_uuid)),
4367 (Attribute::Name, Value::new_iname("kevin")),
4368 (Attribute::DisplayName, Value::new_utf8s("Kevin")),
4369 (Attribute::Class, EntryClass::PosixAccount.to_value()),
4370 (
4371 Attribute::PrimaryCredential,
4372 Value::Cred(
4373 "primary".to_string(),
4374 Credential::new_password_only(&p, "banana", OffsetDateTime::UNIX_EPOCH)
4375 .unwrap()
4376 )
4377 )
4378 );
4379
4380 if has_posix_password {
4381 e.add_ava(
4382 Attribute::UnixPassword,
4383 Value::Cred(
4384 "unix".to_string(),
4385 Credential::new_password_only(&p, "kampai", OffsetDateTime::UNIX_EPOCH)
4386 .unwrap(),
4387 ),
4388 );
4389 }
4390
4391 let ce = CreateEvent::new_internal(vec![e]);
4392 let cr = idms_prox_write.qs_write.create(&ce);
4393 assert!(cr.is_ok());
4394 idms_prox_write.commit().expect("Must not fail");
4395 }
4396
4397 let result = idms
4398 .auth()
4399 .await
4400 .unwrap()
4401 .auth_ldap(
4402 &LdapAuthEvent {
4403 target: target_uuid,
4404 cleartext: if has_posix_password {
4405 "kampai".to_string()
4406 } else {
4407 "banana".to_string()
4408 },
4409 },
4410 ct,
4411 )
4412 .await;
4413
4414 assert!(result.is_ok());
4415 if expected.is_some() {
4416 assert!(result.unwrap().is_some());
4417 } else {
4418 assert!(result.unwrap().is_none());
4419 }
4420 }
4421
4422 #[idm_test]
4423 async fn test_idm_fallback_auth_no_pass_none_fallback(
4424 idms: &IdmServer,
4425 _idms_delayed: &mut IdmServerDelayed,
4426 ) {
4427 idm_fallback_auth_fixture(idms, _idms_delayed, false, None, None).await;
4428 }
4429 #[idm_test]
4430 async fn test_idm_fallback_auth_pass_none_fallback(
4431 idms: &IdmServer,
4432 _idms_delayed: &mut IdmServerDelayed,
4433 ) {
4434 idm_fallback_auth_fixture(idms, _idms_delayed, true, None, Some(())).await;
4435 }
4436 #[idm_test]
4437 async fn test_idm_fallback_auth_no_pass_true_fallback(
4438 idms: &IdmServer,
4439 _idms_delayed: &mut IdmServerDelayed,
4440 ) {
4441 idm_fallback_auth_fixture(idms, _idms_delayed, false, Some(true), Some(())).await;
4442 }
4443 #[idm_test]
4444 async fn test_idm_fallback_auth_pass_true_fallback(
4445 idms: &IdmServer,
4446 _idms_delayed: &mut IdmServerDelayed,
4447 ) {
4448 idm_fallback_auth_fixture(idms, _idms_delayed, true, Some(true), Some(())).await;
4449 }
4450 #[idm_test]
4451 async fn test_idm_fallback_auth_no_pass_false_fallback(
4452 idms: &IdmServer,
4453 _idms_delayed: &mut IdmServerDelayed,
4454 ) {
4455 idm_fallback_auth_fixture(idms, _idms_delayed, false, Some(false), None).await;
4456 }
4457 #[idm_test]
4458 async fn test_idm_fallback_auth_pass_false_fallback(
4459 idms: &IdmServer,
4460 _idms_delayed: &mut IdmServerDelayed,
4461 ) {
4462 idm_fallback_auth_fixture(idms, _idms_delayed, true, Some(false), Some(())).await;
4463 }
4464}