Skip to main content

kanidmd_lib/idm/
server.rs

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