kanidmd_lib/idm/
server.rs

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