kanidmd_lib/idm/
server.rs

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