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