kanidmd_lib/idm/
server.rs

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