kanidmd_lib/idm/
server.rs

1use super::event::ReadBackupCodeEvent;
2use super::ldap::{LdapBoundToken, LdapSession};
3use crate::credential::{softlock::CredSoftLock, Credential};
4use crate::idm::account::Account;
5use crate::idm::application::{
6    GenerateApplicationPasswordEvent, LdapApplications, LdapApplicationsReadTransaction,
7    LdapApplicationsWriteTransaction,
8};
9use crate::idm::audit::AuditEvent;
10use crate::idm::authsession::{AuthSession, AuthSessionData};
11use crate::idm::credupdatesession::CredentialUpdateSessionMutex;
12use crate::idm::delayed::{
13    AuthSessionRecord, BackupCodeRemoval, DelayedAction, PasswordUpgrade, UnixPasswordUpgrade,
14    WebauthnCounterIncrement,
15};
16use crate::idm::event::{AuthEvent, AuthEventStep, AuthResult};
17use crate::idm::event::{
18    CredentialStatusEvent, LdapAuthEvent, LdapTokenAuthEvent, RadiusAuthTokenEvent,
19    RegenerateRadiusSecretEvent, UnixGroupTokenEvent, UnixPasswordChangeEvent, UnixUserAuthEvent,
20    UnixUserTokenEvent,
21};
22use crate::idm::group::{Group, Unix};
23use crate::idm::oauth2::{
24    Oauth2ResourceServers, Oauth2ResourceServersReadTransaction,
25    Oauth2ResourceServersWriteTransaction,
26};
27use crate::idm::radius::RadiusAccount;
28use crate::idm::scim::SyncAccount;
29use crate::idm::serviceaccount::ServiceAccount;
30use crate::idm::AuthState;
31use crate::prelude::*;
32use crate::server::keys::KeyProvidersTransaction;
33use crate::server::DomainInfo;
34use crate::utils::{password_from_random, readable_password_from_random, uuid_from_duration, Sid};
35use crate::value::{Session, SessionState};
36use compact_jwt::{Jwk, JwsCompact};
37use concread::bptree::{BptreeMap, BptreeMapReadTxn, BptreeMapWriteTxn};
38use concread::cowcell::CowCellReadTxn;
39use concread::hashmap::HashMap;
40use kanidm_lib_crypto::CryptoPolicy;
41use kanidm_proto::internal::{
42    ApiToken, BackupCodesView, CredentialStatus, PasswordFeedback, RadiusAuthToken, ScimSyncToken,
43    UatPurpose, UserAuthToken,
44};
45use kanidm_proto::v1::{UnixGroupToken, UnixUserToken};
46use rand::prelude::*;
47use std::convert::TryFrom;
48use std::sync::Arc;
49use std::time::Duration;
50use tokio::sync::mpsc::{
51    unbounded_channel as unbounded, UnboundedReceiver as Receiver, UnboundedSender as Sender,
52};
53use tokio::sync::{Mutex, Semaphore};
54use tracing::trace;
55use url::Url;
56use webauthn_rs::prelude::{Webauthn, WebauthnBuilder};
57use zxcvbn::{zxcvbn, Score};
58
59#[cfg(test)]
60use crate::idm::event::PasswordChangeEvent;
61
62pub(crate) type AuthSessionMutex = Arc<Mutex<AuthSession>>;
63pub(crate) type CredSoftLockMutex = Arc<Mutex<CredSoftLock>>;
64
65pub type DomainInfoRead = CowCellReadTxn<DomainInfo>;
66
67pub struct IdmServer {
68    // There is a good reason to keep this single thread - it
69    // means that limits to sessions can be easily applied and checked to
70    // various accounts, and we have a good idea of how to structure the
71    // in memory caches related to locking.
72    session_ticket: Semaphore,
73    sessions: BptreeMap<Uuid, AuthSessionMutex>,
74    softlocks: HashMap<Uuid, CredSoftLockMutex>,
75    /// A set of in progress credential registrations
76    cred_update_sessions: BptreeMap<Uuid, CredentialUpdateSessionMutex>,
77    /// Reference to the query server.
78    qs: QueryServer,
79    /// The configured crypto policy for the IDM server. Later this could be transactional and loaded from the db similar to access. But today it's just to allow dynamic pbkdf2rounds
80    crypto_policy: CryptoPolicy,
81    async_tx: Sender<DelayedAction>,
82    audit_tx: Sender<AuditEvent>,
83    /// [Webauthn] verifier/config
84    webauthn: Webauthn,
85    oauth2rs: Arc<Oauth2ResourceServers>,
86    applications: Arc<LdapApplications>,
87}
88
89/// Contains methods that require writes, but in the context of writing to the idm in memory structures (maybe the query server too). This is things like authentication.
90pub struct IdmServerAuthTransaction<'a> {
91    pub(crate) session_ticket: &'a Semaphore,
92    pub(crate) sessions: &'a BptreeMap<Uuid, AuthSessionMutex>,
93    pub(crate) softlocks: &'a HashMap<Uuid, CredSoftLockMutex>,
94
95    pub qs_read: QueryServerReadTransaction<'a>,
96    /// Thread/Server ID
97    pub(crate) sid: Sid,
98    // For flagging eventual actions.
99    pub(crate) async_tx: Sender<DelayedAction>,
100    pub(crate) audit_tx: Sender<AuditEvent>,
101    pub(crate) webauthn: &'a Webauthn,
102    pub(crate) applications: LdapApplicationsReadTransaction,
103}
104
105pub struct IdmServerCredUpdateTransaction<'a> {
106    pub(crate) qs_read: QueryServerReadTransaction<'a>,
107    // sid: Sid,
108    pub(crate) webauthn: &'a Webauthn,
109    pub(crate) cred_update_sessions: BptreeMapReadTxn<'a, Uuid, CredentialUpdateSessionMutex>,
110    pub(crate) crypto_policy: &'a CryptoPolicy,
111}
112
113/// This contains read-only methods, like getting users, groups and other structured content.
114pub struct IdmServerProxyReadTransaction<'a> {
115    pub qs_read: QueryServerReadTransaction<'a>,
116    pub(crate) oauth2rs: Oauth2ResourceServersReadTransaction,
117}
118
119pub struct IdmServerProxyWriteTransaction<'a> {
120    // This does NOT take any read to the memory content, allowing safe
121    // qs operations to occur through this interface.
122    pub qs_write: QueryServerWriteTransaction<'a>,
123    /// Associate to an event origin ID, which has a TS and a UUID instead
124    pub(crate) cred_update_sessions: BptreeMapWriteTxn<'a, Uuid, CredentialUpdateSessionMutex>,
125    pub(crate) sid: Sid,
126    crypto_policy: &'a CryptoPolicy,
127    webauthn: &'a Webauthn,
128    pub(crate) oauth2rs: Oauth2ResourceServersWriteTransaction<'a>,
129    pub(crate) applications: LdapApplicationsWriteTransaction<'a>,
130}
131
132pub struct IdmServerDelayed {
133    pub(crate) async_rx: Receiver<DelayedAction>,
134}
135
136pub struct IdmServerAudit {
137    pub(crate) audit_rx: Receiver<AuditEvent>,
138}
139
140impl IdmServer {
141    pub async fn new(
142        qs: QueryServer,
143        origin: &str,
144        is_integration_test: bool,
145    ) -> Result<(IdmServer, IdmServerDelayed, IdmServerAudit), OperationError> {
146        let crypto_policy = if cfg!(test) || is_integration_test {
147            CryptoPolicy::danger_test_minimum()
148        } else {
149            // This is calculated back from:
150            //  100 password auths / thread -> 0.010 sec per op
151            CryptoPolicy::time_target(Duration::from_millis(10))
152        };
153
154        let (async_tx, async_rx) = unbounded();
155        let (audit_tx, audit_rx) = unbounded();
156
157        // Get the domain name, as the relying party id.
158        let (rp_id, rp_name, application_set) = {
159            let mut qs_read = qs.read().await?;
160            (
161                qs_read.get_domain_name().to_string(),
162                qs_read.get_domain_display_name().to_string(),
163                // Add a read/reload of all oauth2 configurations.
164                qs_read.get_applications_set()?,
165            )
166        };
167
168        // Check that it gels with our origin.
169        let origin_url = Url::parse(origin)
170            .map_err(|_e| {
171                admin_error!("Unable to parse origin URL - refusing to start. You must correct the value for origin. {:?}", origin);
172                OperationError::InvalidState
173            })
174            .and_then(|url| {
175                let valid = url.domain().map(|effective_domain| {
176                    // We need to prepend the '.' here to ensure that myexample.com != example.com,
177                    // rather than just ends with.
178                    effective_domain.ends_with(&format!(".{rp_id}"))
179                    || effective_domain == rp_id
180                }).unwrap_or(false);
181
182                if valid {
183                    Ok(url)
184                } else {
185                    admin_error!("Effective domain (ed) is not a descendent of server domain name (rp_id).");
186                    admin_error!("You must change origin or domain name to be consistent. ded: {:?} - rp_id: {:?}", origin, rp_id);
187                    admin_error!("To change the origin or domain name see: https://kanidm.github.io/kanidm/master/server_configuration.html");
188                    Err(OperationError::InvalidState)
189                }
190            })?;
191
192        let webauthn = WebauthnBuilder::new(&rp_id, &origin_url)
193            .and_then(|builder| builder.allow_subdomains(true).rp_name(&rp_name).build())
194            .map_err(|e| {
195                admin_error!("Invalid Webauthn Configuration - {:?}", e);
196                OperationError::InvalidState
197            })?;
198
199        let oauth2rs = Oauth2ResourceServers::new(origin_url).map_err(|err| {
200            error!(?err, "Failed to load oauth2 resource servers");
201            err
202        })?;
203
204        let applications = LdapApplications::try_from(application_set).map_err(|e| {
205            admin_error!("Failed to load ldap applications - {:?}", e);
206            e
207        })?;
208
209        Ok((
210            IdmServer {
211                session_ticket: Semaphore::new(1),
212                sessions: BptreeMap::new(),
213                softlocks: HashMap::new(),
214                cred_update_sessions: BptreeMap::new(),
215                qs,
216                crypto_policy,
217                async_tx,
218                audit_tx,
219                webauthn,
220                oauth2rs: Arc::new(oauth2rs),
221                applications: Arc::new(applications),
222            },
223            IdmServerDelayed { async_rx },
224            IdmServerAudit { audit_rx },
225        ))
226    }
227
228    /// Start an auth txn
229    pub async fn auth(&self) -> Result<IdmServerAuthTransaction<'_>, OperationError> {
230        let qs_read = self.qs.read().await?;
231
232        let mut sid = [0; 4];
233        let mut rng = StdRng::from_os_rng();
234        rng.fill(&mut sid);
235
236        Ok(IdmServerAuthTransaction {
237            session_ticket: &self.session_ticket,
238            sessions: &self.sessions,
239            softlocks: &self.softlocks,
240            qs_read,
241            sid,
242            async_tx: self.async_tx.clone(),
243            audit_tx: self.audit_tx.clone(),
244            webauthn: &self.webauthn,
245            applications: self.applications.read(),
246        })
247    }
248
249    /// Begin a fast (low cost) read of the servers domain info. It is important to note
250    /// this does not conflict with any other type of transaction type and may safely
251    /// beheld over other transaction boundaries.
252    #[instrument(level = "debug", skip_all)]
253    pub fn domain_read(&self) -> DomainInfoRead {
254        self.qs.d_info.read()
255    }
256
257    /// Read from the database, in a transaction.
258    #[instrument(level = "debug", skip_all)]
259    pub async fn proxy_read(&self) -> Result<IdmServerProxyReadTransaction<'_>, OperationError> {
260        let qs_read = self.qs.read().await?;
261        Ok(IdmServerProxyReadTransaction {
262            qs_read,
263            oauth2rs: self.oauth2rs.read(),
264            // async_tx: self.async_tx.clone(),
265        })
266    }
267
268    #[instrument(level = "debug", skip_all)]
269    pub async fn proxy_write(
270        &self,
271        ts: Duration,
272    ) -> Result<IdmServerProxyWriteTransaction<'_>, OperationError> {
273        let qs_write = self.qs.write(ts).await?;
274
275        let mut sid = [0; 4];
276        let mut rng = StdRng::from_os_rng();
277        rng.fill(&mut sid);
278
279        Ok(IdmServerProxyWriteTransaction {
280            cred_update_sessions: self.cred_update_sessions.write(),
281            qs_write,
282            sid,
283            crypto_policy: &self.crypto_policy,
284            webauthn: &self.webauthn,
285            oauth2rs: self.oauth2rs.write(),
286            applications: self.applications.write(),
287        })
288    }
289
290    pub async fn cred_update_transaction(
291        &self,
292    ) -> Result<IdmServerCredUpdateTransaction<'_>, OperationError> {
293        let qs_read = self.qs.read().await?;
294        Ok(IdmServerCredUpdateTransaction {
295            qs_read,
296            // sid: Sid,
297            webauthn: &self.webauthn,
298            cred_update_sessions: self.cred_update_sessions.read(),
299            crypto_policy: &self.crypto_policy,
300        })
301    }
302
303    #[cfg(test)]
304    pub(crate) async fn delayed_action(
305        &self,
306        ct: Duration,
307        da: DelayedAction,
308    ) -> Result<bool, OperationError> {
309        let mut pw = self.proxy_write(ct).await?;
310        pw.process_delayedaction(&da, ct)
311            .and_then(|_| pw.commit())
312            .map(|()| true)
313    }
314}
315
316impl IdmServerAudit {
317    #[cfg(test)]
318    pub(crate) fn check_is_empty_or_panic(&mut self) {
319        use tokio::sync::mpsc::error::TryRecvError;
320
321        match self.audit_rx.try_recv() {
322            Err(TryRecvError::Empty) => {}
323            Err(TryRecvError::Disconnected) => {
324                panic!("Task queue disconnected");
325            }
326            Ok(m) => {
327                trace!(?m);
328                panic!("Task queue not empty");
329            }
330        }
331    }
332
333    pub fn audit_rx(&mut self) -> &mut Receiver<AuditEvent> {
334        &mut self.audit_rx
335    }
336}
337
338impl IdmServerDelayed {
339    #[cfg(test)]
340    pub(crate) fn check_is_empty_or_panic(&mut self) {
341        use tokio::sync::mpsc::error::TryRecvError;
342
343        match self.async_rx.try_recv() {
344            Err(TryRecvError::Empty) => {}
345            Err(TryRecvError::Disconnected) => {
346                panic!("Task queue disconnected");
347            }
348            #[allow(clippy::panic)]
349            Ok(m) => {
350                trace!(?m);
351                panic!("Task queue not empty");
352            }
353        }
354    }
355
356    #[cfg(test)]
357    pub(crate) fn try_recv(&mut self) -> Result<DelayedAction, OperationError> {
358        use core::task::{Context, Poll};
359        use futures::task as futures_task;
360
361        let waker = futures_task::noop_waker();
362        let mut cx = Context::from_waker(&waker);
363        match self.async_rx.poll_recv(&mut cx) {
364            Poll::Pending => Err(OperationError::InvalidState),
365            Poll::Ready(None) => Err(OperationError::QueueDisconnected),
366            Poll::Ready(Some(m)) => Ok(m),
367        }
368    }
369
370    pub async fn recv_many(&mut self, buffer: &mut Vec<DelayedAction>) -> usize {
371        debug_assert!(buffer.is_empty());
372        let limit = buffer.capacity();
373        self.async_rx.recv_many(buffer, limit).await
374    }
375}
376
377pub enum Token {
378    UserAuthToken(UserAuthToken),
379    ApiToken(ApiToken, Arc<EntrySealedCommitted>),
380}
381
382pub trait IdmServerTransaction<'a> {
383    type QsTransactionType: QueryServerTransaction<'a>;
384
385    fn get_qs_txn(&mut self) -> &mut Self::QsTransactionType;
386
387    /// This is the preferred method to transform and securely verify a token into
388    /// an identity that can be used for operations and access enforcement. This
389    /// function *is* aware of the various classes of tokens that may exist, and can
390    /// appropriately check them.
391    ///
392    /// The primary method of verification selection is the use of the KID parameter
393    /// that we internally sign with. We can use this to select the appropriate token type
394    /// and validation method.
395    #[instrument(level = "info", skip_all)]
396    fn validate_client_auth_info_to_ident(
397        &mut self,
398        client_auth_info: ClientAuthInfo,
399        ct: Duration,
400    ) -> Result<Identity, OperationError> {
401        let ClientAuthInfo {
402            source,
403            client_cert,
404            bearer_token,
405            basic_authz: _,
406        } = client_auth_info;
407
408        match (client_cert, bearer_token) {
409            (Some(client_cert_info), _) => {
410                self.client_certificate_to_identity(&client_cert_info, ct, source)
411            }
412            (None, Some(token)) => match self.validate_and_parse_token_to_token(&token, ct)? {
413                Token::UserAuthToken(uat) => self.process_uat_to_identity(&uat, ct, source),
414                Token::ApiToken(apit, entry) => {
415                    self.process_apit_to_identity(&apit, source, entry, ct)
416                }
417            },
418            (None, None) => {
419                debug!("No client certificate or bearer tokens were supplied");
420                Err(OperationError::NotAuthenticated)
421            }
422        }
423    }
424
425    /// This function is not using in authentication flows - it is a reflector of the
426    /// current session state to allow a user-auth-token to be presented to the
427    /// user via the whoami call.
428    #[instrument(level = "info", skip_all)]
429    fn validate_client_auth_info_to_uat(
430        &mut self,
431        client_auth_info: ClientAuthInfo,
432        ct: Duration,
433    ) -> Result<UserAuthToken, OperationError> {
434        let ClientAuthInfo {
435            client_cert,
436            bearer_token,
437            source: _,
438            basic_authz: _,
439        } = client_auth_info;
440
441        match (client_cert, bearer_token) {
442            (Some(client_cert_info), _) => {
443                self.client_certificate_to_user_auth_token(&client_cert_info, ct)
444            }
445            (None, Some(token)) => match self.validate_and_parse_token_to_token(&token, ct)? {
446                Token::UserAuthToken(uat) => Ok(uat),
447                Token::ApiToken(_apit, _entry) => {
448                    warn!("Unable to process non user auth token");
449                    Err(OperationError::NotAuthenticated)
450                }
451            },
452            (None, None) => {
453                debug!("No client certificate or bearer tokens were supplied");
454                Err(OperationError::NotAuthenticated)
455            }
456        }
457    }
458
459    fn validate_and_parse_token_to_token(
460        &mut self,
461        jwsu: &JwsCompact,
462        ct: Duration,
463    ) -> Result<Token, OperationError> {
464        // Our key objects now handle this logic and determine the correct key
465        // from the input type.
466        let jws_inner = self
467            .get_qs_txn()
468            .get_domain_key_object_handle()?
469            .jws_verify(jwsu)
470            .map_err(|err| {
471                security_info!(?err, "Unable to verify token");
472                OperationError::NotAuthenticated
473            })?;
474
475        // Is it a UAT?
476        if let Ok(uat) = jws_inner.from_json::<UserAuthToken>() {
477            if let Some(exp) = uat.expiry {
478                let ct_odt = time::OffsetDateTime::UNIX_EPOCH + ct;
479                if exp < ct_odt {
480                    security_info!(?ct_odt, ?exp, "Session expired");
481                    return Err(OperationError::SessionExpired);
482                } else {
483                    trace!(?ct_odt, ?exp, "Session not yet expired");
484                    return Ok(Token::UserAuthToken(uat));
485                }
486            } else {
487                debug!("Session has no expiry");
488                return Ok(Token::UserAuthToken(uat));
489            }
490        };
491
492        // Is it an API Token?
493        if let Ok(apit) = jws_inner.from_json::<ApiToken>() {
494            if let Some(expiry) = apit.expiry {
495                if time::OffsetDateTime::UNIX_EPOCH + ct >= expiry {
496                    security_info!("Session expired");
497                    return Err(OperationError::SessionExpired);
498                }
499            }
500
501            let entry = self
502                .get_qs_txn()
503                .internal_search_uuid(apit.account_id)
504                .map_err(|err| {
505                    security_info!(?err, "Account associated with api token no longer exists.");
506                    OperationError::NotAuthenticated
507                })?;
508
509            return Ok(Token::ApiToken(apit, entry));
510        };
511
512        security_info!("Unable to verify token, invalid inner JSON");
513        Err(OperationError::NotAuthenticated)
514    }
515
516    fn check_oauth2_account_uuid_valid(
517        &mut self,
518        uuid: Uuid,
519        session_id: Uuid,
520        parent_session_id: Option<Uuid>,
521        iat: i64,
522        ct: Duration,
523    ) -> Result<Option<Arc<Entry<EntrySealed, EntryCommitted>>>, OperationError> {
524        let entry = self.get_qs_txn().internal_search_uuid(uuid).map_err(|e| {
525            admin_error!(?e, "check_oauth2_account_uuid_valid failed");
526            e
527        })?;
528
529        let within_valid_window = Account::check_within_valid_time(
530            ct,
531            entry
532                .get_ava_single_datetime(Attribute::AccountValidFrom)
533                .as_ref(),
534            entry
535                .get_ava_single_datetime(Attribute::AccountExpire)
536                .as_ref(),
537        );
538
539        if !within_valid_window {
540            security_info!("Account has expired or is not yet valid, not allowing to proceed");
541            return Ok(None);
542        }
543
544        // We are past the grace window. Enforce session presence.
545        // We enforce both sessions are present in case of inconsistency
546        // that may occur with replication.
547
548        let grace_valid = ct < (Duration::from_secs(iat as u64) + AUTH_TOKEN_GRACE_WINDOW);
549
550        let oauth2_session = entry
551            .get_ava_as_oauth2session_map(Attribute::OAuth2Session)
552            .and_then(|sessions| sessions.get(&session_id));
553
554        if let Some(oauth2_session) = oauth2_session {
555            // We have the oauth2 session, lets check it.
556            let oauth2_session_valid = !matches!(oauth2_session.state, SessionState::RevokedAt(_));
557
558            if !oauth2_session_valid {
559                security_info!("The oauth2 session associated to this token is revoked.");
560                return Ok(None);
561            }
562
563            // Do we have a parent session? If yes, we need to enforce it's presence.
564            if let Some(parent_session_id) = parent_session_id {
565                let uat_session = entry
566                    .get_ava_as_session_map(Attribute::UserAuthTokenSession)
567                    .and_then(|sessions| sessions.get(&parent_session_id));
568
569                if let Some(uat_session) = uat_session {
570                    let parent_session_valid =
571                        !matches!(uat_session.state, SessionState::RevokedAt(_));
572                    if parent_session_valid {
573                        security_info!(
574                            "A valid parent and oauth2 session value exists for this token"
575                        );
576                    } else {
577                        security_info!(
578                            "The parent oauth2 session associated to this token is revoked."
579                        );
580                        return Ok(None);
581                    }
582                } else if grace_valid {
583                    security_info!(
584                        "The token grace window is in effect. Assuming parent session valid."
585                    );
586                } else {
587                    security_info!("The token grace window has passed and no entry parent sessions exist. Assuming invalid.");
588                    return Ok(None);
589                }
590            }
591            // If we don't have a parent session id, we are good to proceed.
592        } else if grace_valid {
593            security_info!("The token grace window is in effect. Assuming valid.");
594        } else {
595            security_info!(
596                "The token grace window has passed and no entry sessions exist. Assuming invalid."
597            );
598            return Ok(None);
599        }
600
601        Ok(Some(entry))
602    }
603
604    /// For any event/operation to proceed, we need to attach an identity to the
605    /// event for security and access processing. When that event is externally
606    /// triggered via one of our various api layers, we process some type of
607    /// account token into this identity. In the current server this is the
608    /// UserAuthToken. For a UserAuthToken to be provided it MUST have been
609    /// cryptographically verified meaning it is now a *trusted* source of
610    /// data that we previously issued.
611    ///
612    /// This is the function that is responsible for converting that UAT into
613    /// something we can pin access controls and other limits and references to.
614    /// This is why it is the location where validity windows are checked and other
615    /// relevant session information is injected.
616    #[instrument(level = "debug", skip_all)]
617    fn process_uat_to_identity(
618        &mut self,
619        uat: &UserAuthToken,
620        ct: Duration,
621        source: Source,
622    ) -> Result<Identity, OperationError> {
623        // From a UAT, get the current identity and associated information.
624        let entry = self
625            .get_qs_txn()
626            .internal_search_uuid(uat.uuid)
627            .map_err(|e| {
628                admin_error!(?e, "from_ro_uat failed");
629                e
630            })?;
631
632        let valid = Account::check_user_auth_token_valid(ct, uat, &entry);
633
634        if !valid {
635            return Err(OperationError::SessionExpired);
636        }
637
638        // ✅  Session is valid! Start to setup for it to be used.
639
640        let scope = match uat.purpose {
641            UatPurpose::ReadOnly => AccessScope::ReadOnly,
642            UatPurpose::ReadWrite { expiry: None } => AccessScope::ReadOnly,
643            UatPurpose::ReadWrite {
644                expiry: Some(expiry),
645            } => {
646                let cot = time::OffsetDateTime::UNIX_EPOCH + ct;
647                if cot < expiry {
648                    AccessScope::ReadWrite
649                } else {
650                    AccessScope::ReadOnly
651                }
652            }
653        };
654
655        let mut limits = Limits::default();
656        // Apply the limits from the uat
657        if let Some(lim) = uat.limit_search_max_results.and_then(|v| v.try_into().ok()) {
658            limits.search_max_results = lim;
659        }
660        if let Some(lim) = uat
661            .limit_search_max_filter_test
662            .and_then(|v| v.try_into().ok())
663        {
664            limits.search_max_filter_test = lim;
665        }
666
667        // #64: Now apply claims from the uat into the Entry
668        // to allow filtering.
669        /*
670        entry.insert_claim(match &uat.auth_type {
671            AuthType::Anonymous => "authtype_anonymous",
672            AuthType::UnixPassword => "authtype_unixpassword",
673            AuthType::Password => "authtype_password",
674            AuthType::GeneratedPassword => "authtype_generatedpassword",
675            AuthType::Webauthn => "authtype_webauthn",
676            AuthType::PasswordMfa => "authtype_passwordmfa",
677        });
678
679        trace!(claims = ?entry.get_ava_set("claim"), "Applied claims");
680        */
681
682        Ok(Identity::new(
683            IdentType::User(IdentUser { entry }),
684            source,
685            uat.session_id,
686            scope,
687            limits,
688        ))
689    }
690
691    #[instrument(level = "debug", skip_all)]
692    fn process_apit_to_identity(
693        &mut self,
694        apit: &ApiToken,
695        source: Source,
696        entry: Arc<EntrySealedCommitted>,
697        ct: Duration,
698    ) -> Result<Identity, OperationError> {
699        let valid = ServiceAccount::check_api_token_valid(ct, apit, &entry);
700
701        if !valid {
702            // Check_api token logs this.
703            return Err(OperationError::SessionExpired);
704        }
705
706        let scope = (&apit.purpose).into();
707
708        let limits = Limits::api_token();
709        Ok(Identity::new(
710            IdentType::User(IdentUser { entry }),
711            source,
712            apit.token_id,
713            scope,
714            limits,
715        ))
716    }
717
718    fn client_cert_info_entry(
719        &mut self,
720        client_cert_info: &ClientCertInfo,
721    ) -> Result<Arc<EntrySealedCommitted>, OperationError> {
722        let pks256 = hex::encode(client_cert_info.public_key_s256);
723        // Using the certificate hash, find our matching cert.
724        let mut maybe_cert_entries = self.get_qs_txn().internal_search(filter!(f_eq(
725            Attribute::Certificate,
726            PartialValue::HexString(pks256.clone())
727        )))?;
728
729        let maybe_cert_entry = maybe_cert_entries.pop();
730
731        if let Some(cert_entry) = maybe_cert_entry {
732            if maybe_cert_entries.is_empty() {
733                Ok(cert_entry)
734            } else {
735                debug!(?pks256, "Multiple certificates matched, unable to proceed.");
736                Err(OperationError::NotAuthenticated)
737            }
738        } else {
739            debug!(?pks256, "No certificates were able to be mapped.");
740            Err(OperationError::NotAuthenticated)
741        }
742    }
743
744    /// Given a certificate, validate it and discover the associated entry that
745    /// the certificate relates to. Currently, this relies on mapping the public
746    /// key sha256 to a stored client certificate, which then links to the owner.
747    ///
748    /// In the future we *could* consider alternate mapping strategies such as
749    /// subjectAltName or subject DN, but these have subtle security risks and
750    /// configuration challenges, so binary mapping is the simplest - and safest -
751    /// option today.
752    #[instrument(level = "debug", skip_all)]
753    fn client_certificate_to_identity(
754        &mut self,
755        client_cert_info: &ClientCertInfo,
756        ct: Duration,
757        source: Source,
758    ) -> Result<Identity, OperationError> {
759        let cert_entry = self.client_cert_info_entry(client_cert_info)?;
760
761        // This is who the certificate belongs to.
762        let refers_uuid = cert_entry
763            .get_ava_single_refer(Attribute::Refers)
764            .ok_or_else(|| {
765                warn!("Invalid certificate entry, missing refers");
766                OperationError::InvalidState
767            })?;
768
769        // Now get the related entry.
770        let entry = self.get_qs_txn().internal_search_uuid(refers_uuid)?;
771
772        let (account, account_policy) =
773            Account::try_from_entry_with_policy(entry.as_ref(), self.get_qs_txn())?;
774
775        // Is the account in it's valid window?
776        if !account.is_within_valid_time(ct) {
777            // Nope, expired
778            return Err(OperationError::SessionExpired);
779        };
780
781        // scope is related to the cert. For now, default to RO.
782        let scope = AccessScope::ReadOnly;
783
784        let mut limits = Limits::default();
785        // Apply the limits from the account policy
786        if let Some(lim) = account_policy
787            .limit_search_max_results()
788            .and_then(|v| v.try_into().ok())
789        {
790            limits.search_max_results = lim;
791        }
792        if let Some(lim) = account_policy
793            .limit_search_max_filter_test()
794            .and_then(|v| v.try_into().ok())
795        {
796            limits.search_max_filter_test = lim;
797        }
798
799        let certificate_uuid = cert_entry.get_uuid();
800
801        Ok(Identity::new(
802            IdentType::User(IdentUser { entry }),
803            source,
804            // session_id is the certificate uuid.
805            certificate_uuid,
806            scope,
807            limits,
808        ))
809    }
810
811    #[instrument(level = "debug", skip_all)]
812    fn client_certificate_to_user_auth_token(
813        &mut self,
814        client_cert_info: &ClientCertInfo,
815        ct: Duration,
816    ) -> Result<UserAuthToken, OperationError> {
817        let cert_entry = self.client_cert_info_entry(client_cert_info)?;
818
819        // This is who the certificate belongs to.
820        let refers_uuid = cert_entry
821            .get_ava_single_refer(Attribute::Refers)
822            .ok_or_else(|| {
823                warn!("Invalid certificate entry, missing refers");
824                OperationError::InvalidState
825            })?;
826
827        // Now get the related entry.
828        let entry = self.get_qs_txn().internal_search_uuid(refers_uuid)?;
829
830        let (account, account_policy) =
831            Account::try_from_entry_with_policy(entry.as_ref(), self.get_qs_txn())?;
832
833        // Is the account in it's valid window?
834        if !account.is_within_valid_time(ct) {
835            // Nope, expired
836            return Err(OperationError::SessionExpired);
837        };
838
839        let certificate_uuid = cert_entry.get_uuid();
840        let session_is_rw = false;
841
842        account
843            .client_cert_info_to_userauthtoken(certificate_uuid, session_is_rw, ct, &account_policy)
844            .ok_or(OperationError::InvalidState)
845    }
846
847    fn process_ldap_uuid_to_identity(
848        &mut self,
849        uuid: &Uuid,
850        ct: Duration,
851        source: Source,
852    ) -> Result<Identity, OperationError> {
853        let entry = self
854            .get_qs_txn()
855            .internal_search_uuid(*uuid)
856            .map_err(|err| {
857                error!(?err, ?uuid, "Failed to search user by uuid");
858                err
859            })?;
860
861        let (account, account_policy) =
862            Account::try_from_entry_with_policy(entry.as_ref(), self.get_qs_txn())?;
863
864        if !account.is_within_valid_time(ct) {
865            info!("Account is expired or not yet valid.");
866            return Err(OperationError::SessionExpired);
867        }
868
869        // Good to go
870        let anon_entry = if *uuid == UUID_ANONYMOUS {
871            // We already have it.
872            entry
873        } else {
874            // Pull the anon entry for mapping the identity.
875            self.get_qs_txn()
876                .internal_search_uuid(UUID_ANONYMOUS)
877                .map_err(|err| {
878                    error!(
879                        ?err,
880                        "Unable to search anonymous user for privilege bounding."
881                    );
882                    err
883                })?
884        };
885
886        let mut limits = Limits::default();
887        let session_id = Uuid::new_v4();
888
889        // Update limits from account policy
890        if let Some(max_results) = account_policy.limit_search_max_results() {
891            limits.search_max_results = max_results as usize;
892        }
893        if let Some(max_filter) = account_policy.limit_search_max_filter_test() {
894            limits.search_max_filter_test = max_filter as usize;
895        }
896
897        // Users via LDAP are always only granted anonymous rights unless
898        // they auth with an api-token
899        Ok(Identity::new(
900            IdentType::User(IdentUser { entry: anon_entry }),
901            source,
902            session_id,
903            AccessScope::ReadOnly,
904            limits,
905        ))
906    }
907
908    #[instrument(level = "debug", skip_all)]
909    fn validate_ldap_session(
910        &mut self,
911        session: &LdapSession,
912        source: Source,
913        ct: Duration,
914    ) -> Result<Identity, OperationError> {
915        match session {
916            LdapSession::UnixBind(uuid) | LdapSession::ApplicationPasswordBind(_, uuid) => {
917                self.process_ldap_uuid_to_identity(uuid, ct, source)
918            }
919            LdapSession::UserAuthToken(uat) => self.process_uat_to_identity(uat, ct, source),
920            LdapSession::ApiToken(apit) => {
921                let entry = self
922                    .get_qs_txn()
923                    .internal_search_uuid(apit.account_id)
924                    .map_err(|e| {
925                        admin_error!("Failed to validate ldap session -> {:?}", e);
926                        e
927                    })?;
928
929                self.process_apit_to_identity(apit, source, entry, ct)
930            }
931        }
932    }
933
934    #[instrument(level = "info", skip_all)]
935    fn validate_sync_client_auth_info_to_ident(
936        &mut self,
937        client_auth_info: ClientAuthInfo,
938        ct: Duration,
939    ) -> Result<Identity, OperationError> {
940        // FUTURE: Could allow mTLS here instead?
941
942        let jwsu = client_auth_info.bearer_token.ok_or_else(|| {
943            security_info!("No token provided");
944            OperationError::NotAuthenticated
945        })?;
946
947        let jws_inner = self
948            .get_qs_txn()
949            .get_domain_key_object_handle()?
950            .jws_verify(&jwsu)
951            .map_err(|err| {
952                security_info!(?err, "Unable to verify token");
953                OperationError::NotAuthenticated
954            })?;
955
956        let sync_token = jws_inner.from_json::<ScimSyncToken>().map_err(|err| {
957            error!(?err, "Unable to deserialise JWS");
958            OperationError::SerdeJsonError
959        })?;
960
961        let entry = self
962            .get_qs_txn()
963            .internal_search(filter!(f_eq(
964                Attribute::SyncTokenSession,
965                PartialValue::Refer(sync_token.token_id)
966            )))
967            .and_then(|mut vs| match vs.pop() {
968                Some(entry) if vs.is_empty() => Ok(entry),
969                _ => {
970                    admin_error!(
971                        token_id = ?sync_token.token_id,
972                        "entries was empty, or matched multiple results for token id"
973                    );
974                    Err(OperationError::NotAuthenticated)
975                }
976            })?;
977
978        let valid = SyncAccount::check_sync_token_valid(ct, &sync_token, &entry);
979
980        if !valid {
981            security_info!("Unable to proceed with invalid sync token");
982            return Err(OperationError::NotAuthenticated);
983        }
984
985        // If scope is not Synchronise, then fail.
986        let scope = (&sync_token.purpose).into();
987
988        let limits = Limits::unlimited();
989        Ok(Identity::new(
990            IdentType::Synch(entry.get_uuid()),
991            client_auth_info.source,
992            sync_token.token_id,
993            scope,
994            limits,
995        ))
996    }
997}
998
999impl<'a> IdmServerTransaction<'a> for IdmServerAuthTransaction<'a> {
1000    type QsTransactionType = QueryServerReadTransaction<'a>;
1001
1002    fn get_qs_txn(&mut self) -> &mut Self::QsTransactionType {
1003        &mut self.qs_read
1004    }
1005}
1006
1007impl IdmServerAuthTransaction<'_> {
1008    #[cfg(test)]
1009    pub fn is_sessionid_present(&self, sessionid: Uuid) -> bool {
1010        let session_read = self.sessions.read();
1011        session_read.contains_key(&sessionid)
1012    }
1013
1014    pub fn get_origin(&self) -> &Url {
1015        #[allow(clippy::unwrap_used)]
1016        self.webauthn.get_allowed_origins().first().unwrap()
1017    }
1018
1019    #[instrument(level = "trace", skip(self))]
1020    pub async fn expire_auth_sessions(&mut self, ct: Duration) {
1021        // ct is current time - sub the timeout. and then split.
1022        let expire = ct - Duration::from_secs(AUTH_SESSION_TIMEOUT);
1023        let split_at = uuid_from_duration(expire, self.sid);
1024        // Removes older sessions in place.
1025        let _session_ticket = self.session_ticket.acquire().await;
1026        let mut session_write = self.sessions.write();
1027        session_write.split_off_lt(&split_at);
1028        // expired will now be dropped, and can't be used by future sessions.
1029        session_write.commit();
1030    }
1031
1032    pub async fn auth(
1033        &mut self,
1034        ae: &AuthEvent,
1035        ct: Duration,
1036        client_auth_info: ClientAuthInfo,
1037    ) -> Result<AuthResult, OperationError> {
1038        // Match on the auth event, to see what we need to do.
1039        match &ae.step {
1040            AuthEventStep::Init(init) => {
1041                // lperf_segment!("idm::server::auth<Init>", || {
1042                // Allocate a session id, based on current time.
1043                let sessionid = uuid_from_duration(ct, self.sid);
1044
1045                // Begin the auth procedure!
1046                // Start a read
1047                //
1048                // Actually we may not need this - at the time we issue the auth-init
1049                // we could generate the uat, the nonce and cache hashes in memory,
1050                // then this can just be fully without a txn.
1051                //
1052                // We do need a txn so that we can process/search and claims
1053                // or related based on the quality of the provided auth steps
1054                //
1055                // We *DO NOT* need a write though, because I think that lock outs
1056                // and rate limits are *per server* and *in memory* only.
1057                //
1058                // Check anything needed? Get the current auth-session-id from request
1059                // because it associates to the nonce's etc which were all cached.
1060                let euuid = self.qs_read.name_to_uuid(init.username.as_str())?;
1061
1062                // Get the first / single entry we expect here ....
1063                let entry = self.qs_read.internal_search_uuid(euuid)?;
1064
1065                security_info!(
1066                    username = %init.username,
1067                    issue = ?init.issue,
1068                    privileged = ?init.privileged,
1069                    uuid = %euuid,
1070                    "Initiating Authentication Session",
1071                );
1072
1073                // Now, convert the Entry to an account - this gives us some stronger
1074                // typing and functionality so we can assess what auth types can
1075                // continue, and helps to keep non-needed entry specific data
1076                // out of the session tree.
1077                let (account, account_policy) =
1078                    Account::try_from_entry_with_policy(entry.as_ref(), &mut self.qs_read)?;
1079
1080                trace!(?account.primary);
1081
1082                // Intent to take both trees to write.
1083                let _session_ticket = self.session_ticket.acquire().await;
1084
1085                // We don't actually check the softlock here. We just initialise
1086                // it under the write lock we currently have, so that we can validate
1087                // it once we understand what auth mech we will be using.
1088                //
1089                // NOTE: Very careful use of await here to avoid an issue with write.
1090                let _maybe_slock_ref =
1091                    account
1092                        .primary_cred_uuid_and_policy()
1093                        .map(|(cred_uuid, policy)| {
1094                            // Acquire the softlock map
1095                            //
1096                            // We have no issue calling this with .write here, since we
1097                            // already hold the session_ticket above.
1098                            let mut softlock_write = self.softlocks.write();
1099                            let slock_ref: CredSoftLockMutex =
1100                                if let Some(slock_ref) = softlock_write.get(&cred_uuid) {
1101                                    slock_ref.clone()
1102                                } else {
1103                                    // Create if not exist, and the cred type supports softlocking.
1104                                    let slock = Arc::new(Mutex::new(CredSoftLock::new(policy)));
1105                                    softlock_write.insert(cred_uuid, slock.clone());
1106                                    slock
1107                                };
1108                            softlock_write.commit();
1109                            slock_ref
1110                        });
1111
1112                let asd: AuthSessionData = AuthSessionData {
1113                    account,
1114                    account_policy,
1115                    issue: init.issue,
1116                    webauthn: self.webauthn,
1117                    ct,
1118                    client_auth_info,
1119                };
1120
1121                let domain_keys = self.qs_read.get_domain_key_object_handle()?;
1122
1123                let (auth_session, state) = AuthSession::new(asd, init.privileged, domain_keys);
1124
1125                match auth_session {
1126                    Some(auth_session) => {
1127                        let mut session_write = self.sessions.write();
1128                        if session_write.contains_key(&sessionid) {
1129                            // If we have a session of the same id, return an error (despite how
1130                            // unlikely this is ...
1131                            Err(OperationError::InvalidSessionState)
1132                        } else {
1133                            session_write.insert(sessionid, Arc::new(Mutex::new(auth_session)));
1134                            // Debugging: ensure we really inserted ...
1135                            debug_assert!(session_write.get(&sessionid).is_some());
1136                            Ok(())
1137                        }?;
1138                        session_write.commit();
1139                    }
1140                    None => {
1141                        security_info!("Authentication Session Unable to begin");
1142                    }
1143                };
1144
1145                Ok(AuthResult { sessionid, state })
1146            } // AuthEventStep::Init
1147            AuthEventStep::Begin(mech) => {
1148                let session_read = self.sessions.read();
1149                // Do we have a session?
1150                let auth_session_ref = session_read
1151                    // Why is the session missing?
1152                    .get(&mech.sessionid)
1153                    .cloned()
1154                    .ok_or_else(|| {
1155                        admin_error!("Invalid Session State (no present session uuid)");
1156                        OperationError::InvalidSessionState
1157                    })?;
1158
1159                let mut auth_session = auth_session_ref.lock().await;
1160
1161                // Indicate to the session which auth mech we now want to proceed with.
1162                let auth_result = auth_session.start_session(&mech.mech);
1163
1164                let is_valid = match auth_session.get_credential_uuid()? {
1165                    Some(cred_uuid) => {
1166                        // From the auth_session, determine if the current account
1167                        // credential that we are using has become softlocked or not.
1168                        let softlock_read = self.softlocks.read();
1169                        if let Some(slock_ref) = softlock_read.get(&cred_uuid) {
1170                            let mut slock = slock_ref.lock().await;
1171                            // Apply the current time.
1172                            slock.apply_time_step(ct);
1173                            // Now check the results
1174                            slock.is_valid()
1175                        } else {
1176                            trace!("slock not found");
1177                            false
1178                        }
1179                    }
1180                    None => true,
1181                };
1182
1183                if is_valid {
1184                    auth_result
1185                } else {
1186                    // Fail the session
1187                    trace!("lock step begin");
1188                    auth_session.end_session("Account is temporarily locked")
1189                }
1190                .map(|aus| AuthResult {
1191                    sessionid: mech.sessionid,
1192                    state: aus,
1193                })
1194            } // End AuthEventStep::Mech
1195            AuthEventStep::Cred(creds) => {
1196                // lperf_segment!("idm::server::auth<Creds>", || {
1197                // let _session_ticket = self.session_ticket.acquire().await;
1198
1199                let session_read = self.sessions.read();
1200                // Do we have a session?
1201                let auth_session_ref = session_read
1202                    // Why is the session missing?
1203                    .get(&creds.sessionid)
1204                    .cloned()
1205                    .ok_or_else(|| {
1206                        admin_error!("Invalid Session State (no present session uuid)");
1207                        OperationError::InvalidSessionState
1208                    })?;
1209
1210                let mut auth_session = auth_session_ref.lock().await;
1211
1212                let maybe_slock_ref = match auth_session.get_credential_uuid()? {
1213                    Some(cred_uuid) => {
1214                        let softlock_read = self.softlocks.read();
1215                        softlock_read.get(&cred_uuid).cloned()
1216                    }
1217                    None => None,
1218                };
1219
1220                // From the auth_session, determine if the current account
1221                // credential that we are using has become softlocked or not.
1222                let mut maybe_slock = if let Some(s) = maybe_slock_ref.as_ref() {
1223                    Some(s.lock().await)
1224                } else {
1225                    None
1226                };
1227
1228                let is_valid = if let Some(ref mut slock) = maybe_slock {
1229                    // Apply the current time.
1230                    slock.apply_time_step(ct);
1231                    // Now check the results
1232                    slock.is_valid()
1233                } else {
1234                    // No slock is present for this cred_uuid
1235                    true
1236                };
1237
1238                if is_valid {
1239                    // Process the credentials here as required.
1240                    // Basically throw them at the auth_session and see what
1241                    // falls out.
1242                    auth_session
1243                        .validate_creds(
1244                            &creds.cred,
1245                            ct,
1246                            &self.async_tx,
1247                            &self.audit_tx,
1248                            self.webauthn,
1249                            self.qs_read.pw_badlist(),
1250                        )
1251                        .inspect(|aus| {
1252                            // Inspect the result:
1253                            // if it was a failure, we need to inc the softlock.
1254                            if let AuthState::Denied(_) = aus {
1255                                // Update it.
1256                                if let Some(ref mut slock) = maybe_slock {
1257                                    slock.record_failure(ct);
1258                                }
1259                            };
1260                        })
1261                } else {
1262                    // Fail the session
1263                    trace!("lock step cred");
1264                    auth_session.end_session("Account is temporarily locked")
1265                }
1266                .map(|aus| AuthResult {
1267                    sessionid: creds.sessionid,
1268                    state: aus,
1269                })
1270            } // End AuthEventStep::Cred
1271        }
1272    }
1273
1274    async fn auth_with_unix_pass(
1275        &mut self,
1276        id: Uuid,
1277        cleartext: &str,
1278        ct: Duration,
1279    ) -> Result<Option<Account>, OperationError> {
1280        let entry = match self.qs_read.internal_search_uuid(id) {
1281            Ok(entry) => entry,
1282            Err(e) => {
1283                admin_error!("Failed to start auth unix -> {:?}", e);
1284                return Err(e);
1285            }
1286        };
1287
1288        let (account, acp) =
1289            Account::try_from_entry_with_policy(entry.as_ref(), &mut self.qs_read)?;
1290
1291        if !account.is_within_valid_time(ct) {
1292            security_info!("Account is expired or not yet valid.");
1293            return Ok(None);
1294        }
1295
1296        let cred = if acp.allow_primary_cred_fallback() == Some(true) {
1297            account
1298                .unix_extn()
1299                .and_then(|extn| extn.ucred())
1300                .or_else(|| account.primary())
1301        } else {
1302            account.unix_extn().and_then(|extn| extn.ucred())
1303        };
1304
1305        let (cred, cred_id, cred_slock_policy) = match cred {
1306            None => {
1307                if acp.allow_primary_cred_fallback() == Some(true) {
1308                    security_info!("Account does not have a POSIX or primary password configured.");
1309                } else {
1310                    security_info!("Account does not have a POSIX password configured.");
1311                }
1312                return Ok(None);
1313            }
1314            Some(cred) => (cred, cred.uuid, cred.softlock_policy()),
1315        };
1316
1317        // The credential should only ever be a password
1318        let Ok(password) = cred.password_ref() else {
1319            error!("User's UNIX or primary credential is not a password, can't authenticate!");
1320            return Err(OperationError::InvalidState);
1321        };
1322
1323        let slock_ref = {
1324            let softlock_read = self.softlocks.read();
1325            if let Some(slock_ref) = softlock_read.get(&cred_id) {
1326                slock_ref.clone()
1327            } else {
1328                let _session_ticket = self.session_ticket.acquire().await;
1329                let mut softlock_write = self.softlocks.write();
1330                let slock = Arc::new(Mutex::new(CredSoftLock::new(cred_slock_policy)));
1331                softlock_write.insert(cred_id, slock.clone());
1332                softlock_write.commit();
1333                slock
1334            }
1335        };
1336
1337        let mut slock = slock_ref.lock().await;
1338
1339        slock.apply_time_step(ct);
1340
1341        if !slock.is_valid() {
1342            security_info!("Account is softlocked.");
1343            return Ok(None);
1344        }
1345
1346        // Check the provided password against the stored hash
1347        let valid = password.verify(cleartext).map_err(|e| {
1348            error!(crypto_err = ?e);
1349            e.into()
1350        })?;
1351
1352        if !valid {
1353            // Update it.
1354            slock.record_failure(ct);
1355
1356            return Ok(None);
1357        }
1358
1359        security_info!("Successfully authenticated with unix (or primary) password");
1360        if password.requires_upgrade() {
1361            self.async_tx
1362                .send(DelayedAction::UnixPwUpgrade(UnixPasswordUpgrade {
1363                    target_uuid: id,
1364                    existing_password: cleartext.to_string(),
1365                }))
1366                .map_err(|_| {
1367                    admin_error!("failed to queue delayed action - unix password upgrade");
1368                    OperationError::InvalidState
1369                })?;
1370        }
1371
1372        Ok(Some(account))
1373    }
1374
1375    pub async fn auth_unix(
1376        &mut self,
1377        uae: &UnixUserAuthEvent,
1378        ct: Duration,
1379    ) -> Result<Option<UnixUserToken>, OperationError> {
1380        Ok(self
1381            .auth_with_unix_pass(uae.target, &uae.cleartext, ct)
1382            .await?
1383            .and_then(|acc| acc.to_unixusertoken(ct).ok()))
1384    }
1385
1386    pub async fn auth_ldap(
1387        &mut self,
1388        lae: &LdapAuthEvent,
1389        ct: Duration,
1390    ) -> Result<Option<LdapBoundToken>, OperationError> {
1391        if lae.target == UUID_ANONYMOUS {
1392            let account_entry = self.qs_read.internal_search_uuid(lae.target).map_err(|e| {
1393                admin_error!("Failed to start auth ldap -> {:?}", e);
1394                e
1395            })?;
1396
1397            let account = Account::try_from_entry_ro(account_entry.as_ref(), &mut self.qs_read)?;
1398
1399            // Check if the anon account has been locked.
1400            if !account.is_within_valid_time(ct) {
1401                security_info!("Account is not within valid time period");
1402                return Ok(None);
1403            }
1404
1405            let session_id = Uuid::new_v4();
1406            security_info!(
1407                "Starting session {} for {} {}",
1408                session_id,
1409                account.spn,
1410                account.uuid
1411            );
1412
1413            // Account must be anon, so we can gen the uat.
1414            Ok(Some(LdapBoundToken {
1415                session_id,
1416                spn: account.spn,
1417                effective_session: LdapSession::UnixBind(UUID_ANONYMOUS),
1418            }))
1419        } else {
1420            if !self.qs_read.d_info.d_ldap_allow_unix_pw_bind {
1421                security_info!("Bind not allowed through Unix passwords.");
1422                return Ok(None);
1423            }
1424
1425            let auth = self
1426                .auth_with_unix_pass(lae.target, &lae.cleartext, ct)
1427                .await?;
1428
1429            match auth {
1430                Some(account) => {
1431                    let session_id = Uuid::new_v4();
1432                    security_info!(
1433                        "Starting session {} for {} {}",
1434                        session_id,
1435                        account.spn,
1436                        account.uuid
1437                    );
1438
1439                    Ok(Some(LdapBoundToken {
1440                        spn: account.spn,
1441                        session_id,
1442                        effective_session: LdapSession::UnixBind(account.uuid),
1443                    }))
1444                }
1445                None => Ok(None),
1446            }
1447        }
1448    }
1449
1450    pub async fn token_auth_ldap(
1451        &mut self,
1452        lae: &LdapTokenAuthEvent,
1453        ct: Duration,
1454    ) -> Result<Option<LdapBoundToken>, OperationError> {
1455        match self.validate_and_parse_token_to_token(&lae.token, ct)? {
1456            Token::UserAuthToken(uat) => {
1457                let spn = uat.spn.clone();
1458                Ok(Some(LdapBoundToken {
1459                    session_id: uat.session_id,
1460                    spn,
1461                    effective_session: LdapSession::UserAuthToken(uat),
1462                }))
1463            }
1464            Token::ApiToken(apit, entry) => {
1465                let spn = entry
1466                    .get_ava_single_proto_string(Attribute::Spn)
1467                    .ok_or_else(|| OperationError::MissingAttribute(Attribute::Spn))?;
1468
1469                Ok(Some(LdapBoundToken {
1470                    session_id: apit.token_id,
1471                    spn,
1472                    effective_session: LdapSession::ApiToken(apit),
1473                }))
1474            }
1475        }
1476    }
1477
1478    pub fn commit(self) -> Result<(), OperationError> {
1479        Ok(())
1480    }
1481}
1482
1483impl<'a> IdmServerTransaction<'a> for IdmServerProxyReadTransaction<'a> {
1484    type QsTransactionType = QueryServerReadTransaction<'a>;
1485
1486    fn get_qs_txn(&mut self) -> &mut Self::QsTransactionType {
1487        &mut self.qs_read
1488    }
1489}
1490
1491fn gen_password_mod(
1492    cleartext: &str,
1493    crypto_policy: &CryptoPolicy,
1494) -> Result<ModifyList<ModifyInvalid>, OperationError> {
1495    let new_cred = Credential::new_password_only(crypto_policy, cleartext)?;
1496    let cred_value = Value::new_credential("unix", new_cred);
1497    Ok(ModifyList::new_purge_and_set(
1498        Attribute::UnixPassword,
1499        cred_value,
1500    ))
1501}
1502
1503fn gen_password_upgrade_mod(
1504    unix_cred: &Credential,
1505    cleartext: &str,
1506    crypto_policy: &CryptoPolicy,
1507) -> Result<Option<ModifyList<ModifyInvalid>>, OperationError> {
1508    if let Some(new_cred) = unix_cred.upgrade_password(crypto_policy, cleartext)? {
1509        let cred_value = Value::new_credential("primary", new_cred);
1510        Ok(Some(ModifyList::new_purge_and_set(
1511            Attribute::UnixPassword,
1512            cred_value,
1513        )))
1514    } else {
1515        // No action, not the same pw
1516        Ok(None)
1517    }
1518}
1519
1520impl IdmServerProxyReadTransaction<'_> {
1521    pub fn jws_public_jwk(&mut self, key_id: &str) -> Result<Jwk, OperationError> {
1522        self.qs_read
1523            .get_key_providers()
1524            .get_key_object_handle(UUID_DOMAIN_INFO)
1525            // If there is no domain info, error.
1526            .ok_or(OperationError::NoMatchingEntries)
1527            .and_then(|key_object| key_object.jws_public_jwk(key_id))
1528            .and_then(|maybe_key: Option<Jwk>| maybe_key.ok_or(OperationError::NoMatchingEntries))
1529    }
1530
1531    pub fn get_radiusauthtoken(
1532        &mut self,
1533        rate: &RadiusAuthTokenEvent,
1534        ct: Duration,
1535    ) -> Result<RadiusAuthToken, OperationError> {
1536        let account = self
1537            .qs_read
1538            .impersonate_search_ext_uuid(rate.target, &rate.ident)
1539            .and_then(|account_entry| {
1540                RadiusAccount::try_from_entry_reduced(&account_entry, &mut self.qs_read)
1541            })
1542            .map_err(|e| {
1543                admin_error!("Failed to start radius auth token {:?}", e);
1544                e
1545            })?;
1546
1547        account.to_radiusauthtoken(ct)
1548    }
1549
1550    pub fn get_unixusertoken(
1551        &mut self,
1552        uute: &UnixUserTokenEvent,
1553        ct: Duration,
1554    ) -> Result<UnixUserToken, OperationError> {
1555        let account = self
1556            .qs_read
1557            .impersonate_search_uuid(uute.target, &uute.ident)
1558            .and_then(|account_entry| Account::try_from_entry_ro(&account_entry, &mut self.qs_read))
1559            .map_err(|e| {
1560                admin_error!("Failed to start unix user token -> {:?}", e);
1561                e
1562            })?;
1563
1564        account.to_unixusertoken(ct)
1565    }
1566
1567    pub fn get_unixgrouptoken(
1568        &mut self,
1569        uute: &UnixGroupTokenEvent,
1570    ) -> Result<UnixGroupToken, OperationError> {
1571        let group = self
1572            .qs_read
1573            .impersonate_search_ext_uuid(uute.target, &uute.ident)
1574            .and_then(|e| Group::<Unix>::try_from_entry(&e))
1575            .map_err(|e| {
1576                admin_error!("Failed to start unix group token {:?}", e);
1577                e
1578            })?;
1579        Ok(group.to_unixgrouptoken())
1580    }
1581
1582    pub fn get_credentialstatus(
1583        &mut self,
1584        cse: &CredentialStatusEvent,
1585    ) -> Result<CredentialStatus, OperationError> {
1586        let account = self
1587            .qs_read
1588            .impersonate_search_ext_uuid(cse.target, &cse.ident)
1589            .and_then(|account_entry| {
1590                Account::try_from_entry_reduced(&account_entry, &mut self.qs_read)
1591            })
1592            .map_err(|e| {
1593                admin_error!("Failed to search account {:?}", e);
1594                e
1595            })?;
1596
1597        account.to_credentialstatus()
1598    }
1599
1600    pub fn get_backup_codes(
1601        &mut self,
1602        rbce: &ReadBackupCodeEvent,
1603    ) -> Result<BackupCodesView, OperationError> {
1604        let account = self
1605            .qs_read
1606            .impersonate_search_ext_uuid(rbce.target, &rbce.ident)
1607            .and_then(|account_entry| {
1608                Account::try_from_entry_reduced(&account_entry, &mut self.qs_read)
1609            })
1610            .map_err(|e| {
1611                admin_error!("Failed to search account {:?}", e);
1612                e
1613            })?;
1614
1615        account.to_backupcodesview()
1616    }
1617}
1618
1619impl<'a> IdmServerTransaction<'a> for IdmServerProxyWriteTransaction<'a> {
1620    type QsTransactionType = QueryServerWriteTransaction<'a>;
1621
1622    fn get_qs_txn(&mut self) -> &mut Self::QsTransactionType {
1623        &mut self.qs_write
1624    }
1625}
1626
1627impl IdmServerProxyWriteTransaction<'_> {
1628    pub(crate) fn crypto_policy(&self) -> &CryptoPolicy {
1629        self.crypto_policy
1630    }
1631
1632    pub fn get_origin(&self) -> &Url {
1633        #[allow(clippy::unwrap_used)]
1634        self.webauthn.get_allowed_origins().first().unwrap()
1635    }
1636
1637    fn check_password_quality(
1638        &mut self,
1639        cleartext: &str,
1640        related_inputs: &[&str],
1641    ) -> Result<(), OperationError> {
1642        // password strength and badlisting is always global, rather than per-pw-policy.
1643        // pw-policy as check on the account is about requirements for mfa for example.
1644        //
1645
1646        // is the password at least 10 char?
1647        if cleartext.len() < PW_MIN_LENGTH as usize {
1648            return Err(OperationError::PasswordQuality(vec![
1649                PasswordFeedback::TooShort(PW_MIN_LENGTH),
1650            ]));
1651        }
1652
1653        // does the password pass zxcvbn?
1654
1655        let entropy = zxcvbn(cleartext, related_inputs);
1656
1657        // Unix PW's are a single factor, so we enforce good pws
1658        if entropy.score() < Score::Four {
1659            // The password is too week as per:
1660            // https://docs.rs/zxcvbn/2.0.0/zxcvbn/struct.Entropy.html
1661            let feedback: zxcvbn::feedback::Feedback = entropy
1662                .feedback()
1663                .ok_or(OperationError::InvalidState)
1664                .cloned()
1665                .inspect_err(|err| {
1666                    security_info!(?err, "zxcvbn returned no feedback when score < 3");
1667                })?;
1668
1669            security_info!(?feedback, "pw quality feedback");
1670
1671            // return Err(OperationError::PasswordTooWeak(feedback))
1672            // return Err(OperationError::PasswordTooWeak);
1673            return Err(OperationError::PasswordQuality(vec![
1674                PasswordFeedback::BadListed,
1675            ]));
1676        }
1677
1678        // check a password badlist to eliminate more content
1679        // we check the password as "lower case" to help eliminate possibilities
1680        // also, when pw_badlist_cache is read from DB, it is read as Value (iutf8 lowercase)
1681        if self
1682            .qs_write
1683            .pw_badlist()
1684            .contains(&cleartext.to_lowercase())
1685        {
1686            security_info!("Password found in badlist, rejecting");
1687            Err(OperationError::PasswordQuality(vec![
1688                PasswordFeedback::BadListed,
1689            ]))
1690        } else {
1691            Ok(())
1692        }
1693    }
1694
1695    pub(crate) fn target_to_account(&mut self, target: Uuid) -> Result<Account, OperationError> {
1696        // Get the account
1697        let account = self
1698            .qs_write
1699            .internal_search_uuid(target)
1700            .and_then(|account_entry| {
1701                Account::try_from_entry_rw(&account_entry, &mut self.qs_write)
1702            })
1703            .map_err(|e| {
1704                admin_error!("Failed to search account {:?}", e);
1705                e
1706            })?;
1707        // Ask if tis all good - this step checks pwpolicy and such
1708
1709        // Deny the change if the account is anonymous!
1710        if account.is_anonymous() {
1711            admin_warn!("Unable to convert anonymous to account during write txn");
1712            Err(OperationError::SystemProtectedObject)
1713        } else {
1714            Ok(account)
1715        }
1716    }
1717
1718    #[cfg(test)]
1719    pub(crate) fn set_account_password(
1720        &mut self,
1721        pce: &PasswordChangeEvent,
1722    ) -> Result<(), OperationError> {
1723        let account = self.target_to_account(pce.target)?;
1724
1725        // Get the modifications we *want* to perform.
1726        let modlist = account
1727            .gen_password_mod(pce.cleartext.as_str(), self.crypto_policy)
1728            .map_err(|e| {
1729                admin_error!("Failed to generate password mod {:?}", e);
1730                e
1731            })?;
1732        trace!(?modlist, "processing change");
1733
1734        // Check with the QS if we would be ALLOWED to do this change.
1735
1736        let me = self
1737            .qs_write
1738            .impersonate_modify_gen_event(
1739                // Filter as executed
1740                &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(pce.target))),
1741                // Filter as intended (acp)
1742                &filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(pce.target))),
1743                &modlist,
1744                &pce.ident,
1745            )
1746            .map_err(|e| {
1747                request_error!(error = ?e);
1748                e
1749            })?;
1750
1751        let mp = self
1752            .qs_write
1753            .modify_pre_apply(&me)
1754            .and_then(|opt_mp| opt_mp.ok_or(OperationError::NoMatchingEntries))
1755            .map_err(|e| {
1756                request_error!(error = ?e);
1757                e
1758            })?;
1759
1760        // If we got here, then pre-apply succeeded, and that means access control
1761        // passed. Now we can do the extra checks.
1762
1763        // And actually really apply it now.
1764        self.qs_write.modify_apply(mp).map_err(|e| {
1765            request_error!(error = ?e);
1766            e
1767        })?;
1768
1769        Ok(())
1770    }
1771
1772    pub fn set_unix_account_password(
1773        &mut self,
1774        pce: &UnixPasswordChangeEvent,
1775    ) -> Result<(), OperationError> {
1776        // Get the account
1777        let account = self
1778            .qs_write
1779            .internal_search_uuid(pce.target)
1780            .and_then(|account_entry| {
1781                // Assert the account is unix and valid.
1782                Account::try_from_entry_rw(&account_entry, &mut self.qs_write)
1783            })
1784            .map_err(|e| {
1785                admin_error!("Failed to start set unix account password {:?}", e);
1786                e
1787            })?;
1788
1789        // Account is not a unix account
1790        if account.unix_extn().is_none() {
1791            return Err(OperationError::MissingClass(
1792                ENTRYCLASS_POSIX_ACCOUNT.into(),
1793            ));
1794        }
1795
1796        // Deny the change if the account is anonymous!
1797        if account.is_anonymous() {
1798            trace!("Unable to use anonymous to change UNIX account password");
1799            return Err(OperationError::SystemProtectedObject);
1800        }
1801
1802        let modlist =
1803            gen_password_mod(pce.cleartext.as_str(), self.crypto_policy).map_err(|e| {
1804                admin_error!(?e, "Unable to generate password change modlist");
1805                e
1806            })?;
1807        trace!(?modlist, "processing change");
1808
1809        // Check with the QS if we would be ALLOWED to do this change.
1810
1811        let me = self
1812            .qs_write
1813            .impersonate_modify_gen_event(
1814                // Filter as executed
1815                &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(pce.target))),
1816                // Filter as intended (acp)
1817                &filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(pce.target))),
1818                &modlist,
1819                &pce.ident,
1820            )
1821            .map_err(|e| {
1822                request_error!(error = ?e);
1823                e
1824            })?;
1825
1826        let mp = self
1827            .qs_write
1828            .modify_pre_apply(&me)
1829            .and_then(|opt_mp| opt_mp.ok_or(OperationError::NoMatchingEntries))
1830            .map_err(|e| {
1831                request_error!(error = ?e);
1832                e
1833            })?;
1834
1835        // If we got here, then pre-apply succeeded, and that means access control
1836        // passed. Now we can do the extra checks.
1837
1838        self.check_password_quality(pce.cleartext.as_str(), account.related_inputs().as_slice())
1839            .map_err(|e| {
1840                admin_error!(?e, "Failed to checked password quality");
1841                e
1842            })?;
1843
1844        // And actually really apply it now.
1845        self.qs_write.modify_apply(mp).map_err(|e| {
1846            request_error!(error = ?e);
1847            e
1848        })?;
1849
1850        Ok(())
1851    }
1852
1853    #[instrument(level = "debug", skip_all)]
1854    pub fn recover_account(
1855        &mut self,
1856        name: &str,
1857        cleartext: Option<&str>,
1858    ) -> Result<String, OperationError> {
1859        // name to uuid
1860        let target = self.qs_write.name_to_uuid(name).map_err(|e| {
1861            admin_error!(?e, "name to uuid failed");
1862            e
1863        })?;
1864
1865        let cleartext = cleartext
1866            .map(|s| s.to_string())
1867            .unwrap_or_else(password_from_random);
1868
1869        let ncred = Credential::new_generatedpassword_only(self.crypto_policy, &cleartext)
1870            .map_err(|e| {
1871                admin_error!("Unable to generate password mod {:?}", e);
1872                e
1873            })?;
1874        let vcred = Value::new_credential("primary", ncred);
1875        // We need to remove other credentials too.
1876        let modlist = ModifyList::new_list(vec![
1877            m_purge(Attribute::PassKeys),
1878            m_purge(Attribute::PrimaryCredential),
1879            Modify::Present(Attribute::PrimaryCredential, vcred),
1880        ]);
1881
1882        trace!(?modlist, "processing change");
1883
1884        self.qs_write
1885            .internal_modify(
1886                // Filter as executed
1887                &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(target))),
1888                &modlist,
1889            )
1890            .map_err(|e| {
1891                request_error!(error = ?e);
1892                e
1893            })?;
1894
1895        Ok(cleartext)
1896    }
1897
1898    #[instrument(level = "debug", skip_all)]
1899    pub fn regenerate_radius_secret(
1900        &mut self,
1901        rrse: &RegenerateRadiusSecretEvent,
1902    ) -> Result<String, OperationError> {
1903        let account = self.target_to_account(rrse.target)?;
1904
1905        // Difference to the password above, this is intended to be read/copied
1906        // by a human wiath a keyboard in some cases.
1907        let cleartext = readable_password_from_random();
1908
1909        // Create a modlist from the change.
1910        let modlist = account
1911            .regenerate_radius_secret_mod(cleartext.as_str())
1912            .map_err(|e| {
1913                admin_error!("Unable to generate radius secret mod {:?}", e);
1914                e
1915            })?;
1916        trace!(?modlist, "processing change");
1917
1918        // Apply it.
1919        self.qs_write
1920            .impersonate_modify(
1921                // Filter as executed
1922                &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(rrse.target))),
1923                // Filter as intended (acp)
1924                &filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(rrse.target))),
1925                &modlist,
1926                // Provide the event to impersonate
1927                &rrse.ident,
1928            )
1929            .map_err(|e| {
1930                request_error!(error = ?e);
1931                e
1932            })
1933            .map(|_| cleartext)
1934    }
1935
1936    // -- delayed action processing --
1937    #[instrument(level = "debug", skip_all)]
1938    fn process_pwupgrade(&mut self, pwu: &PasswordUpgrade) -> Result<(), OperationError> {
1939        // get the account
1940        let account = self.target_to_account(pwu.target_uuid)?;
1941
1942        info!(session_id = %pwu.target_uuid, "Processing password hash upgrade");
1943
1944        let maybe_modlist = account
1945            .gen_password_upgrade_mod(pwu.existing_password.as_str(), self.crypto_policy)
1946            .map_err(|e| {
1947                admin_error!("Unable to generate password mod {:?}", e);
1948                e
1949            })?;
1950
1951        if let Some(modlist) = maybe_modlist {
1952            self.qs_write.internal_modify(
1953                &filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(pwu.target_uuid))),
1954                &modlist,
1955            )
1956        } else {
1957            // No action needed, it's probably been changed/updated already.
1958            Ok(())
1959        }
1960    }
1961
1962    #[instrument(level = "debug", skip_all)]
1963    fn process_unixpwupgrade(&mut self, pwu: &UnixPasswordUpgrade) -> Result<(), OperationError> {
1964        info!(session_id = %pwu.target_uuid, "Processing unix password hash upgrade");
1965
1966        let account = self
1967            .qs_write
1968            .internal_search_uuid(pwu.target_uuid)
1969            .and_then(|account_entry| {
1970                Account::try_from_entry_rw(&account_entry, &mut self.qs_write)
1971            })
1972            .map_err(|e| {
1973                admin_error!("Failed to start unix pw upgrade -> {:?}", e);
1974                e
1975            })?;
1976
1977        let cred = match account.unix_extn() {
1978            Some(ue) => ue.ucred(),
1979            None => {
1980                return Err(OperationError::MissingClass(
1981                    ENTRYCLASS_POSIX_ACCOUNT.into(),
1982                ));
1983            }
1984        };
1985
1986        // No credential no problem
1987        let Some(cred) = cred else {
1988            return Ok(());
1989        };
1990
1991        let maybe_modlist =
1992            gen_password_upgrade_mod(cred, pwu.existing_password.as_str(), self.crypto_policy)?;
1993
1994        match maybe_modlist {
1995            Some(modlist) => self.qs_write.internal_modify(
1996                &filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(pwu.target_uuid))),
1997                &modlist,
1998            ),
1999            None => Ok(()),
2000        }
2001    }
2002
2003    #[instrument(level = "debug", skip_all)]
2004    pub(crate) fn process_webauthncounterinc(
2005        &mut self,
2006        wci: &WebauthnCounterIncrement,
2007    ) -> Result<(), OperationError> {
2008        info!(session_id = %wci.target_uuid, "Processing webauthn counter increment");
2009
2010        let mut account = self.target_to_account(wci.target_uuid)?;
2011
2012        // Generate an optional mod and then attempt to apply it.
2013        let opt_modlist = account
2014            .gen_webauthn_counter_mod(&wci.auth_result)
2015            .map_err(|e| {
2016                admin_error!("Unable to generate webauthn counter mod {:?}", e);
2017                e
2018            })?;
2019
2020        if let Some(modlist) = opt_modlist {
2021            self.qs_write.internal_modify(
2022                &filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(wci.target_uuid))),
2023                &modlist,
2024            )
2025        } else {
2026            // No mod needed.
2027            trace!("No modification required");
2028            Ok(())
2029        }
2030    }
2031
2032    #[instrument(level = "debug", skip_all)]
2033    pub(crate) fn process_backupcoderemoval(
2034        &mut self,
2035        bcr: &BackupCodeRemoval,
2036    ) -> Result<(), OperationError> {
2037        info!(session_id = %bcr.target_uuid, "Processing backup code removal");
2038
2039        let account = self.target_to_account(bcr.target_uuid)?;
2040        // Generate an optional mod and then attempt to apply it.
2041        let modlist = account
2042            .invalidate_backup_code_mod(&bcr.code_to_remove)
2043            .map_err(|e| {
2044                admin_error!("Unable to generate backup code mod {:?}", e);
2045                e
2046            })?;
2047
2048        self.qs_write.internal_modify(
2049            &filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(bcr.target_uuid))),
2050            &modlist,
2051        )
2052    }
2053
2054    #[instrument(level = "debug", skip_all)]
2055    pub(crate) fn process_authsessionrecord(
2056        &mut self,
2057        asr: &AuthSessionRecord,
2058    ) -> Result<(), OperationError> {
2059        // We have to get the entry so we can work out if we need to expire any of it's sessions.
2060        let state = match asr.expiry {
2061            Some(e) => SessionState::ExpiresAt(e),
2062            None => SessionState::NeverExpires,
2063        };
2064
2065        let session = Value::Session(
2066            asr.session_id,
2067            Session {
2068                label: asr.label.clone(),
2069                state,
2070                // Need the other inner bits?
2071                // for the gracewindow.
2072                issued_at: asr.issued_at,
2073                // Who actually created this?
2074                issued_by: asr.issued_by.clone(),
2075                // Which credential was used?
2076                cred_id: asr.cred_id,
2077                // What is the access scope of this session? This is
2078                // for auditing purposes.
2079                scope: asr.scope,
2080                type_: asr.type_,
2081            },
2082        );
2083
2084        info!(session_id = %asr.session_id, "Persisting auth session");
2085
2086        // modify the account to put the session onto it.
2087        let modlist = ModifyList::new_append(Attribute::UserAuthTokenSession, session);
2088
2089        self.qs_write
2090            .internal_modify(
2091                &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(asr.target_uuid))),
2092                &modlist,
2093            )
2094            .map_err(|e| {
2095                admin_error!("Failed to persist user auth token {:?}", e);
2096                e
2097            })
2098        // Done!
2099    }
2100
2101    #[instrument(level = "debug", skip_all)]
2102    pub fn process_delayedaction(
2103        &mut self,
2104        da: &DelayedAction,
2105        _ct: Duration,
2106    ) -> Result<(), OperationError> {
2107        match da {
2108            DelayedAction::PwUpgrade(pwu) => self.process_pwupgrade(pwu),
2109            DelayedAction::UnixPwUpgrade(upwu) => self.process_unixpwupgrade(upwu),
2110            DelayedAction::WebauthnCounterIncrement(wci) => self.process_webauthncounterinc(wci),
2111            DelayedAction::BackupCodeRemoval(bcr) => self.process_backupcoderemoval(bcr),
2112            DelayedAction::AuthSessionRecord(asr) => self.process_authsessionrecord(asr),
2113        }
2114    }
2115
2116    #[instrument(level = "debug", skip_all)]
2117    pub fn commit(mut self) -> Result<(), OperationError> {
2118        // The problem we have here is that we need the qs_write layer to reload *first*
2119        // so that things like schema and key objects are ready.
2120        self.qs_write.reload()?;
2121
2122        // Now that's done, let's proceed.
2123
2124        if self.qs_write.get_changed_app() {
2125            self.qs_write
2126                .get_applications_set()
2127                .and_then(|application_set| self.applications.reload(application_set))?;
2128        }
2129        if self.qs_write.get_changed_oauth2() {
2130            let domain_level = self.qs_write.get_domain_version();
2131            self.qs_write.get_oauth2rs_set().and_then(|oauth2rs_set| {
2132                let key_providers = self.qs_write.get_key_providers();
2133                self.oauth2rs
2134                    .reload(oauth2rs_set, key_providers, domain_level)
2135            })?;
2136            // Clear the flag to indicate we completed the reload.
2137            self.qs_write.clear_changed_oauth2();
2138        }
2139
2140        // Commit everything.
2141        self.applications.commit();
2142        self.oauth2rs.commit();
2143        self.cred_update_sessions.commit();
2144
2145        trace!("cred_update_session.commit");
2146        self.qs_write.commit()
2147    }
2148
2149    #[instrument(level = "debug", skip_all)]
2150    pub fn generate_application_password(
2151        &mut self,
2152        ev: &GenerateApplicationPasswordEvent,
2153    ) -> Result<String, OperationError> {
2154        let account = self.target_to_account(ev.target)?;
2155
2156        // This is intended to be read/copied by a human
2157        let cleartext = readable_password_from_random();
2158
2159        // Create a modlist from the change
2160        let modlist = account
2161            .generate_application_password_mod(
2162                ev.application,
2163                ev.label.as_str(),
2164                cleartext.as_str(),
2165                self.crypto_policy,
2166            )
2167            .map_err(|e| {
2168                admin_error!("Unable to generate application password mod {:?}", e);
2169                e
2170            })?;
2171        trace!(?modlist, "processing change");
2172        // Apply it
2173        self.qs_write
2174            .impersonate_modify(
2175                // Filter as executed
2176                &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(ev.target))),
2177                // Filter as intended (acp)
2178                &filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(ev.target))),
2179                &modlist,
2180                // Provide the event to impersonate
2181                &ev.ident,
2182            )
2183            .map_err(|e| {
2184                error!(error = ?e);
2185                e
2186            })
2187            .map(|_| cleartext)
2188    }
2189}
2190
2191// Need tests of the sessions and the auth ...
2192
2193#[cfg(test)]
2194mod tests {
2195    use std::convert::TryFrom;
2196    use std::time::Duration;
2197
2198    use kanidm_proto::v1::{AuthAllowed, AuthIssueSession, AuthMech};
2199    use time::OffsetDateTime;
2200    use uuid::Uuid;
2201
2202    use crate::credential::{Credential, Password};
2203    use crate::idm::account::DestroySessionTokenEvent;
2204    use crate::idm::accountpolicy::ResolvedAccountPolicy;
2205    use crate::idm::audit::AuditEvent;
2206    use crate::idm::delayed::{AuthSessionRecord, DelayedAction};
2207    use crate::idm::event::{AuthEvent, AuthResult};
2208    use crate::idm::event::{
2209        LdapAuthEvent, PasswordChangeEvent, RadiusAuthTokenEvent, RegenerateRadiusSecretEvent,
2210        UnixGroupTokenEvent, UnixPasswordChangeEvent, UnixUserAuthEvent, UnixUserTokenEvent,
2211    };
2212
2213    use crate::idm::server::{IdmServer, IdmServerTransaction, Token};
2214    use crate::idm::AuthState;
2215    use crate::modify::{Modify, ModifyList};
2216    use crate::prelude::*;
2217    use crate::server::keys::KeyProvidersTransaction;
2218    use crate::value::{AuthType, SessionState};
2219    use compact_jwt::{traits::JwsVerifiable, JwsCompact, JwsEs256Verifier, JwsVerifier};
2220    use kanidm_lib_crypto::CryptoPolicy;
2221
2222    const TEST_PASSWORD: &str = "ntaoeuntnaoeuhraohuercahu😍";
2223    const TEST_PASSWORD_INC: &str = "ntaoentu nkrcgaeunhibwmwmqj;k wqjbkx ";
2224    const TEST_CURRENT_TIME: u64 = 6000;
2225
2226    #[idm_test]
2227    async fn test_idm_anonymous_auth(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
2228        // Start and test anonymous auth.
2229        let mut idms_auth = idms.auth().await.unwrap();
2230        // Send the initial auth event for initialising the session
2231        let anon_init = AuthEvent::anonymous_init();
2232        // Expect success
2233        let r1 = idms_auth
2234            .auth(
2235                &anon_init,
2236                Duration::from_secs(TEST_CURRENT_TIME),
2237                Source::Internal.into(),
2238            )
2239            .await;
2240        /* Some weird lifetime things happen here ... */
2241
2242        let sid = match r1 {
2243            Ok(ar) => {
2244                let AuthResult { sessionid, state } = ar;
2245                match state {
2246                    AuthState::Choose(mut conts) => {
2247                        // Should only be one auth mech
2248                        assert_eq!(conts.len(), 1);
2249                        // And it should be anonymous
2250                        let m = conts.pop().expect("Should not fail");
2251                        assert_eq!(m, AuthMech::Anonymous);
2252                    }
2253                    _ => {
2254                        error!("A critical error has occurred! We have a non-continue result!");
2255                        panic!();
2256                    }
2257                };
2258                // Now pass back the sessionid, we are good to continue.
2259                sessionid
2260            }
2261            Err(e) => {
2262                // Should not occur!
2263                error!("A critical error has occurred! {:?}", e);
2264                panic!();
2265            }
2266        };
2267
2268        debug!("sessionid is ==> {:?}", sid);
2269
2270        idms_auth.commit().expect("Must not fail");
2271
2272        let mut idms_auth = idms.auth().await.unwrap();
2273        let anon_begin = AuthEvent::begin_mech(sid, AuthMech::Anonymous);
2274
2275        let r2 = idms_auth
2276            .auth(
2277                &anon_begin,
2278                Duration::from_secs(TEST_CURRENT_TIME),
2279                Source::Internal.into(),
2280            )
2281            .await;
2282        debug!("r2 ==> {:?}", r2);
2283
2284        match r2 {
2285            Ok(ar) => {
2286                let AuthResult {
2287                    sessionid: _,
2288                    state,
2289                } = ar;
2290
2291                match state {
2292                    AuthState::Continue(allowed) => {
2293                        // Check the uat.
2294                        assert_eq!(allowed.len(), 1);
2295                        assert_eq!(allowed.first(), Some(&AuthAllowed::Anonymous));
2296                    }
2297                    _ => {
2298                        error!("A critical error has occurred! We have a non-continue result!");
2299                        panic!();
2300                    }
2301                }
2302            }
2303            Err(e) => {
2304                error!("A critical error has occurred! {:?}", e);
2305                // Should not occur!
2306                panic!();
2307            }
2308        };
2309
2310        idms_auth.commit().expect("Must not fail");
2311
2312        let mut idms_auth = idms.auth().await.unwrap();
2313        // Now send the anonymous request, given the session id.
2314        let anon_step = AuthEvent::cred_step_anonymous(sid);
2315
2316        // Expect success
2317        let r2 = idms_auth
2318            .auth(
2319                &anon_step,
2320                Duration::from_secs(TEST_CURRENT_TIME),
2321                Source::Internal.into(),
2322            )
2323            .await;
2324        debug!("r2 ==> {:?}", r2);
2325
2326        match r2 {
2327            Ok(ar) => {
2328                let AuthResult {
2329                    sessionid: _,
2330                    state,
2331                } = ar;
2332
2333                match state {
2334                    AuthState::Success(_uat, AuthIssueSession::Token) => {
2335                        // Check the uat.
2336                    }
2337                    _ => {
2338                        error!("A critical error has occurred! We have a non-success result!");
2339                        panic!();
2340                    }
2341                }
2342            }
2343            Err(e) => {
2344                error!("A critical error has occurred! {:?}", e);
2345                // Should not occur!
2346                panic!();
2347            }
2348        };
2349
2350        idms_auth.commit().expect("Must not fail");
2351    }
2352
2353    // Test sending anonymous but with no session init.
2354    #[idm_test]
2355    async fn test_idm_anonymous_auth_invalid_states(
2356        idms: &IdmServer,
2357        _idms_delayed: &IdmServerDelayed,
2358    ) {
2359        {
2360            let mut idms_auth = idms.auth().await.unwrap();
2361            let sid = Uuid::new_v4();
2362            let anon_step = AuthEvent::cred_step_anonymous(sid);
2363
2364            // Expect failure
2365            let r2 = idms_auth
2366                .auth(
2367                    &anon_step,
2368                    Duration::from_secs(TEST_CURRENT_TIME),
2369                    Source::Internal.into(),
2370                )
2371                .await;
2372            debug!("r2 ==> {:?}", r2);
2373
2374            match r2 {
2375                Ok(_) => {
2376                    error!("Auth state machine not correctly enforced!");
2377                    panic!();
2378                }
2379                Err(e) => match e {
2380                    OperationError::InvalidSessionState => {}
2381                    _ => panic!(),
2382                },
2383            };
2384        }
2385    }
2386
2387    async fn init_testperson_w_password(
2388        idms: &IdmServer,
2389        pw: &str,
2390    ) -> Result<Uuid, OperationError> {
2391        let p = CryptoPolicy::minimum();
2392        let cred = Credential::new_password_only(&p, pw)?;
2393        let cred_id = cred.uuid;
2394        let v_cred = Value::new_credential("primary", cred);
2395        let mut idms_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
2396
2397        idms_write
2398            .qs_write
2399            .internal_create(vec![E_TESTPERSON_1.clone()])
2400            .expect("Failed to create test person");
2401
2402        // now modify and provide a primary credential.
2403        let me_inv_m = ModifyEvent::new_internal_invalid(
2404            filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_TESTPERSON_1))),
2405            ModifyList::new_list(vec![Modify::Present(Attribute::PrimaryCredential, v_cred)]),
2406        );
2407        // go!
2408        assert!(idms_write.qs_write.modify(&me_inv_m).is_ok());
2409
2410        idms_write.commit().map(|()| cred_id)
2411    }
2412
2413    async fn init_authsession_sid(idms: &IdmServer, ct: Duration, name: &str) -> Uuid {
2414        let mut idms_auth = idms.auth().await.unwrap();
2415        let admin_init = AuthEvent::named_init(name);
2416
2417        let r1 = idms_auth
2418            .auth(&admin_init, ct, Source::Internal.into())
2419            .await;
2420        let ar = r1.unwrap();
2421        let AuthResult { sessionid, state } = ar;
2422
2423        assert!(matches!(state, AuthState::Choose(_)));
2424
2425        // Now push that we want the Password Mech.
2426        let admin_begin = AuthEvent::begin_mech(sessionid, AuthMech::Password);
2427
2428        let r2 = idms_auth
2429            .auth(&admin_begin, ct, Source::Internal.into())
2430            .await;
2431        let ar = r2.unwrap();
2432        let AuthResult { sessionid, state } = ar;
2433
2434        match state {
2435            AuthState::Continue(_) => {}
2436            s => {
2437                error!(?s, "Sessions was not initialised");
2438                panic!();
2439            }
2440        };
2441
2442        idms_auth.commit().expect("Must not fail");
2443
2444        sessionid
2445    }
2446
2447    async fn check_testperson_password(idms: &IdmServer, pw: &str, ct: Duration) -> JwsCompact {
2448        let sid = init_authsession_sid(idms, ct, "testperson1").await;
2449
2450        let mut idms_auth = idms.auth().await.unwrap();
2451        let anon_step = AuthEvent::cred_step_password(sid, pw);
2452
2453        // Expect success
2454        let r2 = idms_auth
2455            .auth(&anon_step, ct, Source::Internal.into())
2456            .await;
2457        debug!("r2 ==> {:?}", r2);
2458
2459        let token = match r2 {
2460            Ok(ar) => {
2461                let AuthResult {
2462                    sessionid: _,
2463                    state,
2464                } = ar;
2465
2466                match state {
2467                    AuthState::Success(token, AuthIssueSession::Token) => {
2468                        // Check the uat.
2469                        token
2470                    }
2471                    _ => {
2472                        error!("A critical error has occurred! We have a non-success result!");
2473                        panic!();
2474                    }
2475                }
2476            }
2477            Err(e) => {
2478                error!("A critical error has occurred! {:?}", e);
2479                // Should not occur!
2480                panic!();
2481            }
2482        };
2483
2484        idms_auth.commit().expect("Must not fail");
2485
2486        *token
2487    }
2488
2489    #[idm_test]
2490    async fn test_idm_simple_password_auth(idms: &IdmServer, idms_delayed: &mut IdmServerDelayed) {
2491        let ct = duration_from_epoch_now();
2492        init_testperson_w_password(idms, TEST_PASSWORD)
2493            .await
2494            .expect("Failed to setup admin account");
2495        check_testperson_password(idms, TEST_PASSWORD, ct).await;
2496
2497        // Clear our the session record
2498        let da = idms_delayed.try_recv().expect("invalid");
2499        assert!(matches!(da, DelayedAction::AuthSessionRecord(_)));
2500        idms_delayed.check_is_empty_or_panic();
2501    }
2502
2503    #[idm_test]
2504    async fn test_idm_simple_password_spn_auth(
2505        idms: &IdmServer,
2506        idms_delayed: &mut IdmServerDelayed,
2507    ) {
2508        init_testperson_w_password(idms, TEST_PASSWORD)
2509            .await
2510            .expect("Failed to setup admin account");
2511
2512        let sid = init_authsession_sid(
2513            idms,
2514            Duration::from_secs(TEST_CURRENT_TIME),
2515            "testperson1@example.com",
2516        )
2517        .await;
2518
2519        let mut idms_auth = idms.auth().await.unwrap();
2520        let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD);
2521
2522        // Expect success
2523        let r2 = idms_auth
2524            .auth(
2525                &anon_step,
2526                Duration::from_secs(TEST_CURRENT_TIME),
2527                Source::Internal.into(),
2528            )
2529            .await;
2530        debug!("r2 ==> {:?}", r2);
2531
2532        match r2 {
2533            Ok(ar) => {
2534                let AuthResult {
2535                    sessionid: _,
2536                    state,
2537                } = ar;
2538                match state {
2539                    AuthState::Success(_uat, AuthIssueSession::Token) => {
2540                        // Check the uat.
2541                    }
2542                    _ => {
2543                        error!("A critical error has occurred! We have a non-success result!");
2544                        panic!();
2545                    }
2546                }
2547            }
2548            Err(e) => {
2549                error!("A critical error has occurred! {:?}", e);
2550                // Should not occur!
2551                panic!();
2552            }
2553        };
2554
2555        // Clear our the session record
2556        let da = idms_delayed.try_recv().expect("invalid");
2557        assert!(matches!(da, DelayedAction::AuthSessionRecord(_)));
2558        idms_delayed.check_is_empty_or_panic();
2559
2560        idms_auth.commit().expect("Must not fail");
2561    }
2562
2563    #[idm_test(audit = 1)]
2564    async fn test_idm_simple_password_invalid(
2565        idms: &IdmServer,
2566        _idms_delayed: &IdmServerDelayed,
2567        idms_audit: &mut IdmServerAudit,
2568    ) {
2569        init_testperson_w_password(idms, TEST_PASSWORD)
2570            .await
2571            .expect("Failed to setup admin account");
2572        let sid =
2573            init_authsession_sid(idms, Duration::from_secs(TEST_CURRENT_TIME), "testperson1").await;
2574        let mut idms_auth = idms.auth().await.unwrap();
2575        let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD_INC);
2576
2577        // Expect success
2578        let r2 = idms_auth
2579            .auth(
2580                &anon_step,
2581                Duration::from_secs(TEST_CURRENT_TIME),
2582                Source::Internal.into(),
2583            )
2584            .await;
2585        debug!("r2 ==> {:?}", r2);
2586
2587        match r2 {
2588            Ok(ar) => {
2589                let AuthResult {
2590                    sessionid: _,
2591                    state,
2592                } = ar;
2593                match state {
2594                    AuthState::Denied(_reason) => {
2595                        // Check the uat.
2596                    }
2597                    _ => {
2598                        error!("A critical error has occurred! We have a non-denied result!");
2599                        panic!();
2600                    }
2601                }
2602            }
2603            Err(e) => {
2604                error!("A critical error has occurred! {:?}", e);
2605                // Should not occur!
2606                panic!();
2607            }
2608        };
2609
2610        // There should be a queued audit event
2611        match idms_audit.audit_rx().try_recv() {
2612            Ok(AuditEvent::AuthenticationDenied { .. }) => {}
2613            _ => panic!("Oh no"),
2614        }
2615
2616        idms_auth.commit().expect("Must not fail");
2617    }
2618
2619    #[idm_test]
2620    async fn test_idm_simple_password_reset(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
2621        let pce = PasswordChangeEvent::new_internal(UUID_ADMIN, TEST_PASSWORD);
2622
2623        let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
2624        assert!(idms_prox_write.set_account_password(&pce).is_ok());
2625        assert!(idms_prox_write.set_account_password(&pce).is_ok());
2626        assert!(idms_prox_write.commit().is_ok());
2627    }
2628
2629    #[idm_test]
2630    async fn test_idm_anonymous_set_password_denied(
2631        idms: &IdmServer,
2632        _idms_delayed: &IdmServerDelayed,
2633    ) {
2634        let pce = PasswordChangeEvent::new_internal(UUID_ANONYMOUS, TEST_PASSWORD);
2635
2636        let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
2637        assert!(idms_prox_write.set_account_password(&pce).is_err());
2638        assert!(idms_prox_write.commit().is_ok());
2639    }
2640
2641    #[idm_test]
2642    async fn test_idm_regenerate_radius_secret(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
2643        let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
2644
2645        idms_prox_write
2646            .qs_write
2647            .internal_create(vec![E_TESTPERSON_1.clone()])
2648            .expect("unable to create test person");
2649
2650        let rrse = RegenerateRadiusSecretEvent::new_internal(UUID_TESTPERSON_1);
2651
2652        // Generates a new credential when none exists
2653        let r1 = idms_prox_write
2654            .regenerate_radius_secret(&rrse)
2655            .expect("Failed to reset radius credential 1");
2656        // Regenerates and overwrites the radius credential
2657        let r2 = idms_prox_write
2658            .regenerate_radius_secret(&rrse)
2659            .expect("Failed to reset radius credential 2");
2660        assert!(r1 != r2);
2661    }
2662
2663    #[idm_test]
2664    async fn test_idm_radiusauthtoken(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
2665        let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
2666
2667        idms_prox_write
2668            .qs_write
2669            .internal_create(vec![E_TESTPERSON_1.clone()])
2670            .expect("unable to create test person");
2671
2672        let rrse = RegenerateRadiusSecretEvent::new_internal(UUID_TESTPERSON_1);
2673        let r1 = idms_prox_write
2674            .regenerate_radius_secret(&rrse)
2675            .expect("Failed to reset radius credential 1");
2676        idms_prox_write.commit().expect("failed to commit");
2677
2678        let mut idms_prox_read = idms.proxy_read().await.unwrap();
2679        let person_entry = idms_prox_read
2680            .qs_read
2681            .internal_search_uuid(UUID_TESTPERSON_1)
2682            .expect("Can't access admin entry.");
2683
2684        let rate = RadiusAuthTokenEvent::new_impersonate(person_entry, UUID_TESTPERSON_1);
2685        let tok_r = idms_prox_read
2686            .get_radiusauthtoken(&rate, duration_from_epoch_now())
2687            .expect("Failed to generate radius auth token");
2688
2689        // view the token?
2690        assert_eq!(r1, tok_r.secret);
2691    }
2692
2693    #[idm_test]
2694    async fn test_idm_unixusertoken(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
2695        let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
2696        // Modify admin to have posixaccount
2697        let me_posix = ModifyEvent::new_internal_invalid(
2698            filter!(f_eq(Attribute::Name, PartialValue::new_iname("admin"))),
2699            ModifyList::new_list(vec![
2700                Modify::Present(Attribute::Class, EntryClass::PosixAccount.into()),
2701                Modify::Present(Attribute::GidNumber, Value::new_uint32(2001)),
2702            ]),
2703        );
2704        assert!(idms_prox_write.qs_write.modify(&me_posix).is_ok());
2705        // Add a posix group that has the admin as a member.
2706        let e: Entry<EntryInit, EntryNew> = entry_init!(
2707            (Attribute::Class, EntryClass::Object.to_value()),
2708            (Attribute::Class, EntryClass::Group.to_value()),
2709            (Attribute::Class, EntryClass::PosixGroup.to_value()),
2710            (Attribute::Name, Value::new_iname("testgroup")),
2711            (
2712                Attribute::Uuid,
2713                Value::Uuid(uuid::uuid!("01609135-a1c4-43d5-966b-a28227644445"))
2714            ),
2715            (Attribute::Description, Value::new_utf8s("testgroup")),
2716            (
2717                Attribute::Member,
2718                Value::Refer(uuid::uuid!("00000000-0000-0000-0000-000000000000"))
2719            )
2720        );
2721
2722        let ce = CreateEvent::new_internal(vec![e]);
2723
2724        assert!(idms_prox_write.qs_write.create(&ce).is_ok());
2725
2726        idms_prox_write.commit().expect("failed to commit");
2727
2728        let mut idms_prox_read = idms.proxy_read().await.unwrap();
2729
2730        // Get the account that will be doing the actual reads.
2731        let admin_entry = idms_prox_read
2732            .qs_read
2733            .internal_search_uuid(UUID_ADMIN)
2734            .expect("Can't access admin entry.");
2735
2736        let ugte = UnixGroupTokenEvent::new_impersonate(
2737            admin_entry.clone(),
2738            uuid!("01609135-a1c4-43d5-966b-a28227644445"),
2739        );
2740        let tok_g = idms_prox_read
2741            .get_unixgrouptoken(&ugte)
2742            .expect("Failed to generate unix group token");
2743
2744        assert_eq!(tok_g.name, "testgroup");
2745        assert_eq!(tok_g.spn, "testgroup@example.com");
2746
2747        let uute = UnixUserTokenEvent::new_internal(UUID_ADMIN);
2748        let tok_r = idms_prox_read
2749            .get_unixusertoken(&uute, duration_from_epoch_now())
2750            .expect("Failed to generate unix user token");
2751
2752        assert_eq!(tok_r.name, "admin");
2753        assert_eq!(tok_r.spn, "admin@example.com");
2754        assert_eq!(tok_r.groups.len(), 2);
2755        assert_eq!(tok_r.groups[0].name, "admin");
2756        assert_eq!(tok_r.groups[1].name, "testgroup");
2757        assert!(tok_r.valid);
2758
2759        // Show we can get the admin as a unix group token too
2760        let ugte = UnixGroupTokenEvent::new_impersonate(
2761            admin_entry,
2762            uuid!("00000000-0000-0000-0000-000000000000"),
2763        );
2764        let tok_g = idms_prox_read
2765            .get_unixgrouptoken(&ugte)
2766            .expect("Failed to generate unix group token");
2767
2768        assert_eq!(tok_g.name, "admin");
2769        assert_eq!(tok_g.spn, "admin@example.com");
2770    }
2771
2772    #[idm_test]
2773    async fn test_idm_simple_unix_password_reset(
2774        idms: &IdmServer,
2775        _idms_delayed: &IdmServerDelayed,
2776    ) {
2777        let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
2778        // make the admin a valid posix account
2779        let me_posix = ModifyEvent::new_internal_invalid(
2780            filter!(f_eq(Attribute::Name, PartialValue::new_iname("admin"))),
2781            ModifyList::new_list(vec![
2782                Modify::Present(Attribute::Class, EntryClass::PosixAccount.into()),
2783                Modify::Present(Attribute::GidNumber, Value::new_uint32(2001)),
2784            ]),
2785        );
2786        assert!(idms_prox_write.qs_write.modify(&me_posix).is_ok());
2787
2788        let pce = UnixPasswordChangeEvent::new_internal(UUID_ADMIN, TEST_PASSWORD);
2789
2790        assert!(idms_prox_write.set_unix_account_password(&pce).is_ok());
2791        assert!(idms_prox_write.commit().is_ok());
2792
2793        let mut idms_auth = idms.auth().await.unwrap();
2794        // Check auth verification of the password
2795
2796        let uuae_good = UnixUserAuthEvent::new_internal(UUID_ADMIN, TEST_PASSWORD);
2797        let a1 = idms_auth
2798            .auth_unix(&uuae_good, Duration::from_secs(TEST_CURRENT_TIME))
2799            .await;
2800        match a1 {
2801            Ok(Some(_tok)) => {}
2802            _ => panic!("Oh no"),
2803        };
2804        // Check bad password
2805        let uuae_bad = UnixUserAuthEvent::new_internal(UUID_ADMIN, TEST_PASSWORD_INC);
2806        let a2 = idms_auth
2807            .auth_unix(&uuae_bad, Duration::from_secs(TEST_CURRENT_TIME))
2808            .await;
2809        match a2 {
2810            Ok(None) => {}
2811            _ => panic!("Oh no"),
2812        };
2813        assert!(idms_auth.commit().is_ok());
2814
2815        // Check deleting the password
2816        let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
2817        let me_purge_up = ModifyEvent::new_internal_invalid(
2818            filter!(f_eq(Attribute::Name, PartialValue::new_iname("admin"))),
2819            ModifyList::new_list(vec![Modify::Purged(Attribute::UnixPassword)]),
2820        );
2821        assert!(idms_prox_write.qs_write.modify(&me_purge_up).is_ok());
2822        assert!(idms_prox_write.commit().is_ok());
2823
2824        // And auth should now fail due to the lack of PW material (note that
2825        // softlocking WON'T kick in because the cred_uuid is gone!)
2826        let mut idms_auth = idms.auth().await.unwrap();
2827        let a3 = idms_auth
2828            .auth_unix(&uuae_good, Duration::from_secs(TEST_CURRENT_TIME))
2829            .await;
2830        match a3 {
2831            Ok(None) => {}
2832            _ => panic!("Oh no"),
2833        };
2834        assert!(idms_auth.commit().is_ok());
2835    }
2836
2837    #[idm_test]
2838    async fn test_idm_simple_password_upgrade(
2839        idms: &IdmServer,
2840        idms_delayed: &mut IdmServerDelayed,
2841    ) {
2842        let ct = duration_from_epoch_now();
2843        // Assert the delayed action queue is empty
2844        idms_delayed.check_is_empty_or_panic();
2845        // Setup the admin w_ an imported password.
2846        {
2847            let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
2848            // now modify and provide a primary credential.
2849
2850            idms_prox_write
2851                .qs_write
2852                .internal_create(vec![E_TESTPERSON_1.clone()])
2853                .expect("Failed to create test person");
2854
2855            let me_inv_m =
2856                ModifyEvent::new_internal_invalid(
2857                        filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_TESTPERSON_1))),
2858                        ModifyList::new_list(vec![Modify::Present(
2859                            Attribute::PasswordImport,
2860                            Value::from("{SSHA512}JwrSUHkI7FTAfHRVR6KoFlSN0E3dmaQWARjZ+/UsShYlENOqDtFVU77HJLLrY2MuSp0jve52+pwtdVl2QUAHukQ0XUf5LDtM")
2861                        )]),
2862                    );
2863            // go!
2864            assert!(idms_prox_write.qs_write.modify(&me_inv_m).is_ok());
2865            assert!(idms_prox_write.commit().is_ok());
2866        }
2867        // Still empty
2868        idms_delayed.check_is_empty_or_panic();
2869
2870        let mut idms_prox_read = idms.proxy_read().await.unwrap();
2871        let person_entry = idms_prox_read
2872            .qs_read
2873            .internal_search_uuid(UUID_TESTPERSON_1)
2874            .expect("Can't access admin entry.");
2875        let cred_before = person_entry
2876            .get_ava_single_credential(Attribute::PrimaryCredential)
2877            .expect("No credential present")
2878            .clone();
2879        drop(idms_prox_read);
2880
2881        // Do an auth, this will trigger the action to send.
2882        check_testperson_password(idms, "password", ct).await;
2883
2884        // ⚠️  We have to be careful here. Between these two actions, it's possible
2885        // that on the pw upgrade that the credential uuid changes. This immediately
2886        // causes the session to be invalidated.
2887
2888        // We need to check the credential id does not change between these steps to
2889        // prevent this!
2890
2891        // process it.
2892        let da = idms_delayed.try_recv().expect("invalid");
2893        // The first task is the pw upgrade
2894        assert!(matches!(da, DelayedAction::PwUpgrade(_)));
2895        let r = idms.delayed_action(duration_from_epoch_now(), da).await;
2896        // The second is the auth session record
2897        let da = idms_delayed.try_recv().expect("invalid");
2898        assert!(matches!(da, DelayedAction::AuthSessionRecord(_)));
2899        assert_eq!(Ok(true), r);
2900
2901        let mut idms_prox_read = idms.proxy_read().await.unwrap();
2902        let person_entry = idms_prox_read
2903            .qs_read
2904            .internal_search_uuid(UUID_TESTPERSON_1)
2905            .expect("Can't access admin entry.");
2906        let cred_after = person_entry
2907            .get_ava_single_credential(Attribute::PrimaryCredential)
2908            .expect("No credential present")
2909            .clone();
2910        drop(idms_prox_read);
2911
2912        assert_eq!(cred_before.uuid, cred_after.uuid);
2913
2914        // Check the admin pw still matches
2915        check_testperson_password(idms, "password", ct).await;
2916        // Clear the next auth session record
2917        let da = idms_delayed.try_recv().expect("invalid");
2918        assert!(matches!(da, DelayedAction::AuthSessionRecord(_)));
2919
2920        // No delayed action was queued.
2921        idms_delayed.check_is_empty_or_panic();
2922    }
2923
2924    #[idm_test]
2925    async fn test_idm_unix_password_upgrade(idms: &IdmServer, idms_delayed: &mut IdmServerDelayed) {
2926        // Assert the delayed action queue is empty
2927        idms_delayed.check_is_empty_or_panic();
2928        // Setup the admin with an imported unix pw.
2929        let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
2930
2931        let im_pw = "{SSHA512}JwrSUHkI7FTAfHRVR6KoFlSN0E3dmaQWARjZ+/UsShYlENOqDtFVU77HJLLrY2MuSp0jve52+pwtdVl2QUAHukQ0XUf5LDtM";
2932        let pw = Password::try_from(im_pw).expect("failed to parse");
2933        let cred = Credential::new_from_password(pw);
2934        let v_cred = Value::new_credential("unix", cred);
2935
2936        let me_posix = ModifyEvent::new_internal_invalid(
2937            filter!(f_eq(Attribute::Name, PartialValue::new_iname("admin"))),
2938            ModifyList::new_list(vec![
2939                Modify::Present(Attribute::Class, EntryClass::PosixAccount.into()),
2940                Modify::Present(Attribute::GidNumber, Value::new_uint32(2001)),
2941                Modify::Present(Attribute::UnixPassword, v_cred),
2942            ]),
2943        );
2944        assert!(idms_prox_write.qs_write.modify(&me_posix).is_ok());
2945        assert!(idms_prox_write.commit().is_ok());
2946        idms_delayed.check_is_empty_or_panic();
2947        // Get the auth ready.
2948        let uuae = UnixUserAuthEvent::new_internal(UUID_ADMIN, "password");
2949        let mut idms_auth = idms.auth().await.unwrap();
2950        let a1 = idms_auth
2951            .auth_unix(&uuae, Duration::from_secs(TEST_CURRENT_TIME))
2952            .await;
2953        match a1 {
2954            Ok(Some(_tok)) => {}
2955            _ => panic!("Oh no"),
2956        };
2957        idms_auth.commit().expect("Must not fail");
2958        // The upgrade was queued
2959        // Process it.
2960        let da = idms_delayed.try_recv().expect("invalid");
2961        let _r = idms.delayed_action(duration_from_epoch_now(), da).await;
2962        // Go again
2963        let mut idms_auth = idms.auth().await.unwrap();
2964        let a2 = idms_auth
2965            .auth_unix(&uuae, Duration::from_secs(TEST_CURRENT_TIME))
2966            .await;
2967        match a2 {
2968            Ok(Some(_tok)) => {}
2969            _ => panic!("Oh no"),
2970        };
2971        idms_auth.commit().expect("Must not fail");
2972        // No delayed action was queued.
2973        idms_delayed.check_is_empty_or_panic();
2974    }
2975
2976    // For testing the timeouts
2977    // We need times on this scale
2978    //    not yet valid <-> valid from time <-> current_time <-> expire time <-> expired
2979    const TEST_NOT_YET_VALID_TIME: u64 = TEST_CURRENT_TIME - 240;
2980    const TEST_VALID_FROM_TIME: u64 = TEST_CURRENT_TIME - 120;
2981    const TEST_EXPIRE_TIME: u64 = TEST_CURRENT_TIME + 120;
2982    const TEST_AFTER_EXPIRY: u64 = TEST_CURRENT_TIME + 240;
2983
2984    async fn set_testperson_valid_time(idms: &IdmServer) {
2985        let mut idms_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
2986
2987        let v_valid_from = Value::new_datetime_epoch(Duration::from_secs(TEST_VALID_FROM_TIME));
2988        let v_expire = Value::new_datetime_epoch(Duration::from_secs(TEST_EXPIRE_TIME));
2989
2990        // now modify and provide a primary credential.
2991        let me_inv_m = ModifyEvent::new_internal_invalid(
2992            filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_TESTPERSON_1))),
2993            ModifyList::new_list(vec![
2994                Modify::Present(Attribute::AccountExpire, v_expire),
2995                Modify::Present(Attribute::AccountValidFrom, v_valid_from),
2996            ]),
2997        );
2998        // go!
2999        assert!(idms_write.qs_write.modify(&me_inv_m).is_ok());
3000
3001        idms_write.commit().expect("Must not fail");
3002    }
3003
3004    #[idm_test]
3005    async fn test_idm_account_valid_from_expire(
3006        idms: &IdmServer,
3007        _idms_delayed: &mut IdmServerDelayed,
3008    ) {
3009        // Any account that is not yet valrid / expired can't auth.
3010
3011        init_testperson_w_password(idms, TEST_PASSWORD)
3012            .await
3013            .expect("Failed to setup admin account");
3014        // Set the valid bounds high/low
3015        // TEST_VALID_FROM_TIME/TEST_EXPIRE_TIME
3016        set_testperson_valid_time(idms).await;
3017
3018        let time_low = Duration::from_secs(TEST_NOT_YET_VALID_TIME);
3019        let time_high = Duration::from_secs(TEST_AFTER_EXPIRY);
3020
3021        let mut idms_auth = idms.auth().await.unwrap();
3022        let admin_init = AuthEvent::named_init("admin");
3023        let r1 = idms_auth
3024            .auth(&admin_init, time_low, Source::Internal.into())
3025            .await;
3026
3027        let ar = r1.unwrap();
3028        let AuthResult {
3029            sessionid: _,
3030            state,
3031        } = ar;
3032
3033        match state {
3034            AuthState::Denied(_) => {}
3035            _ => {
3036                panic!();
3037            }
3038        };
3039
3040        idms_auth.commit().expect("Must not fail");
3041
3042        // And here!
3043        let mut idms_auth = idms.auth().await.unwrap();
3044        let admin_init = AuthEvent::named_init("admin");
3045        let r1 = idms_auth
3046            .auth(&admin_init, time_high, Source::Internal.into())
3047            .await;
3048
3049        let ar = r1.unwrap();
3050        let AuthResult {
3051            sessionid: _,
3052            state,
3053        } = ar;
3054
3055        match state {
3056            AuthState::Denied(_) => {}
3057            _ => {
3058                panic!();
3059            }
3060        };
3061
3062        idms_auth.commit().expect("Must not fail");
3063    }
3064
3065    #[idm_test]
3066    async fn test_idm_unix_valid_from_expire(
3067        idms: &IdmServer,
3068        _idms_delayed: &mut IdmServerDelayed,
3069    ) {
3070        // Any account that is expired can't unix auth.
3071        init_testperson_w_password(idms, TEST_PASSWORD)
3072            .await
3073            .expect("Failed to setup admin account");
3074        set_testperson_valid_time(idms).await;
3075
3076        let time_low = Duration::from_secs(TEST_NOT_YET_VALID_TIME);
3077        let time_high = Duration::from_secs(TEST_AFTER_EXPIRY);
3078
3079        // make the admin a valid posix account
3080        let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
3081        let me_posix = ModifyEvent::new_internal_invalid(
3082            filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_TESTPERSON_1))),
3083            ModifyList::new_list(vec![
3084                Modify::Present(Attribute::Class, EntryClass::PosixAccount.into()),
3085                Modify::Present(Attribute::GidNumber, Value::new_uint32(2001)),
3086            ]),
3087        );
3088        assert!(idms_prox_write.qs_write.modify(&me_posix).is_ok());
3089
3090        let pce = UnixPasswordChangeEvent::new_internal(UUID_TESTPERSON_1, TEST_PASSWORD);
3091
3092        assert!(idms_prox_write.set_unix_account_password(&pce).is_ok());
3093        assert!(idms_prox_write.commit().is_ok());
3094
3095        // Now check auth when the time is too high or too low.
3096        let mut idms_auth = idms.auth().await.unwrap();
3097        let uuae_good = UnixUserAuthEvent::new_internal(UUID_TESTPERSON_1, TEST_PASSWORD);
3098
3099        let a1 = idms_auth.auth_unix(&uuae_good, time_low).await;
3100        // Should this actually send an error with the details? Or just silently act as
3101        // badpw?
3102        match a1 {
3103            Ok(None) => {}
3104            _ => panic!("Oh no"),
3105        };
3106
3107        let a2 = idms_auth.auth_unix(&uuae_good, time_high).await;
3108        match a2 {
3109            Ok(None) => {}
3110            _ => panic!("Oh no"),
3111        };
3112
3113        idms_auth.commit().expect("Must not fail");
3114        // Also check the generated unix tokens are invalid.
3115        let mut idms_prox_read = idms.proxy_read().await.unwrap();
3116        let uute = UnixUserTokenEvent::new_internal(UUID_TESTPERSON_1);
3117
3118        let tok_r = idms_prox_read
3119            .get_unixusertoken(&uute, time_low)
3120            .expect("Failed to generate unix user token");
3121
3122        assert_eq!(tok_r.name, "testperson1");
3123        assert!(!tok_r.valid);
3124
3125        let tok_r = idms_prox_read
3126            .get_unixusertoken(&uute, time_high)
3127            .expect("Failed to generate unix user token");
3128
3129        assert_eq!(tok_r.name, "testperson1");
3130        assert!(!tok_r.valid);
3131    }
3132
3133    #[idm_test]
3134    async fn test_idm_radius_valid_from_expire(
3135        idms: &IdmServer,
3136        _idms_delayed: &mut IdmServerDelayed,
3137    ) {
3138        // Any account not valid/expiry should not return
3139        // a radius packet.
3140        init_testperson_w_password(idms, TEST_PASSWORD)
3141            .await
3142            .expect("Failed to setup admin account");
3143        set_testperson_valid_time(idms).await;
3144
3145        let time_low = Duration::from_secs(TEST_NOT_YET_VALID_TIME);
3146        let time_high = Duration::from_secs(TEST_AFTER_EXPIRY);
3147
3148        let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
3149        let rrse = RegenerateRadiusSecretEvent::new_internal(UUID_TESTPERSON_1);
3150        let _r1 = idms_prox_write
3151            .regenerate_radius_secret(&rrse)
3152            .expect("Failed to reset radius credential 1");
3153        idms_prox_write.commit().expect("failed to commit");
3154
3155        let mut idms_prox_read = idms.proxy_read().await.unwrap();
3156        let admin_entry = idms_prox_read
3157            .qs_read
3158            .internal_search_uuid(UUID_ADMIN)
3159            .expect("Can't access admin entry.");
3160
3161        let rate = RadiusAuthTokenEvent::new_impersonate(admin_entry, UUID_ADMIN);
3162        let tok_r = idms_prox_read.get_radiusauthtoken(&rate, time_low);
3163
3164        if tok_r.is_err() {
3165            // Ok?
3166        } else {
3167            debug_assert!(false);
3168        }
3169
3170        let tok_r = idms_prox_read.get_radiusauthtoken(&rate, time_high);
3171
3172        if tok_r.is_err() {
3173            // Ok?
3174        } else {
3175            debug_assert!(false);
3176        }
3177    }
3178
3179    #[idm_test(audit = 1)]
3180    async fn test_idm_account_softlocking(
3181        idms: &IdmServer,
3182        idms_delayed: &mut IdmServerDelayed,
3183        idms_audit: &mut IdmServerAudit,
3184    ) {
3185        init_testperson_w_password(idms, TEST_PASSWORD)
3186            .await
3187            .expect("Failed to setup admin account");
3188
3189        // Auth invalid, no softlock present.
3190        let sid =
3191            init_authsession_sid(idms, Duration::from_secs(TEST_CURRENT_TIME), "testperson1").await;
3192        let mut idms_auth = idms.auth().await.unwrap();
3193        let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD_INC);
3194
3195        let r2 = idms_auth
3196            .auth(
3197                &anon_step,
3198                Duration::from_secs(TEST_CURRENT_TIME),
3199                Source::Internal.into(),
3200            )
3201            .await;
3202        debug!("r2 ==> {:?}", r2);
3203
3204        match r2 {
3205            Ok(ar) => {
3206                let AuthResult {
3207                    sessionid: _,
3208                    state,
3209                } = ar;
3210                match state {
3211                    AuthState::Denied(reason) => {
3212                        assert!(reason != "Account is temporarily locked");
3213                    }
3214                    _ => {
3215                        error!("A critical error has occurred! We have a non-denied result!");
3216                        panic!();
3217                    }
3218                }
3219            }
3220            Err(e) => {
3221                error!("A critical error has occurred! {:?}", e);
3222                panic!();
3223            }
3224        };
3225
3226        // There should be a queued audit event
3227        match idms_audit.audit_rx().try_recv() {
3228            Ok(AuditEvent::AuthenticationDenied { .. }) => {}
3229            _ => panic!("Oh no"),
3230        }
3231
3232        idms_auth.commit().expect("Must not fail");
3233
3234        // Auth init, softlock present, count == 1, same time (so before unlock_at)
3235        // aka Auth valid immediate, (ct < exp), autofail
3236        // aka Auth invalid immediate, (ct < exp), autofail
3237        let mut idms_auth = idms.auth().await.unwrap();
3238        let admin_init = AuthEvent::named_init("testperson1");
3239
3240        let r1 = idms_auth
3241            .auth(
3242                &admin_init,
3243                Duration::from_secs(TEST_CURRENT_TIME),
3244                Source::Internal.into(),
3245            )
3246            .await;
3247        let ar = r1.unwrap();
3248        let AuthResult { sessionid, state } = ar;
3249        assert!(matches!(state, AuthState::Choose(_)));
3250
3251        // Soft locks only apply once a mechanism is chosen
3252        let admin_begin = AuthEvent::begin_mech(sessionid, AuthMech::Password);
3253
3254        let r2 = idms_auth
3255            .auth(
3256                &admin_begin,
3257                Duration::from_secs(TEST_CURRENT_TIME),
3258                Source::Internal.into(),
3259            )
3260            .await;
3261        let ar = r2.unwrap();
3262        let AuthResult {
3263            sessionid: _,
3264            state,
3265        } = ar;
3266
3267        match state {
3268            AuthState::Denied(reason) => {
3269                assert_eq!(reason, "Account is temporarily locked");
3270            }
3271            _ => {
3272                error!("Sessions was not denied (softlock)");
3273                panic!();
3274            }
3275        };
3276
3277        idms_auth.commit().expect("Must not fail");
3278
3279        // Auth invalid once softlock pass (count == 2, exp_at grows)
3280        // Tested in the softlock state machine.
3281
3282        // Auth valid once softlock pass, valid. Count remains.
3283        let sid = init_authsession_sid(
3284            idms,
3285            Duration::from_secs(TEST_CURRENT_TIME + 2),
3286            "testperson1",
3287        )
3288        .await;
3289
3290        let mut idms_auth = idms.auth().await.unwrap();
3291        let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD);
3292
3293        // Expect success
3294        let r2 = idms_auth
3295            .auth(
3296                &anon_step,
3297                Duration::from_secs(TEST_CURRENT_TIME + 2),
3298                Source::Internal.into(),
3299            )
3300            .await;
3301        debug!("r2 ==> {:?}", r2);
3302
3303        match r2 {
3304            Ok(ar) => {
3305                let AuthResult {
3306                    sessionid: _,
3307                    state,
3308                } = ar;
3309                match state {
3310                    AuthState::Success(_uat, AuthIssueSession::Token) => {
3311                        // Check the uat.
3312                    }
3313                    _ => {
3314                        error!("A critical error has occurred! We have a non-success result!");
3315                        panic!();
3316                    }
3317                }
3318            }
3319            Err(e) => {
3320                error!("A critical error has occurred! {:?}", e);
3321                // Should not occur!
3322                panic!();
3323            }
3324        };
3325
3326        idms_auth.commit().expect("Must not fail");
3327
3328        // Clear the auth session record
3329        let da = idms_delayed.try_recv().expect("invalid");
3330        assert!(matches!(da, DelayedAction::AuthSessionRecord(_)));
3331        idms_delayed.check_is_empty_or_panic();
3332
3333        // Auth valid after reset at, count == 0.
3334        // Tested in the softlock state machine.
3335
3336        // Auth invalid, softlock present, count == 1
3337        // Auth invalid after reset at, count == 0 and then to count == 1
3338        // Tested in the softlock state machine.
3339    }
3340
3341    #[idm_test(audit = 1)]
3342    async fn test_idm_account_softlocking_interleaved(
3343        idms: &IdmServer,
3344        _idms_delayed: &mut IdmServerDelayed,
3345        idms_audit: &mut IdmServerAudit,
3346    ) {
3347        init_testperson_w_password(idms, TEST_PASSWORD)
3348            .await
3349            .expect("Failed to setup admin account");
3350
3351        // Start an *early* auth session.
3352        let sid_early =
3353            init_authsession_sid(idms, Duration::from_secs(TEST_CURRENT_TIME), "testperson1").await;
3354
3355        // Start a second auth session
3356        let sid_later =
3357            init_authsession_sid(idms, Duration::from_secs(TEST_CURRENT_TIME), "testperson1").await;
3358        // Get the detail wrong in sid_later.
3359        let mut idms_auth = idms.auth().await.unwrap();
3360        let anon_step = AuthEvent::cred_step_password(sid_later, TEST_PASSWORD_INC);
3361
3362        let r2 = idms_auth
3363            .auth(
3364                &anon_step,
3365                Duration::from_secs(TEST_CURRENT_TIME),
3366                Source::Internal.into(),
3367            )
3368            .await;
3369        debug!("r2 ==> {:?}", r2);
3370
3371        match r2 {
3372            Ok(ar) => {
3373                let AuthResult {
3374                    sessionid: _,
3375                    state,
3376                } = ar;
3377                match state {
3378                    AuthState::Denied(reason) => {
3379                        assert!(reason != "Account is temporarily locked");
3380                    }
3381                    _ => {
3382                        error!("A critical error has occurred! We have a non-denied result!");
3383                        panic!();
3384                    }
3385                }
3386            }
3387            Err(e) => {
3388                error!("A critical error has occurred! {:?}", e);
3389                panic!();
3390            }
3391        };
3392
3393        match idms_audit.audit_rx().try_recv() {
3394            Ok(AuditEvent::AuthenticationDenied { .. }) => {}
3395            _ => panic!("Oh no"),
3396        }
3397
3398        idms_auth.commit().expect("Must not fail");
3399
3400        // Now check that sid_early is denied due to softlock.
3401        let mut idms_auth = idms.auth().await.unwrap();
3402        let anon_step = AuthEvent::cred_step_password(sid_early, TEST_PASSWORD);
3403
3404        // Expect success
3405        let r2 = idms_auth
3406            .auth(
3407                &anon_step,
3408                Duration::from_secs(TEST_CURRENT_TIME),
3409                Source::Internal.into(),
3410            )
3411            .await;
3412        debug!("r2 ==> {:?}", r2);
3413        match r2 {
3414            Ok(ar) => {
3415                let AuthResult {
3416                    sessionid: _,
3417                    state,
3418                } = ar;
3419                match state {
3420                    AuthState::Denied(reason) => {
3421                        assert_eq!(reason, "Account is temporarily locked");
3422                    }
3423                    _ => {
3424                        error!("A critical error has occurred! We have a non-denied result!");
3425                        panic!();
3426                    }
3427                }
3428            }
3429            Err(e) => {
3430                error!("A critical error has occurred! {:?}", e);
3431                panic!();
3432            }
3433        };
3434        idms_auth.commit().expect("Must not fail");
3435    }
3436
3437    #[idm_test]
3438    async fn test_idm_account_unix_softlocking(
3439        idms: &IdmServer,
3440        _idms_delayed: &mut IdmServerDelayed,
3441    ) {
3442        init_testperson_w_password(idms, TEST_PASSWORD)
3443            .await
3444            .expect("Failed to setup admin account");
3445        // make the admin a valid posix account
3446        let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
3447        let me_posix = ModifyEvent::new_internal_invalid(
3448            filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_TESTPERSON_1))),
3449            ModifyList::new_list(vec![
3450                Modify::Present(Attribute::Class, EntryClass::PosixAccount.into()),
3451                Modify::Present(Attribute::GidNumber, Value::new_uint32(2001)),
3452            ]),
3453        );
3454        assert!(idms_prox_write.qs_write.modify(&me_posix).is_ok());
3455
3456        let pce = UnixPasswordChangeEvent::new_internal(UUID_TESTPERSON_1, TEST_PASSWORD);
3457        assert!(idms_prox_write.set_unix_account_password(&pce).is_ok());
3458        assert!(idms_prox_write.commit().is_ok());
3459
3460        let mut idms_auth = idms.auth().await.unwrap();
3461        let uuae_good = UnixUserAuthEvent::new_internal(UUID_TESTPERSON_1, TEST_PASSWORD);
3462        let uuae_bad = UnixUserAuthEvent::new_internal(UUID_TESTPERSON_1, TEST_PASSWORD_INC);
3463
3464        let a2 = idms_auth
3465            .auth_unix(&uuae_bad, Duration::from_secs(TEST_CURRENT_TIME))
3466            .await;
3467        match a2 {
3468            Ok(None) => {}
3469            _ => panic!("Oh no"),
3470        };
3471
3472        // Now if we immediately auth again, should fail at same time due to SL
3473        let a1 = idms_auth
3474            .auth_unix(&uuae_good, Duration::from_secs(TEST_CURRENT_TIME))
3475            .await;
3476        match a1 {
3477            Ok(None) => {}
3478            _ => panic!("Oh no"),
3479        };
3480
3481        // And then later, works because of SL lifting.
3482        let a1 = idms_auth
3483            .auth_unix(&uuae_good, Duration::from_secs(TEST_CURRENT_TIME + 2))
3484            .await;
3485        match a1 {
3486            Ok(Some(_tok)) => {}
3487            _ => panic!("Oh no"),
3488        };
3489
3490        assert!(idms_auth.commit().is_ok());
3491    }
3492
3493    #[idm_test]
3494    async fn test_idm_jwt_uat_expiry(idms: &IdmServer, idms_delayed: &mut IdmServerDelayed) {
3495        let ct = Duration::from_secs(TEST_CURRENT_TIME);
3496        let expiry = ct + Duration::from_secs((DEFAULT_AUTH_SESSION_EXPIRY + 1).into());
3497        // Do an authenticate
3498        init_testperson_w_password(idms, TEST_PASSWORD)
3499            .await
3500            .expect("Failed to setup admin account");
3501        let token = check_testperson_password(idms, TEST_PASSWORD, ct).await;
3502
3503        // Clear out the queued session record
3504        let da = idms_delayed.try_recv().expect("invalid");
3505        assert!(matches!(da, DelayedAction::AuthSessionRecord(_)));
3506        // Persist it.
3507        let r = idms.delayed_action(ct, da).await;
3508        assert_eq!(Ok(true), r);
3509        idms_delayed.check_is_empty_or_panic();
3510
3511        let mut idms_prox_read = idms.proxy_read().await.unwrap();
3512
3513        // Check it's valid - This is within the time window so will pass.
3514        idms_prox_read
3515            .validate_client_auth_info_to_ident(token.clone().into(), ct)
3516            .expect("Failed to validate");
3517
3518        // In X time it should be INVALID
3519        match idms_prox_read.validate_client_auth_info_to_ident(token.into(), expiry) {
3520            Err(OperationError::SessionExpired) => {}
3521            _ => panic!("Oh no"),
3522        }
3523    }
3524
3525    #[idm_test]
3526    async fn test_idm_expired_auth_session_cleanup(
3527        idms: &IdmServer,
3528        _idms_delayed: &mut IdmServerDelayed,
3529    ) {
3530        let ct = Duration::from_secs(TEST_CURRENT_TIME);
3531        let expiry_a = ct + Duration::from_secs((DEFAULT_AUTH_SESSION_EXPIRY + 1).into());
3532        let expiry_b = ct + Duration::from_secs(((DEFAULT_AUTH_SESSION_EXPIRY + 1) * 2).into());
3533
3534        let session_a = Uuid::new_v4();
3535        let session_b = Uuid::new_v4();
3536
3537        // We need to put the credential on the admin.
3538        let cred_id = init_testperson_w_password(idms, TEST_PASSWORD)
3539            .await
3540            .expect("Failed to setup admin account");
3541
3542        // Assert no sessions present
3543        let mut idms_prox_read = idms.proxy_read().await.unwrap();
3544        let admin = idms_prox_read
3545            .qs_read
3546            .internal_search_uuid(UUID_TESTPERSON_1)
3547            .expect("failed");
3548        let sessions = admin.get_ava_as_session_map(Attribute::UserAuthTokenSession);
3549        assert!(sessions.is_none());
3550        drop(idms_prox_read);
3551
3552        let da = DelayedAction::AuthSessionRecord(AuthSessionRecord {
3553            target_uuid: UUID_TESTPERSON_1,
3554            session_id: session_a,
3555            cred_id,
3556            label: "Test Session A".to_string(),
3557            expiry: Some(OffsetDateTime::UNIX_EPOCH + expiry_a),
3558            issued_at: OffsetDateTime::UNIX_EPOCH + ct,
3559            issued_by: IdentityId::User(UUID_ADMIN),
3560            scope: SessionScope::ReadOnly,
3561            type_: AuthType::Passkey,
3562        });
3563        // Persist it.
3564        let r = idms.delayed_action(ct, da).await;
3565        assert_eq!(Ok(true), r);
3566
3567        // Check it was written, and check
3568        let mut idms_prox_read = idms.proxy_read().await.unwrap();
3569        let admin = idms_prox_read
3570            .qs_read
3571            .internal_search_uuid(UUID_TESTPERSON_1)
3572            .expect("failed");
3573        let sessions = admin
3574            .get_ava_as_session_map(Attribute::UserAuthTokenSession)
3575            .expect("Sessions must be present!");
3576        assert_eq!(sessions.len(), 1);
3577        let session_data_a = sessions.get(&session_a).expect("Session A is missing!");
3578        assert!(matches!(session_data_a.state, SessionState::ExpiresAt(_)));
3579
3580        drop(idms_prox_read);
3581
3582        // When we re-auth, this is what triggers the session revoke via the delayed action.
3583
3584        let da = DelayedAction::AuthSessionRecord(AuthSessionRecord {
3585            target_uuid: UUID_TESTPERSON_1,
3586            session_id: session_b,
3587            cred_id,
3588            label: "Test Session B".to_string(),
3589            expiry: Some(OffsetDateTime::UNIX_EPOCH + expiry_b),
3590            issued_at: OffsetDateTime::UNIX_EPOCH + ct,
3591            issued_by: IdentityId::User(UUID_ADMIN),
3592            scope: SessionScope::ReadOnly,
3593            type_: AuthType::Passkey,
3594        });
3595        // Persist it.
3596        let r = idms.delayed_action(expiry_a, da).await;
3597        assert_eq!(Ok(true), r);
3598
3599        let mut idms_prox_read = idms.proxy_read().await.unwrap();
3600        let admin = idms_prox_read
3601            .qs_read
3602            .internal_search_uuid(UUID_TESTPERSON_1)
3603            .expect("failed");
3604        let sessions = admin
3605            .get_ava_as_session_map(Attribute::UserAuthTokenSession)
3606            .expect("Sessions must be present!");
3607        trace!(?sessions);
3608        assert_eq!(sessions.len(), 2);
3609
3610        let session_data_a = sessions.get(&session_a).expect("Session A is missing!");
3611        assert!(matches!(session_data_a.state, SessionState::RevokedAt(_)));
3612
3613        let session_data_b = sessions.get(&session_b).expect("Session B is missing!");
3614        assert!(matches!(session_data_b.state, SessionState::ExpiresAt(_)));
3615        // Now show that sessions trim!
3616    }
3617
3618    #[idm_test]
3619    async fn test_idm_account_session_validation(
3620        idms: &IdmServer,
3621        idms_delayed: &mut IdmServerDelayed,
3622    ) {
3623        use kanidm_proto::internal::UserAuthToken;
3624
3625        let ct = duration_from_epoch_now();
3626
3627        let post_grace = ct + AUTH_TOKEN_GRACE_WINDOW + Duration::from_secs(1);
3628        let expiry = ct + Duration::from_secs(DEFAULT_AUTH_SESSION_EXPIRY as u64 + 1);
3629
3630        // Assert that our grace time is less than expiry, so we know the failure is due to
3631        // this.
3632        assert!(post_grace < expiry);
3633
3634        // Do an authenticate
3635        init_testperson_w_password(idms, TEST_PASSWORD)
3636            .await
3637            .expect("Failed to setup admin account");
3638        let uat_unverified = check_testperson_password(idms, TEST_PASSWORD, ct).await;
3639
3640        // Process the session info.
3641        let da = idms_delayed.try_recv().expect("invalid");
3642        assert!(matches!(da, DelayedAction::AuthSessionRecord(_)));
3643        let r = idms.delayed_action(ct, da).await;
3644        assert_eq!(Ok(true), r);
3645
3646        let mut idms_prox_read = idms.proxy_read().await.unwrap();
3647
3648        let token_kid = uat_unverified.kid().expect("no key id present");
3649
3650        let uat_jwk = idms_prox_read
3651            .qs_read
3652            .get_key_providers()
3653            .get_key_object(UUID_DOMAIN_INFO)
3654            .and_then(|object| {
3655                object
3656                    .jws_public_jwk(token_kid)
3657                    .expect("Unable to access uat jwk")
3658            })
3659            .expect("No jwk by this kid");
3660
3661        let jws_validator = JwsEs256Verifier::try_from(&uat_jwk).unwrap();
3662
3663        let uat_inner: UserAuthToken = jws_validator
3664            .verify(&uat_unverified)
3665            .unwrap()
3666            .from_json()
3667            .unwrap();
3668
3669        // Check it's valid.
3670        idms_prox_read
3671            .validate_client_auth_info_to_ident(uat_unverified.clone().into(), ct)
3672            .expect("Failed to validate");
3673
3674        // If the auth session record wasn't processed, this will fail.
3675        idms_prox_read
3676            .validate_client_auth_info_to_ident(uat_unverified.clone().into(), post_grace)
3677            .expect("Failed to validate");
3678
3679        drop(idms_prox_read);
3680
3681        // Mark the session as invalid now.
3682        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3683        let dte = DestroySessionTokenEvent::new_internal(uat_inner.uuid, uat_inner.session_id);
3684        assert!(idms_prox_write.account_destroy_session_token(&dte).is_ok());
3685        assert!(idms_prox_write.commit().is_ok());
3686
3687        // Now check again with the session destroyed.
3688        let mut idms_prox_read = idms.proxy_read().await.unwrap();
3689
3690        // Now, within gracewindow, it's NOT valid because the session entry exists and is in
3691        // the revoked state!
3692        match idms_prox_read
3693            .validate_client_auth_info_to_ident(uat_unverified.clone().into(), post_grace)
3694        {
3695            Err(OperationError::SessionExpired) => {}
3696            _ => panic!("Oh no"),
3697        }
3698        drop(idms_prox_read);
3699
3700        // Force trim the session out so that we can check the grate handling.
3701        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3702        let filt = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(uat_inner.uuid)));
3703        let mut work_set = idms_prox_write
3704            .qs_write
3705            .internal_search_writeable(&filt)
3706            .expect("Failed to perform internal search writeable");
3707        for (_, entry) in work_set.iter_mut() {
3708            let _ = entry.force_trim_ava(Attribute::UserAuthTokenSession);
3709        }
3710        assert!(idms_prox_write
3711            .qs_write
3712            .internal_apply_writable(work_set)
3713            .is_ok());
3714
3715        assert!(idms_prox_write.commit().is_ok());
3716
3717        let mut idms_prox_read = idms.proxy_read().await.unwrap();
3718        idms_prox_read
3719            .validate_client_auth_info_to_ident(uat_unverified.clone().into(), ct)
3720            .expect("Failed to validate");
3721
3722        // post grace, it's not valid.
3723        match idms_prox_read
3724            .validate_client_auth_info_to_ident(uat_unverified.clone().into(), post_grace)
3725        {
3726            Err(OperationError::SessionExpired) => {}
3727            _ => panic!("Oh no"),
3728        }
3729    }
3730
3731    #[idm_test]
3732    async fn test_idm_account_session_expiry(
3733        idms: &IdmServer,
3734        _idms_delayed: &mut IdmServerDelayed,
3735    ) {
3736        let ct = Duration::from_secs(TEST_CURRENT_TIME);
3737
3738        //we first set the expiry to a custom value
3739        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3740
3741        let new_authsession_expiry = 1000;
3742
3743        let modlist = ModifyList::new_purge_and_set(
3744            Attribute::AuthSessionExpiry,
3745            Value::Uint32(new_authsession_expiry),
3746        );
3747        idms_prox_write
3748            .qs_write
3749            .internal_modify_uuid(UUID_IDM_ALL_ACCOUNTS, &modlist)
3750            .expect("Unable to change default session exp");
3751
3752        assert!(idms_prox_write.commit().is_ok());
3753
3754        // Start anonymous auth.
3755        let mut idms_auth = idms.auth().await.unwrap();
3756        // Send the initial auth event for initialising the session
3757        let anon_init = AuthEvent::anonymous_init();
3758        // Expect success
3759        let r1 = idms_auth
3760            .auth(&anon_init, ct, Source::Internal.into())
3761            .await;
3762        /* Some weird lifetime things happen here ... */
3763
3764        let sid = match r1 {
3765            Ok(ar) => {
3766                let AuthResult { sessionid, state } = ar;
3767                match state {
3768                    AuthState::Choose(mut conts) => {
3769                        // Should only be one auth mech
3770                        assert_eq!(conts.len(), 1);
3771                        // And it should be anonymous
3772                        let m = conts.pop().expect("Should not fail");
3773                        assert_eq!(m, AuthMech::Anonymous);
3774                    }
3775                    _ => {
3776                        error!("A critical error has occurred! We have a non-continue result!");
3777                        panic!();
3778                    }
3779                };
3780                // Now pass back the sessionid, we are good to continue.
3781                sessionid
3782            }
3783            Err(e) => {
3784                // Should not occur!
3785                error!("A critical error has occurred! {:?}", e);
3786                panic!();
3787            }
3788        };
3789
3790        idms_auth.commit().expect("Must not fail");
3791
3792        let mut idms_auth = idms.auth().await.unwrap();
3793        let anon_begin = AuthEvent::begin_mech(sid, AuthMech::Anonymous);
3794
3795        let r2 = idms_auth
3796            .auth(&anon_begin, ct, Source::Internal.into())
3797            .await;
3798
3799        match r2 {
3800            Ok(ar) => {
3801                let AuthResult {
3802                    sessionid: _,
3803                    state,
3804                } = ar;
3805
3806                match state {
3807                    AuthState::Continue(allowed) => {
3808                        // Check the uat.
3809                        assert_eq!(allowed.len(), 1);
3810                        assert_eq!(allowed.first(), Some(&AuthAllowed::Anonymous));
3811                    }
3812                    _ => {
3813                        error!("A critical error has occurred! We have a non-continue result!");
3814                        panic!();
3815                    }
3816                }
3817            }
3818            Err(e) => {
3819                error!("A critical error has occurred! {:?}", e);
3820                // Should not occur!
3821                panic!();
3822            }
3823        };
3824
3825        idms_auth.commit().expect("Must not fail");
3826
3827        let mut idms_auth = idms.auth().await.unwrap();
3828        // Now send the anonymous request, given the session id.
3829        let anon_step = AuthEvent::cred_step_anonymous(sid);
3830
3831        // Expect success
3832        let r2 = idms_auth
3833            .auth(&anon_step, ct, Source::Internal.into())
3834            .await;
3835
3836        let token = match r2 {
3837            Ok(ar) => {
3838                let AuthResult {
3839                    sessionid: _,
3840                    state,
3841                } = ar;
3842
3843                match state {
3844                    AuthState::Success(uat, AuthIssueSession::Token) => uat,
3845                    _ => {
3846                        error!("A critical error has occurred! We have a non-success result!");
3847                        panic!();
3848                    }
3849                }
3850            }
3851            Err(e) => {
3852                error!("A critical error has occurred! {:?}", e);
3853                // Should not occur!
3854                panic!("A critical error has occurred! {:?}", e);
3855            }
3856        };
3857
3858        idms_auth.commit().expect("Must not fail");
3859
3860        // Token_str to uat
3861        // we have to do it this way because anonymous doesn't have an ideantity for which we cam get the expiry value
3862        let Token::UserAuthToken(uat) = idms
3863            .proxy_read()
3864            .await
3865            .unwrap()
3866            .validate_and_parse_token_to_token(&token, ct)
3867            .expect("Must not fail")
3868        else {
3869            panic!("Unexpected auth token type for anonymous auth");
3870        };
3871
3872        debug!(?uat);
3873
3874        assert!(
3875            matches!(uat.expiry, Some(exp) if exp == OffsetDateTime::UNIX_EPOCH + ct + Duration::from_secs(new_authsession_expiry as u64))
3876        );
3877    }
3878
3879    #[idm_test]
3880    async fn test_idm_uat_claim_insertion(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) {
3881        let ct = Duration::from_secs(TEST_CURRENT_TIME);
3882        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3883
3884        // get an account.
3885        let account = idms_prox_write
3886            .target_to_account(UUID_ADMIN)
3887            .expect("account must exist");
3888
3889        // Create some fake UATs, then process them and see what claims fall out 🥳
3890        let session_id = uuid::Uuid::new_v4();
3891
3892        // For the different auth types, check that we get the correct claims:
3893
3894        // == anonymous
3895        let uat = account
3896            .to_userauthtoken(
3897                session_id,
3898                SessionScope::ReadWrite,
3899                ct,
3900                &ResolvedAccountPolicy::test_policy(),
3901            )
3902            .expect("Unable to create uat");
3903        let ident = idms_prox_write
3904            .process_uat_to_identity(&uat, ct, Source::Internal)
3905            .expect("Unable to process uat");
3906
3907        assert!(!ident.has_claim("authtype_anonymous"));
3908        // Does NOT have this
3909        assert!(!ident.has_claim("authlevel_strong"));
3910        assert!(!ident.has_claim("authclass_single"));
3911        assert!(!ident.has_claim("authclass_mfa"));
3912
3913        // == unixpassword
3914        let uat = account
3915            .to_userauthtoken(
3916                session_id,
3917                SessionScope::ReadWrite,
3918                ct,
3919                &ResolvedAccountPolicy::test_policy(),
3920            )
3921            .expect("Unable to create uat");
3922        let ident = idms_prox_write
3923            .process_uat_to_identity(&uat, ct, Source::Internal)
3924            .expect("Unable to process uat");
3925
3926        assert!(!ident.has_claim("authtype_unixpassword"));
3927        assert!(!ident.has_claim("authclass_single"));
3928        // Does NOT have this
3929        assert!(!ident.has_claim("authlevel_strong"));
3930        assert!(!ident.has_claim("authclass_mfa"));
3931
3932        // == password
3933        let uat = account
3934            .to_userauthtoken(
3935                session_id,
3936                SessionScope::ReadWrite,
3937                ct,
3938                &ResolvedAccountPolicy::test_policy(),
3939            )
3940            .expect("Unable to create uat");
3941        let ident = idms_prox_write
3942            .process_uat_to_identity(&uat, ct, Source::Internal)
3943            .expect("Unable to process uat");
3944
3945        assert!(!ident.has_claim("authtype_password"));
3946        assert!(!ident.has_claim("authclass_single"));
3947        // Does NOT have this
3948        assert!(!ident.has_claim("authlevel_strong"));
3949        assert!(!ident.has_claim("authclass_mfa"));
3950
3951        // == generatedpassword
3952        let uat = account
3953            .to_userauthtoken(
3954                session_id,
3955                SessionScope::ReadWrite,
3956                ct,
3957                &ResolvedAccountPolicy::test_policy(),
3958            )
3959            .expect("Unable to create uat");
3960        let ident = idms_prox_write
3961            .process_uat_to_identity(&uat, ct, Source::Internal)
3962            .expect("Unable to process uat");
3963
3964        assert!(!ident.has_claim("authtype_generatedpassword"));
3965        assert!(!ident.has_claim("authclass_single"));
3966        assert!(!ident.has_claim("authlevel_strong"));
3967        // Does NOT have this
3968        assert!(!ident.has_claim("authclass_mfa"));
3969
3970        // == webauthn
3971        let uat = account
3972            .to_userauthtoken(
3973                session_id,
3974                SessionScope::ReadWrite,
3975                ct,
3976                &ResolvedAccountPolicy::test_policy(),
3977            )
3978            .expect("Unable to create uat");
3979        let ident = idms_prox_write
3980            .process_uat_to_identity(&uat, ct, Source::Internal)
3981            .expect("Unable to process uat");
3982
3983        assert!(!ident.has_claim("authtype_webauthn"));
3984        assert!(!ident.has_claim("authclass_single"));
3985        assert!(!ident.has_claim("authlevel_strong"));
3986        // Does NOT have this
3987        assert!(!ident.has_claim("authclass_mfa"));
3988
3989        // == passwordmfa
3990        let uat = account
3991            .to_userauthtoken(
3992                session_id,
3993                SessionScope::ReadWrite,
3994                ct,
3995                &ResolvedAccountPolicy::test_policy(),
3996            )
3997            .expect("Unable to create uat");
3998        let ident = idms_prox_write
3999            .process_uat_to_identity(&uat, ct, Source::Internal)
4000            .expect("Unable to process uat");
4001
4002        assert!(!ident.has_claim("authtype_passwordmfa"));
4003        assert!(!ident.has_claim("authlevel_strong"));
4004        assert!(!ident.has_claim("authclass_mfa"));
4005        // Does NOT have this
4006        assert!(!ident.has_claim("authclass_single"));
4007    }
4008
4009    #[idm_test]
4010    async fn test_idm_uat_limits_account_policy(
4011        idms: &IdmServer,
4012        _idms_delayed: &mut IdmServerDelayed,
4013    ) {
4014        let ct = Duration::from_secs(TEST_CURRENT_TIME);
4015        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4016
4017        idms_prox_write
4018            .qs_write
4019            .internal_create(vec![E_TESTPERSON_1.clone()])
4020            .expect("Failed to create test person");
4021
4022        // get an account.
4023        let account = idms_prox_write
4024            .target_to_account(UUID_TESTPERSON_1)
4025            .expect("account must exist");
4026
4027        // Create a fake UATs
4028        let session_id = uuid::Uuid::new_v4();
4029
4030        let uat = account
4031            .to_userauthtoken(
4032                session_id,
4033                SessionScope::ReadWrite,
4034                ct,
4035                &ResolvedAccountPolicy::test_policy(),
4036            )
4037            .expect("Unable to create uat");
4038
4039        let ident = idms_prox_write
4040            .process_uat_to_identity(&uat, ct, Source::Internal)
4041            .expect("Unable to process uat");
4042
4043        assert_eq!(
4044            ident.limits().search_max_results,
4045            DEFAULT_LIMIT_SEARCH_MAX_RESULTS as usize
4046        );
4047        assert_eq!(
4048            ident.limits().search_max_filter_test,
4049            DEFAULT_LIMIT_SEARCH_MAX_FILTER_TEST as usize
4050        );
4051    }
4052
4053    #[idm_test]
4054    async fn test_idm_jwt_uat_token_key_reload(
4055        idms: &IdmServer,
4056        idms_delayed: &mut IdmServerDelayed,
4057    ) {
4058        let ct = duration_from_epoch_now();
4059
4060        init_testperson_w_password(idms, TEST_PASSWORD)
4061            .await
4062            .expect("Failed to setup admin account");
4063        let token = check_testperson_password(idms, TEST_PASSWORD, ct).await;
4064
4065        // Clear the session record
4066        let da = idms_delayed.try_recv().expect("invalid");
4067        assert!(matches!(da, DelayedAction::AuthSessionRecord(_)));
4068        idms_delayed.check_is_empty_or_panic();
4069
4070        let mut idms_prox_read = idms.proxy_read().await.unwrap();
4071
4072        // Check it's valid.
4073        idms_prox_read
4074            .validate_client_auth_info_to_ident(token.clone().into(), ct)
4075            .expect("Failed to validate");
4076
4077        drop(idms_prox_read);
4078
4079        // We need to get the token key id and revoke it.
4080        let revoke_kid = token.kid().expect("token does not contain a key id");
4081
4082        // Now revoke the token_key
4083        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4084        let me_reset_tokens = ModifyEvent::new_internal_invalid(
4085            filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_DOMAIN_INFO))),
4086            ModifyList::new_append(
4087                Attribute::KeyActionRevoke,
4088                Value::HexString(revoke_kid.to_string()),
4089            ),
4090        );
4091        assert!(idms_prox_write.qs_write.modify(&me_reset_tokens).is_ok());
4092        assert!(idms_prox_write.commit().is_ok());
4093
4094        let new_token = check_testperson_password(idms, TEST_PASSWORD, ct).await;
4095
4096        // Clear the session record
4097        let da = idms_delayed.try_recv().expect("invalid");
4098        assert!(matches!(da, DelayedAction::AuthSessionRecord(_)));
4099        idms_delayed.check_is_empty_or_panic();
4100
4101        let mut idms_prox_read = idms.proxy_read().await.unwrap();
4102
4103        // Check the old token is invalid, due to reload.
4104        assert!(idms_prox_read
4105            .validate_client_auth_info_to_ident(token.into(), ct)
4106            .is_err());
4107
4108        // A new token will work due to the matching key.
4109        idms_prox_read
4110            .validate_client_auth_info_to_ident(new_token.into(), ct)
4111            .expect("Failed to validate");
4112    }
4113
4114    #[idm_test]
4115    async fn test_idm_service_account_to_person(
4116        idms: &IdmServer,
4117        _idms_delayed: &mut IdmServerDelayed,
4118    ) {
4119        let ct = Duration::from_secs(TEST_CURRENT_TIME);
4120        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4121
4122        let ident = Identity::from_internal();
4123        let target_uuid = Uuid::new_v4();
4124
4125        // Create a service account
4126        let e = entry_init!(
4127            (Attribute::Class, EntryClass::Object.to_value()),
4128            (Attribute::Class, EntryClass::Account.to_value()),
4129            (Attribute::Class, EntryClass::ServiceAccount.to_value()),
4130            (Attribute::Name, Value::new_iname("testaccount")),
4131            (Attribute::Uuid, Value::Uuid(target_uuid)),
4132            (Attribute::Description, Value::new_utf8s("testaccount")),
4133            (Attribute::DisplayName, Value::new_utf8s("Test Account"))
4134        );
4135
4136        let ce = CreateEvent::new_internal(vec![e]);
4137        let cr = idms_prox_write.qs_write.create(&ce);
4138        assert!(cr.is_ok());
4139
4140        // Do the migrate.
4141        assert!(idms_prox_write
4142            .service_account_into_person(&ident, target_uuid)
4143            .is_ok());
4144
4145        // Any checks?
4146    }
4147
4148    async fn idm_fallback_auth_fixture(
4149        idms: &IdmServer,
4150        _idms_delayed: &mut IdmServerDelayed,
4151        has_posix_password: bool,
4152        allow_primary_cred_fallback: Option<bool>,
4153        expected: Option<()>,
4154    ) {
4155        let ct = Duration::from_secs(TEST_CURRENT_TIME);
4156        let target_uuid = Uuid::new_v4();
4157        let p = CryptoPolicy::minimum();
4158
4159        {
4160            let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4161
4162            if let Some(allow_primary_cred_fallback) = allow_primary_cred_fallback {
4163                idms_prox_write
4164                    .qs_write
4165                    .internal_modify_uuid(
4166                        UUID_IDM_ALL_ACCOUNTS,
4167                        &ModifyList::new_purge_and_set(
4168                            Attribute::AllowPrimaryCredFallback,
4169                            Value::new_bool(allow_primary_cred_fallback),
4170                        ),
4171                    )
4172                    .expect("Unable to change default session exp");
4173            }
4174
4175            let mut e = entry_init!(
4176                (Attribute::Class, EntryClass::Object.to_value()),
4177                (Attribute::Class, EntryClass::Account.to_value()),
4178                (Attribute::Class, EntryClass::Person.to_value()),
4179                (Attribute::Uuid, Value::Uuid(target_uuid)),
4180                (Attribute::Name, Value::new_iname("kevin")),
4181                (Attribute::DisplayName, Value::new_utf8s("Kevin")),
4182                (Attribute::Class, EntryClass::PosixAccount.to_value()),
4183                (
4184                    Attribute::PrimaryCredential,
4185                    Value::Cred(
4186                        "primary".to_string(),
4187                        Credential::new_password_only(&p, "banana").unwrap()
4188                    )
4189                )
4190            );
4191
4192            if has_posix_password {
4193                e.add_ava(
4194                    Attribute::UnixPassword,
4195                    Value::Cred(
4196                        "unix".to_string(),
4197                        Credential::new_password_only(&p, "kampai").unwrap(),
4198                    ),
4199                );
4200            }
4201
4202            let ce = CreateEvent::new_internal(vec![e]);
4203            let cr = idms_prox_write.qs_write.create(&ce);
4204            assert!(cr.is_ok());
4205            idms_prox_write.commit().expect("Must not fail");
4206        }
4207
4208        let result = idms
4209            .auth()
4210            .await
4211            .unwrap()
4212            .auth_ldap(
4213                &LdapAuthEvent {
4214                    target: target_uuid,
4215                    cleartext: if has_posix_password {
4216                        "kampai".to_string()
4217                    } else {
4218                        "banana".to_string()
4219                    },
4220                },
4221                ct,
4222            )
4223            .await;
4224
4225        assert!(result.is_ok());
4226        if expected.is_some() {
4227            assert!(result.unwrap().is_some());
4228        } else {
4229            assert!(result.unwrap().is_none());
4230        }
4231    }
4232
4233    #[idm_test]
4234    async fn test_idm_fallback_auth_no_pass_none_fallback(
4235        idms: &IdmServer,
4236        _idms_delayed: &mut IdmServerDelayed,
4237    ) {
4238        idm_fallback_auth_fixture(idms, _idms_delayed, false, None, None).await;
4239    }
4240    #[idm_test]
4241    async fn test_idm_fallback_auth_pass_none_fallback(
4242        idms: &IdmServer,
4243        _idms_delayed: &mut IdmServerDelayed,
4244    ) {
4245        idm_fallback_auth_fixture(idms, _idms_delayed, true, None, Some(())).await;
4246    }
4247    #[idm_test]
4248    async fn test_idm_fallback_auth_no_pass_true_fallback(
4249        idms: &IdmServer,
4250        _idms_delayed: &mut IdmServerDelayed,
4251    ) {
4252        idm_fallback_auth_fixture(idms, _idms_delayed, false, Some(true), Some(())).await;
4253    }
4254    #[idm_test]
4255    async fn test_idm_fallback_auth_pass_true_fallback(
4256        idms: &IdmServer,
4257        _idms_delayed: &mut IdmServerDelayed,
4258    ) {
4259        idm_fallback_auth_fixture(idms, _idms_delayed, true, Some(true), Some(())).await;
4260    }
4261    #[idm_test]
4262    async fn test_idm_fallback_auth_no_pass_false_fallback(
4263        idms: &IdmServer,
4264        _idms_delayed: &mut IdmServerDelayed,
4265    ) {
4266        idm_fallback_auth_fixture(idms, _idms_delayed, false, Some(false), None).await;
4267    }
4268    #[idm_test]
4269    async fn test_idm_fallback_auth_pass_false_fallback(
4270        idms: &IdmServer,
4271        _idms_delayed: &mut IdmServerDelayed,
4272    ) {
4273        idm_fallback_auth_fixture(idms, _idms_delayed, true, Some(false), Some(())).await;
4274    }
4275}