kanidmd_lib/idm/
server.rs

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