kanidmd_lib/idm/
server.rs

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