kanidmd_lib/idm/
server.rs

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