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