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