Skip to main content

kanidmd_lib/idm/
server.rs

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