Skip to main content

kanidmd_lib/idm/
server.rs

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