kanidm_unix_resolver/
resolver.rs

1// use async_trait::async_trait;
2use crate::db::{Cache, Db};
3use crate::idprovider::interface::{
4    AuthCredHandler,
5    AuthResult,
6    GroupToken,
7    GroupTokenState,
8    Id,
9    IdProvider,
10    IdpError,
11    ProviderOrigin,
12    // KeyStore,
13    UserToken,
14    UserTokenState,
15};
16use crate::idprovider::system::{
17    Shadow, SystemAuthResult, SystemProvider, SystemProviderAuthInit, SystemProviderSession,
18};
19use hashbrown::HashMap;
20use kanidm_hsm_crypto::provider::BoxedDynTpm;
21use kanidm_unix_common::constants::{
22    DEFAULT_CACHE_TIMEOUT_JITTER_MS, DEFAULT_CACHE_TIMEOUT_MAXIMUM, DEFAULT_CACHE_TIMEOUT_MINIMUM,
23    DEFAULT_SHELL_SEARCH_PATHS, SYSTEM_SHADOW_PATH,
24};
25use kanidm_unix_common::unix_config::{HomeAttr, UidAttr};
26use kanidm_unix_common::unix_passwd::{EtcGroup, EtcShadow, EtcUser};
27use kanidm_unix_common::unix_proto::{
28    HomeDirectoryInfo, NssGroup, NssUser, PamAuthRequest, PamAuthResponse, PamServiceInfo,
29    ProviderStatus,
30};
31use lru::LruCache;
32use std::fmt::Display;
33use std::num::NonZeroUsize;
34use std::ops::DerefMut;
35use std::path::{Path, PathBuf};
36use std::string::ToString;
37use std::sync::Arc;
38use std::time::{Duration, SystemTime};
39use time::OffsetDateTime;
40use tokio::sync::broadcast;
41use tokio::sync::mpsc;
42use tokio::sync::Mutex;
43use uuid::Uuid;
44
45const NXCACHE_SIZE: NonZeroUsize =
46    NonZeroUsize::new(256).expect("Invalid NXCACHE constant at compile time");
47// Can sometimes have duplicates, so it can be a bit longer.
48const ASYNC_REFRESH_QUEUE: usize = 128;
49
50pub enum AuthSession {
51    Online {
52        client: Arc<dyn IdProvider + Sync + Send>,
53        account_id: String,
54        id: Id,
55        cred_handler: AuthCredHandler,
56        /// Some authentication operations may need to spawn background tasks. These tasks need
57        /// to know when to stop as the caller has disconnected. This receiver allows that, so
58        /// that tasks which .resubscribe() to this channel can then select! on it and be notified
59        /// when they need to stop.
60        shutdown_rx: broadcast::Receiver<()>,
61    },
62    Offline {
63        account_id: String,
64        id: Id,
65        client: Arc<dyn IdProvider + Sync + Send>,
66        session_token: Box<UserToken>,
67        cred_handler: AuthCredHandler,
68    },
69    System {
70        account_id: String,
71        id: Id,
72        cred_handler: AuthCredHandler,
73        shadow: Arc<Shadow>,
74    },
75    Success,
76    Denied,
77}
78
79#[derive(Debug, PartialEq)]
80/// The expiration state of a cache item
81enum ExpiryState {
82    /// The item is valid
83    Valid,
84    /// The item is valid, but approaching it's expiry so a background/async refresh should be
85    /// performed.
86    ValidRefresh,
87    /// The item is expired and needs refresh before we can proceed
88    Expired,
89}
90
91pub struct Resolver {
92    // Generic / modular types.
93    db: Db,
94    hsm: Mutex<BoxedDynTpm>,
95
96    // A local passwd/shadow resolver.
97    system_provider: Arc<SystemProvider>,
98
99    // client: Box<dyn IdProvider + Sync + Send>,
100    client_ids: HashMap<ProviderOrigin, Arc<dyn IdProvider + Sync + Send>>,
101
102    // A set of remote resolvers, ordered by priority.
103    clients: Vec<Arc<dyn IdProvider + Sync + Send>>,
104
105    // The id of the primary-provider which may use name over spn.
106    primary_origin: ProviderOrigin,
107
108    timeout_seconds: u64,
109    async_refresh_seconds: u64,
110    default_shell: String,
111    home_prefix: PathBuf,
112    home_attr: HomeAttr,
113    home_alias: Option<HomeAttr>,
114    uid_attr_map: UidAttr,
115    gid_attr_map: UidAttr,
116    nxcache: Mutex<LruCache<Id, SystemTime>>,
117    async_refresh_tx: mpsc::Sender<Id>,
118}
119
120impl Display for Id {
121    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122        f.write_str(&match self {
123            Id::Name(s) => s.to_string(),
124            Id::Gid(g) => g.to_string(),
125        })
126    }
127}
128
129impl Resolver {
130    #[allow(clippy::too_many_arguments)]
131    pub async fn new(
132        db: Db,
133        system_provider: Arc<SystemProvider>,
134        clients: Vec<Arc<dyn IdProvider + Sync + Send>>,
135        hsm: BoxedDynTpm,
136        timeout_seconds: u64,
137        default_shell: String,
138        home_prefix: PathBuf,
139        home_attr: HomeAttr,
140        home_alias: Option<HomeAttr>,
141        uid_attr_map: UidAttr,
142        gid_attr_map: UidAttr,
143    ) -> Result<(Self, mpsc::Receiver<Id>), ()> {
144        let hsm = Mutex::new(hsm);
145
146        let primary_origin = clients.first().map(|c| c.origin()).unwrap_or_default();
147
148        let client_ids: HashMap<_, _> = clients
149            .iter()
150            .map(|provider| (provider.origin(), provider.clone()))
151            .collect();
152
153        let (async_refresh_tx, async_refresh_rx) = mpsc::channel(ASYNC_REFRESH_QUEUE);
154
155        // How many seconds before expiry should we be refreshing an entry.
156        // We want to balance this - we don't want too many refreshes, but we
157        // also need to account for long cache times and ensuring that we are checking
158        // account validity in the background.
159
160        let timeout_seconds =
161            timeout_seconds.clamp(DEFAULT_CACHE_TIMEOUT_MINIMUM, DEFAULT_CACHE_TIMEOUT_MAXIMUM);
162        let async_refresh_seconds = (timeout_seconds / 3) * 2;
163
164        // We assume we are offline at start up, and we mark the next "online check" as
165        // being valid from "now".
166        Ok((
167            Resolver {
168                db,
169                hsm,
170                system_provider,
171                clients,
172                primary_origin,
173                client_ids,
174                timeout_seconds,
175                async_refresh_seconds,
176                default_shell,
177                home_prefix,
178                home_attr,
179                home_alias,
180                uid_attr_map,
181                gid_attr_map,
182                nxcache: Mutex::new(LruCache::new(NXCACHE_SIZE)),
183                async_refresh_tx,
184            },
185            async_refresh_rx,
186        ))
187    }
188
189    #[instrument(level = "debug", skip_all)]
190    pub async fn mark_next_check_now(&self, now: SystemTime) {
191        for c in self.clients.iter() {
192            c.mark_next_check(now).await;
193        }
194    }
195
196    #[instrument(level = "debug", skip_all)]
197    pub async fn mark_offline(&self) {
198        for c in self.clients.iter() {
199            c.mark_offline().await;
200        }
201    }
202
203    #[instrument(level = "debug", skip_all)]
204    pub async fn clear_cache(&self) -> Result<(), ()> {
205        let mut dbtxn = self.db.write().await;
206        let mut nxcache_txn = self.nxcache.lock().await;
207        nxcache_txn.clear();
208        dbtxn.clear().and_then(|_| dbtxn.commit()).map_err(|_| ())
209    }
210
211    #[instrument(level = "debug", skip_all)]
212    pub async fn invalidate(&self) -> Result<(), ()> {
213        let mut dbtxn = self.db.write().await;
214        let mut nxcache_txn = self.nxcache.lock().await;
215        nxcache_txn.clear();
216        dbtxn
217            .invalidate()
218            .and_then(|_| dbtxn.commit())
219            .map_err(|_| ())
220    }
221
222    #[instrument(level = "debug", skip_all)]
223    async fn get_cached_usertokens(&self) -> Result<Vec<UserToken>, ()> {
224        let mut dbtxn = self.db.write().await;
225        dbtxn.get_accounts().map_err(|_| ())
226    }
227
228    #[instrument(level = "debug", skip_all)]
229    async fn get_cached_grouptokens(&self) -> Result<Vec<GroupToken>, ()> {
230        let mut dbtxn = self.db.write().await;
231        dbtxn.get_groups().map_err(|_| ())
232    }
233
234    #[instrument(level = "debug", skip_all)]
235    async fn set_nxcache(&self, id: &Id) {
236        let mut nxcache_txn = self.nxcache.lock().await;
237        // To try and prevent too many requests occuring all at the same time, we subtract a small
238        // amount of "jitter" from expiry values so that we space out refreshes.
239        let jitter = rand::random_range(0..DEFAULT_CACHE_TIMEOUT_JITTER_MS);
240        let ex_time = SystemTime::now() + Duration::from_secs(self.timeout_seconds)
241            - Duration::from_millis(jitter);
242        nxcache_txn.put(id.clone(), ex_time);
243    }
244
245    #[instrument(level = "debug", skip_all)]
246    pub async fn check_nxcache(&self, id: &Id) -> Option<SystemTime> {
247        let mut nxcache_txn = self.nxcache.lock().await;
248        nxcache_txn.get(id).copied()
249    }
250
251    #[instrument(level = "info", skip_all)]
252    pub async fn reload_system_identities(
253        &self,
254        users: Vec<EtcUser>,
255        shadow: Vec<EtcShadow>,
256        groups: Vec<EtcGroup>,
257    ) {
258        self.system_provider.reload(users, shadow, groups).await
259    }
260
261    #[instrument(level = "debug", skip_all)]
262    async fn get_cached_usertoken(
263        &self,
264        account_id: &Id,
265        current_time: SystemTime,
266    ) -> Result<(ExpiryState, Option<UserToken>), ()> {
267        // Is it in the nxcache?
268        if let Some(ex_time) = self.check_nxcache(account_id).await {
269            if current_time >= ex_time {
270                // It's in the LRU, but we are past the expiry so
271                // lets attempt a refresh.
272                return Ok((ExpiryState::ValidRefresh, None));
273            } else {
274                // It's in the LRU and still valid, so return that
275                // no check is needed.
276                return Ok((ExpiryState::Valid, None));
277            }
278        }
279
280        // Account_id could be:
281        //  * gidnumber
282        //  * name
283        //  * spn
284        //  * uuid
285        //  Attempt to search these in the db.
286        let mut dbtxn = self.db.write().await;
287        let r = dbtxn.get_account(account_id).map_err(|err| {
288            debug!(?err, "get_cached_usertoken");
289        })?;
290
291        drop(dbtxn);
292
293        match r {
294            Some((ut, ex)) => {
295                // Are we expired?
296                let ex_time = SystemTime::UNIX_EPOCH + Duration::from_secs(ex);
297
298                // Should we async refresh?
299                let async_ref_time = if ex > self.async_refresh_seconds {
300                    SystemTime::UNIX_EPOCH + Duration::from_secs(ex - self.async_refresh_seconds)
301                } else {
302                    ex_time
303                };
304
305                if current_time >= ex_time {
306                    Ok((ExpiryState::Expired, Some(ut)))
307                } else if current_time >= async_ref_time {
308                    Ok((ExpiryState::ValidRefresh, Some(ut)))
309                } else {
310                    Ok((ExpiryState::Valid, Some(ut)))
311                }
312            }
313            None => {
314                // it wasn't in the DB, and it wasn't in the nxcache.
315                Ok((ExpiryState::Expired, None))
316            }
317        } // end match r
318    }
319
320    #[instrument(level = "debug", skip_all)]
321    async fn get_cached_grouptoken(&self, grp_id: &Id) -> Result<(bool, Option<GroupToken>), ()> {
322        let current_time = SystemTime::now();
323        // Is it in the nxcache?
324        if let Some(ex_time) = self.check_nxcache(grp_id).await {
325            if current_time >= ex_time {
326                // It's in the LRU, but we are past the expiry so
327                // lets attempt a refresh.
328                return Ok((true, None));
329            } else {
330                // It's in the LRU and still valid, so return that
331                // no check is needed.
332                return Ok((false, None));
333            }
334        }
335        // grp_id could be:
336        //  * gidnumber
337        //  * name
338        //  * spn
339        //  * uuid
340        //  Attempt to search these in the db.
341        let mut dbtxn = self.db.write().await;
342        let r = dbtxn.get_group(grp_id).map_err(|err| {
343            debug!(?err, "get_cached_grouptoken");
344        })?;
345
346        drop(dbtxn);
347
348        match r {
349            Some((ut, ex)) => {
350                // Are we expired?
351                let offset = Duration::from_secs(ex);
352                let ex_time = SystemTime::UNIX_EPOCH + offset;
353
354                if current_time >= ex_time {
355                    Ok((true, Some(ut)))
356                } else {
357                    Ok((false, Some(ut)))
358                }
359            }
360            None => {
361                // it wasn't in the DB, and it wasn't in the nxcache.
362                Ok((true, None))
363            }
364        }
365    }
366
367    #[instrument(level = "debug", skip_all)]
368    async fn set_cache_usertoken(
369        &self,
370        token: &mut UserToken,
371        // This is just for proof that only one write can occur at a time.
372        _tpm: &mut BoxedDynTpm,
373    ) -> Result<(), ()> {
374        // Set an expiry
375        // To try and prevent too many requests occuring all at the same time, we subtract a small
376        // amount of "jitter" from expiry values so that we space out refreshes.
377        let jitter = rand::random_range(0..DEFAULT_CACHE_TIMEOUT_JITTER_MS);
378        let ex_time = SystemTime::now() + Duration::from_secs(self.timeout_seconds)
379            - Duration::from_millis(jitter);
380        let offset = ex_time
381            .duration_since(SystemTime::UNIX_EPOCH)
382            .map_err(|e| {
383                error!(
384                    "Time conversion error - cache expiry time became less than epoch? {:?}",
385                    e
386                );
387            })?;
388
389        // Check if requested `shell` exists on the system, else use `default_shell`
390        let maybe_shell = token.shell.as_ref().map(PathBuf::from);
391
392        let requested_shell_exists = if let Some(shell_path) = maybe_shell.as_ref() {
393            // Does the shell path as configured exist?
394            let mut exists = shell_path
395                .canonicalize()
396                .map_err(|err| {
397                    debug!(
398                        "Failed to canonicalize path, using base path. Tried: {} Error: {:?}",
399                        shell_path.to_string_lossy(),
400                        err
401                    );
402                })
403                .unwrap_or(Path::new(shell_path).to_path_buf())
404                .exists();
405
406            if !exists {
407                // Does the shell binary exist in a search path that is configured?
408                if let Some(shell_binary_name) = shell_path.file_name() {
409                    for search_path in DEFAULT_SHELL_SEARCH_PATHS {
410                        //
411                        let shell_path = Path::new(search_path).join(shell_binary_name);
412                        if shell_path.exists() {
413                            // Okay, the binary name exists but in an alternate path. This can
414                            // commonly occur with freebsd where the shell may be installed
415                            // in /usr/local/bin instead of /bin.
416                            //
417                            // This could also occur if the user configured the shell as "zsh"
418                            // rather than an absolute path.
419                            let Some(shell_path_utf8) = shell_path.to_str().map(String::from)
420                            else {
421                                warn!("Configured shell \"{}\" for {} was found but the complete path is not valid utf-8 and can not be used.",
422                                        shell_binary_name.to_string_lossy(), token.name);
423                                continue;
424                            };
425
426                            // Update the path
427                            token.shell = Some(shell_path_utf8);
428                            // We exist
429                            exists = true;
430                            // No need to loop any more
431                            break;
432                        }
433                    }
434                }
435            }
436
437            if !exists {
438                warn!(
439                        "Configured shell \"{}\" for {} is not present on this system. Check `/etc/shells` for valid shell options.",
440                        shell_path.to_string_lossy(), token.name
441                    )
442            }
443
444            exists
445        } else {
446            info!("User has not specified a shell, using default");
447            false
448        };
449
450        if !requested_shell_exists {
451            token.shell = Some(self.default_shell.clone())
452        }
453
454        let mut dbtxn = self.db.write().await;
455        token
456            .groups
457            .iter()
458            // We need to add the groups first
459            .try_for_each(|g| dbtxn.update_group(g, offset.as_secs()))
460            .and_then(|_|
461                // So that when we add the account it can make the relationships.
462                dbtxn
463                    .update_account(token, offset.as_secs()))
464            .and_then(|_| dbtxn.commit())
465            .map_err(|_| ())
466    }
467
468    #[instrument(level = "debug", skip_all)]
469    async fn set_cache_grouptoken(&self, token: &GroupToken) -> Result<(), ()> {
470        // Set an expiry
471        let ex_time = SystemTime::now() + Duration::from_secs(self.timeout_seconds);
472        let offset = ex_time
473            .duration_since(SystemTime::UNIX_EPOCH)
474            .map_err(|e| {
475                error!("time conversion error - ex_time less than epoch? {:?}", e);
476            })?;
477
478        let mut dbtxn = self.db.write().await;
479        dbtxn
480            .update_group(token, offset.as_secs())
481            .and_then(|_| dbtxn.commit())
482            .map_err(|_| ())
483    }
484
485    #[instrument(level = "debug", skip_all)]
486    async fn delete_cache_usertoken(&self, a_uuid: Uuid) -> Result<(), ()> {
487        let mut dbtxn = self.db.write().await;
488        dbtxn
489            .delete_account(a_uuid)
490            .and_then(|_| dbtxn.commit())
491            .map_err(|_| ())
492    }
493
494    #[instrument(level = "debug", skip_all)]
495    async fn delete_cache_grouptoken(&self, g_uuid: Uuid) -> Result<(), ()> {
496        let mut dbtxn = self.db.write().await;
497        dbtxn
498            .delete_group(g_uuid)
499            .and_then(|_| dbtxn.commit())
500            .map_err(|_| ())
501    }
502
503    #[instrument(level = "debug", skip_all)]
504    pub async fn refresh_usertoken(
505        &self,
506        account_id: &Id,
507        current_time: SystemTime,
508    ) -> Result<Option<UserToken>, ()> {
509        let mut hsm_lock = self.hsm.lock().await;
510
511        // We need to re-acquire the token now behind the hsmlock - this is so that
512        // we know that as we write the updated token, we know that no one else has
513        // written to this token, since we are now the only task that is allowed
514        // to be in a write phase.
515        let token = self
516            .get_cached_usertoken(account_id, current_time)
517            .await
518            .map(|(_expired, option_token)| option_token)
519            .map_err(|err| {
520                debug!(?err, "get_usertoken error");
521            })?;
522
523        let user_get_result = if let Some(tok) = token.as_ref() {
524            // Re-use the provider that the token is from.
525            match self.client_ids.get(&tok.provider) {
526                Some(client) => {
527                    client
528                        .unix_user_get(
529                            account_id,
530                            token.as_ref(),
531                            hsm_lock.deref_mut(),
532                            current_time,
533                        )
534                        .await
535                }
536                None => {
537                    error!(provider = ?tok.provider, "Token was resolved by a provider that no longer appears to be present.");
538
539                    // We don't want to use a token from a former provider, we want it refreshed,
540                    // so lets indicate that we didn't find the token. If we return useCcahed like
541                    // we did previously, we'd never clear and reset this token since we'd never
542                    // locate it's provider.
543                    Ok(UserTokenState::NotFound)
544                }
545            }
546        } else {
547            // We've never seen it before, so iterate over the providers in priority order.
548            'search: {
549                for client in self.clients.iter() {
550                    match client
551                        .unix_user_get(
552                            account_id,
553                            token.as_ref(),
554                            hsm_lock.deref_mut(),
555                            current_time,
556                        )
557                        .await
558                    {
559                        // Ignore this one.
560                        Ok(UserTokenState::NotFound) => {}
561                        result => break 'search result,
562                    }
563                }
564                break 'search Ok(UserTokenState::NotFound);
565            }
566        };
567
568        match user_get_result {
569            Ok(UserTokenState::Update(mut n_tok)) => {
570                // We have the token!
571                self.set_cache_usertoken(&mut n_tok, hsm_lock.deref_mut())
572                    .await?;
573                Ok(Some(n_tok))
574            }
575            Ok(UserTokenState::NotFound) => {
576                // It previously existed, so now purge it.
577                if let Some(tok) = token {
578                    self.delete_cache_usertoken(tok.uuid).await?;
579                };
580                // Cache the NX here.
581                self.set_nxcache(account_id).await;
582                Ok(None)
583            }
584            Ok(UserTokenState::UseCached) => Ok(token),
585            Err(err) => {
586                // Something went wrong, we don't know what, but lets return the token
587                // anyway.
588                error!(?err);
589                Ok(token)
590            }
591        }
592    }
593
594    #[instrument(level = "debug", skip_all)]
595    async fn refresh_grouptoken(
596        &self,
597        grp_id: &Id,
598        token: Option<GroupToken>,
599        current_time: SystemTime,
600    ) -> Result<Option<GroupToken>, ()> {
601        let mut hsm_lock = self.hsm.lock().await;
602
603        let group_get_result = if let Some(tok) = token.as_ref() {
604            // Re-use the provider that the token is from.
605            match self.client_ids.get(&tok.provider) {
606                Some(client) => {
607                    client
608                        .unix_group_get(grp_id, hsm_lock.deref_mut(), current_time)
609                        .await
610                }
611                None => {
612                    error!(provider = ?tok.provider, "Token was resolved by a provider that no longer appears to be present.");
613                    // We don't want to use a token from a former provider, we want it refreshed,
614                    // so lets indicate that we didn't find the token. If we return useCcahed like
615                    // we did previously, we'd never clear and reset this token since we'd never
616                    // locate it's provider.
617                    Ok(GroupTokenState::NotFound)
618                }
619            }
620        } else {
621            // We've never seen it before, so iterate over the providers in priority order.
622            'search: {
623                for client in self.clients.iter() {
624                    match client
625                        .unix_group_get(grp_id, hsm_lock.deref_mut(), current_time)
626                        .await
627                    {
628                        // Ignore this one.
629                        Ok(GroupTokenState::NotFound) => {}
630                        result => break 'search result,
631                    }
632                }
633                break 'search Ok(GroupTokenState::NotFound);
634            }
635        };
636
637        drop(hsm_lock);
638
639        match group_get_result {
640            Ok(GroupTokenState::Update(n_tok)) => {
641                self.set_cache_grouptoken(&n_tok).await?;
642                Ok(Some(n_tok))
643            }
644            Ok(GroupTokenState::NotFound) => {
645                if let Some(tok) = token {
646                    self.delete_cache_grouptoken(tok.uuid).await?;
647                };
648                // Cache the NX here.
649                self.set_nxcache(grp_id).await;
650                Ok(None)
651            }
652            Ok(GroupTokenState::UseCached) => Ok(token),
653            Err(err) => {
654                // Some other transient error, continue with the token.
655                error!(?err);
656                Ok(token)
657            }
658        }
659    }
660
661    #[instrument(level = "trace", skip_all)]
662    async fn get_usertoken(
663        &self,
664        account_id: &Id,
665        current_time: SystemTime,
666    ) -> Result<Option<UserToken>, ()> {
667        // get the item from the cache
668        let (expiry_state, item) = self
669            .get_cached_usertoken(account_id, current_time)
670            .await
671            .map_err(|e| {
672                debug!("get_usertoken error -> {:?}", e);
673            })?;
674
675        // If the token isn't found, get_cached will set expired = true.
676        if expiry_state == ExpiryState::Expired {
677            self.refresh_usertoken(account_id, current_time).await
678        } else {
679            if expiry_state == ExpiryState::ValidRefresh {
680                // We don't mind if the buffer is full.
681                if self.async_refresh_tx.try_send(account_id.clone()).is_err() {
682                    debug!(?account_id, "unable to queue async refresh");
683                }
684            }
685            // Still valid, return the cached entry.
686            Ok(item)
687        }
688        .map(|t| {
689            trace!("token -> {:?}", t);
690            t
691        })
692    }
693
694    #[instrument(level = "trace", skip(self))]
695    async fn get_grouptoken(
696        &self,
697        grp_id: Id,
698        current_time: SystemTime,
699    ) -> Result<Option<GroupToken>, ()> {
700        let (expired, item) = self.get_cached_grouptoken(&grp_id).await.map_err(|e| {
701            debug!("get_grouptoken error -> {:?}", e);
702        })?;
703
704        if expired {
705            self.refresh_grouptoken(&grp_id, item, current_time).await
706        } else {
707            // Still valid, return the cached entry.
708            Ok(item)
709        }
710        .map(|t| {
711            trace!("token -> {:?}", t);
712            t
713        })
714    }
715
716    async fn get_groupmembers(&self, g_uuid: Uuid) -> Vec<String> {
717        let mut dbtxn = self.db.write().await;
718
719        dbtxn
720            .get_group_members(g_uuid)
721            .unwrap_or_else(|_| Vec::new())
722            .into_iter()
723            .map(|ut| self.token_uidattr(&ut))
724            .collect()
725    }
726
727    // Get ssh keys for an account id
728    #[instrument(level = "debug", skip(self))]
729    pub async fn get_sshkeys(&self, account_id: &str) -> Result<Vec<String>, ()> {
730        let current_time = SystemTime::now();
731        let token = self
732            .get_usertoken(&Id::Name(account_id.to_string()), current_time)
733            .await?;
734        Ok(token
735            .map(|t| {
736                // Only return keys if the account is valid
737                if t.valid {
738                    t.sshkeys
739                } else {
740                    Vec::with_capacity(0)
741                }
742            })
743            .unwrap_or_else(|| Vec::with_capacity(0)))
744    }
745
746    fn token_homedirectory_alias(&self, token: &UserToken) -> Option<String> {
747        let is_primary_origin = token.provider == self.primary_origin;
748        self.home_alias.map(|t| match t {
749            // If we have an alias. use it.
750            HomeAttr::Name if is_primary_origin => token.name.as_str().to_string(),
751            HomeAttr::Uuid => token.uuid.hyphenated().to_string(),
752            HomeAttr::Spn | HomeAttr::Name => token.spn.as_str().to_string(),
753        })
754    }
755
756    fn token_homedirectory_attr(&self, token: &UserToken) -> String {
757        let is_primary_origin = token.provider == self.primary_origin;
758        match self.home_attr {
759            HomeAttr::Name if is_primary_origin => token.name.as_str().to_string(),
760            HomeAttr::Uuid => token.uuid.hyphenated().to_string(),
761            HomeAttr::Spn | HomeAttr::Name => token.spn.as_str().to_string(),
762        }
763    }
764
765    fn token_homedirectory(&self, token: &UserToken) -> String {
766        self.token_homedirectory_alias(token)
767            .unwrap_or_else(|| self.token_homedirectory_attr(token))
768    }
769
770    fn token_abs_homedirectory(&self, token: &UserToken) -> String {
771        self.home_prefix
772            .join(self.token_homedirectory(token))
773            .to_string_lossy()
774            .to_string()
775    }
776
777    fn token_uidattr(&self, token: &UserToken) -> String {
778        let is_primary_origin = token.provider == self.primary_origin;
779        match self.uid_attr_map {
780            UidAttr::Name if is_primary_origin => token.name.as_str(),
781            UidAttr::Spn | UidAttr::Name => token.spn.as_str(),
782        }
783        .to_string()
784    }
785
786    #[instrument(level = "debug", skip_all)]
787    pub async fn get_nssaccounts(&self) -> Result<Vec<NssUser>, ()> {
788        // We don't need to filter the cached tokens as the cache shouldn't
789        // have anything that collides with system.
790        let system_nss_users = self.system_provider.get_nssaccounts().await;
791
792        let cached = self.get_cached_usertokens().await?;
793
794        Ok(system_nss_users
795            .into_iter()
796            .chain(cached.into_iter().map(|tok| NssUser {
797                homedir: self.token_abs_homedirectory(&tok),
798                name: self.token_uidattr(&tok),
799                uid: tok.gidnumber,
800                gid: tok.gidnumber,
801                gecos: tok.displayname,
802                shell: tok.shell.unwrap_or_else(|| self.default_shell.clone()),
803            }))
804            .collect())
805    }
806
807    #[instrument(level = "trace", skip_all)]
808    async fn get_nssaccount(
809        &self,
810        account_id: Id,
811        current_time: SystemTime,
812    ) -> Result<Option<NssUser>, ()> {
813        if let Some(nss_user) = self.system_provider.get_nssaccount(&account_id).await {
814            debug!("system provider satisfied request");
815            return Ok(Some(nss_user));
816        }
817
818        let token = self.get_usertoken(&account_id, current_time).await?;
819        Ok(token.map(|tok| NssUser {
820            homedir: self.token_abs_homedirectory(&tok),
821            name: self.token_uidattr(&tok),
822            uid: tok.gidnumber,
823            gid: tok.gidnumber,
824            gecos: tok.displayname,
825            shell: tok.shell.unwrap_or_else(|| self.default_shell.clone()),
826        }))
827    }
828
829    #[instrument(level = "debug", skip(self))]
830    pub async fn get_nssaccount_name_time(
831        &self,
832        account_id: &str,
833        current_time: SystemTime,
834    ) -> Result<Option<NssUser>, ()> {
835        self.get_nssaccount(Id::Name(account_id.to_string()), current_time)
836            .await
837    }
838
839    #[instrument(level = "debug", skip(self))]
840    pub async fn get_nssaccount_name(&self, account_id: &str) -> Result<Option<NssUser>, ()> {
841        let current_time = SystemTime::now();
842        self.get_nssaccount(Id::Name(account_id.to_string()), current_time)
843            .await
844    }
845
846    #[instrument(level = "debug", skip(self))]
847    pub async fn get_nssaccount_gid(&self, gid: u32) -> Result<Option<NssUser>, ()> {
848        let current_time = SystemTime::now();
849        self.get_nssaccount(Id::Gid(gid), current_time).await
850    }
851
852    fn token_gidattr(&self, token: &GroupToken) -> String {
853        match self.gid_attr_map {
854            UidAttr::Spn => token.spn.as_str(),
855            UidAttr::Name => token.name.as_str(),
856        }
857        .to_string()
858    }
859
860    #[instrument(level = "debug", skip_all)]
861    pub async fn get_nssgroups(&self) -> Result<Vec<NssGroup>, ()> {
862        let mut r = self.system_provider.get_nssgroups().await;
863
864        // Extend all the local groups if maps exist.
865        for nss_group in r.iter_mut() {
866            for client in self.clients.iter() {
867                if let Some(extend_group_id) = client.has_map_group(&nss_group.name) {
868                    let (_, token) = self.get_cached_grouptoken(extend_group_id).await?;
869                    if let Some(token) = token {
870                        let members = self.get_groupmembers(token.uuid).await;
871                        nss_group.members.extend(members);
872                        debug!(
873                            "extended group {} with members from {}",
874                            nss_group.name, token.name
875                        );
876                    }
877                }
878            }
879        }
880
881        let l = self.get_cached_grouptokens().await?;
882        r.reserve(l.len());
883        for tok in l.into_iter() {
884            let members = self.get_groupmembers(tok.uuid).await;
885            r.push(NssGroup {
886                name: self.token_gidattr(&tok),
887                gid: tok.gidnumber,
888                members,
889            })
890        }
891        Ok(r)
892    }
893
894    #[instrument(level = "trace", skip_all)]
895    async fn get_nssgroup(&self, grp_id: Id) -> Result<Option<NssGroup>, ()> {
896        if let Some(mut nss_group) = self.system_provider.get_nssgroup(&grp_id).await {
897            debug!("system provider satisfied request");
898
899            for client in self.clients.iter() {
900                if let Some(extend_group_id) = client.has_map_group(&nss_group.name) {
901                    let token = self
902                        .get_grouptoken(extend_group_id.clone(), SystemTime::now())
903                        .await?;
904                    if let Some(token) = token {
905                        let members = self.get_groupmembers(token.uuid).await;
906                        nss_group.members.extend(members);
907                        debug!(
908                            "extended group {} with members from {}",
909                            nss_group.name, token.name
910                        );
911                    }
912                }
913            }
914
915            nss_group.members.sort_unstable();
916            nss_group.members.dedup();
917
918            return Ok(Some(nss_group));
919        }
920
921        let token = self.get_grouptoken(grp_id, SystemTime::now()).await?;
922        // Get members set.
923        match token {
924            Some(tok) => {
925                let members = self.get_groupmembers(tok.uuid).await;
926                Ok(Some(NssGroup {
927                    name: self.token_gidattr(&tok),
928                    gid: tok.gidnumber,
929                    members,
930                }))
931            }
932            None => Ok(None),
933        }
934    }
935
936    #[instrument(level = "debug", skip(self))]
937    pub async fn get_nssgroup_name(&self, grp_id: &str) -> Result<Option<NssGroup>, ()> {
938        self.get_nssgroup(Id::Name(grp_id.to_string())).await
939    }
940
941    #[instrument(level = "debug", skip(self))]
942    pub async fn get_nssgroup_gid(&self, gid: u32) -> Result<Option<NssGroup>, ()> {
943        self.get_nssgroup(Id::Gid(gid)).await
944    }
945
946    #[instrument(level = "debug", skip(self))]
947    pub async fn pam_account_allowed(&self, account_id: &str) -> Result<Option<bool>, ()> {
948        let current_time = SystemTime::now();
949        let id = Id::Name(account_id.to_string());
950
951        if let Some(answer) = self.system_provider.authorise(&id).await {
952            return Ok(Some(answer));
953        };
954
955        // Not a system account, handle with the provider.
956        let token = self.get_usertoken(&id, current_time).await?;
957
958        // If there is no token, return Ok(None) to trigger unknown-user path in pam.
959        match token {
960            Some(token) => {
961                let client = self.client_ids.get(&token.provider)
962                    .cloned()
963                    .ok_or_else(|| {
964                        error!(provider = ?token.provider, "Token was resolved by a provider that no longer appears to be present.");
965                    })?;
966
967                client.unix_user_authorise(&token).await.map_err(|err| {
968                    error!(?err, "unable to authorise account");
969                })
970            }
971            None => Ok(None),
972        }
973    }
974
975    #[instrument(level = "debug", skip(self, shutdown_rx, current_time))]
976    pub async fn pam_account_authenticate_init(
977        &self,
978        account_id: &str,
979        pam_info: &PamServiceInfo,
980        current_time: OffsetDateTime,
981        shutdown_rx: broadcast::Receiver<()>,
982    ) -> Result<(AuthSession, PamAuthResponse), ()> {
983        // Setup an auth session. If possible bring the resolver online.
984        // Further steps won't attempt to bring the cache online to prevent
985        // weird interactions - they should assume online/offline only for
986        // the duration of their operation. A failure of connectivity during
987        // an online operation will take the cache offline however.
988        let now = SystemTime::now();
989
990        let id = Id::Name(account_id.to_string());
991
992        match self.system_provider.auth_init(&id, current_time).await {
993            // The system provider will not take part in this authentication.
994            SystemProviderAuthInit::Ignore => {
995                debug!(?account_id, "account unknown to system provider, continue.");
996            }
997            // The provider knows the account, and is unable to proceed,
998            // We return unknown here so that pam_kanidm can be skipped and fall back
999            // to pam_unix.so.
1000            SystemProviderAuthInit::ShadowMissing => {
1001                warn!(
1002                    ?account_id,
1003                    "Resolver unable to proceed, {SYSTEM_SHADOW_PATH} was not accessible."
1004                );
1005                return Ok((AuthSession::Denied, PamAuthResponse::Unknown));
1006            }
1007            // There are no credentials for this account
1008            SystemProviderAuthInit::CredentialsUnavailable => {
1009                warn!(
1010                    ?account_id,
1011                    "Denying auth request for system user with no valid credentials"
1012                );
1013                return Ok((AuthSession::Denied, PamAuthResponse::Denied));
1014            }
1015            // The account has expired
1016            SystemProviderAuthInit::Expired => {
1017                warn!(
1018                    ?account_id,
1019                    "Denying auth request for system user with expired credentials"
1020                );
1021                return Ok((AuthSession::Denied, PamAuthResponse::Denied));
1022            }
1023            // The provider knows the account and wants to proceed,
1024            SystemProviderAuthInit::Begin {
1025                next_request,
1026                cred_handler,
1027                shadow,
1028            } => {
1029                let auth_session = AuthSession::System {
1030                    account_id: account_id.to_string(),
1031                    id,
1032                    shadow,
1033                    cred_handler,
1034                };
1035
1036                return Ok((auth_session, next_request.into()));
1037            }
1038        }
1039
1040        let token = self.get_usertoken(&id, now).await?;
1041
1042        // Get the provider associated to this token.
1043
1044        let mut hsm_lock = self.hsm.lock().await;
1045
1046        // We don't care if we are expired - we will always attempt to go
1047        // online and perform this operation online if possible.
1048
1049        if let Some(token) = token {
1050            // We have a token, we know what provider is needed
1051            let client = self.client_ids.get(&token.provider)
1052                .cloned()
1053                .ok_or_else(|| {
1054                    error!(provider = ?token.provider, "Token was resolved by a provider that no longer appears to be present.");
1055                })?;
1056
1057            let online_at_init = client.attempt_online(hsm_lock.deref_mut(), now).await;
1058            // if we are online, we try and start an online auth.
1059            debug!(?online_at_init);
1060
1061            if online_at_init {
1062                let init_result = client
1063                    .unix_user_online_auth_init(
1064                        account_id,
1065                        &token,
1066                        hsm_lock.deref_mut(),
1067                        &shutdown_rx,
1068                    )
1069                    .await;
1070
1071                match init_result {
1072                    Ok((next_req, cred_handler)) => {
1073                        let auth_session = AuthSession::Online {
1074                            client,
1075                            account_id: account_id.to_string(),
1076                            id,
1077                            cred_handler,
1078                            shutdown_rx,
1079                        };
1080                        Ok((auth_session, next_req.into()))
1081                    }
1082                    Err(err) => {
1083                        error!(?err, "Unable to start authentication");
1084                        Err(())
1085                    }
1086                }
1087            } else {
1088                // Can the auth proceed offline?
1089                let init_result = client.unix_user_offline_auth_init(&token).await;
1090
1091                match init_result {
1092                    Ok((next_req, cred_handler)) => {
1093                        let auth_session = AuthSession::Offline {
1094                            account_id: account_id.to_string(),
1095                            id,
1096                            client,
1097                            session_token: Box::new(token),
1098                            cred_handler,
1099                        };
1100                        Ok((auth_session, next_req.into()))
1101                    }
1102                    Err(err) => {
1103                        error!(?err, "Unable to start authentication");
1104                        Err(())
1105                    }
1106                }
1107            }
1108        } else {
1109            // We don't know anything about this user. Can we try to auth them?
1110
1111            // TODO: If any provider is offline should we fail the auth? I can imagine a possible
1112            // issue where if we had provides A, B, C stacked, and A was offline, then B could
1113            // service an auth that A *should* have serviced.
1114
1115            for client in self.clients.iter() {
1116                let online_at_init = client.attempt_online(hsm_lock.deref_mut(), now).await;
1117                debug!(?online_at_init);
1118
1119                if !online_at_init {
1120                    warn!(?account_id, "Unable to proceed with authentication, all providers must be online for unknown user authentication.");
1121                    return Ok((AuthSession::Denied, PamAuthResponse::Unknown));
1122                }
1123            }
1124
1125            for client in self.clients.iter() {
1126                let init_result = client
1127                    .unix_unknown_user_online_auth_init(
1128                        account_id,
1129                        hsm_lock.deref_mut(),
1130                        &shutdown_rx,
1131                    )
1132                    .await;
1133
1134                match init_result {
1135                    Ok(Some((next_req, cred_handler))) => {
1136                        let auth_session = AuthSession::Online {
1137                            client: client.clone(),
1138                            account_id: account_id.to_string(),
1139                            id,
1140                            cred_handler,
1141                            shutdown_rx,
1142                        };
1143                        return Ok((auth_session, next_req.into()));
1144                    }
1145                    Ok(None) => {
1146                        // Not for us, check the next provider.
1147                    }
1148                    Err(err) => {
1149                        error!(?err, "Unable to start authentication");
1150                        return Err(());
1151                    }
1152                }
1153            }
1154
1155            // No module signaled that they want it, bail.
1156            warn!("No provider is willing to service authentication of unknown account.");
1157            Ok((AuthSession::Denied, PamAuthResponse::Unknown))
1158        }
1159    }
1160
1161    #[instrument(level = "debug", skip_all)]
1162    pub async fn pam_account_authenticate_step(
1163        &self,
1164        auth_session: &mut AuthSession,
1165        pam_next_req: PamAuthRequest,
1166    ) -> Result<PamAuthResponse, ()> {
1167        let current_time = SystemTime::now();
1168        let mut hsm_lock = self.hsm.lock().await;
1169
1170        let maybe_err = match &mut *auth_session {
1171            &mut AuthSession::Online {
1172                ref client,
1173                ref account_id,
1174                ref id,
1175                ref mut cred_handler,
1176                ref shutdown_rx,
1177            } => {
1178                // This is not used in the authentication, but is so that any new
1179                // extra keys or data on the token are updated correctly if the authentication
1180                // requests an update. Since we hold the hsm_lock, no other task can
1181                // update this token between now and completion of the fn.
1182                let current_token = self
1183                    .get_cached_usertoken(id, current_time)
1184                    .await
1185                    .map(|(_expired, option_token)| option_token)
1186                    .map_err(|err| {
1187                        debug!(?err, "get_usertoken error");
1188                    })?;
1189
1190                let result = client
1191                    .unix_user_online_auth_step(
1192                        account_id,
1193                        current_token.as_ref(),
1194                        cred_handler,
1195                        pam_next_req,
1196                        hsm_lock.deref_mut(),
1197                        shutdown_rx,
1198                    )
1199                    .await;
1200
1201                match result {
1202                    Ok(AuthResult::SuccessUpdate { .. } | AuthResult::Success) => {
1203                        info!(?account_id, "Authentication Success");
1204                    }
1205                    Ok(AuthResult::Denied) => {
1206                        info!(?account_id, "Authentication Denied");
1207                    }
1208                    Ok(AuthResult::Next(_)) => {
1209                        info!(?account_id, "Authentication Continue");
1210                    }
1211                    _ => {}
1212                };
1213
1214                result
1215            }
1216            &mut AuthSession::Offline {
1217                ref account_id,
1218                ref id,
1219                ref client,
1220                ref session_token,
1221                ref mut cred_handler,
1222            } => {
1223                // This is not used in the authentication, but is so that any new
1224                // extra keys or data on the token are updated correctly if the authentication
1225                // requests an update. Since we hold the hsm_lock, no other task can
1226                // update this token between now and completion of the fn.
1227                let current_token = self
1228                    .get_cached_usertoken(id, current_time)
1229                    .await
1230                    .map(|(_expired, option_token)| option_token)
1231                    .map_err(|err| {
1232                        debug!(?err, "get_usertoken error");
1233                    })?;
1234
1235                // We are offline, continue. Remember, authsession should have
1236                // *everything you need* to proceed here!
1237                let result = client
1238                    .unix_user_offline_auth_step(
1239                        current_token.as_ref(),
1240                        session_token,
1241                        cred_handler,
1242                        pam_next_req,
1243                        hsm_lock.deref_mut(),
1244                    )
1245                    .await;
1246
1247                match result {
1248                    Ok(AuthResult::SuccessUpdate { .. } | AuthResult::Success) => {
1249                        info!(?account_id, "Authentication Success");
1250                    }
1251                    Ok(AuthResult::Denied) => {
1252                        info!(?account_id, "Authentication Denied");
1253                    }
1254                    Ok(AuthResult::Next(_)) => {
1255                        info!(?account_id, "Authentication Continue");
1256                    }
1257                    _ => {}
1258                };
1259
1260                result
1261            }
1262            &mut AuthSession::System {
1263                ref account_id,
1264                id: _,
1265                ref mut cred_handler,
1266                ref shadow,
1267            } => {
1268                // I had a lot of thoughts here, but I think system auth is
1269                // not the same as provider, so I think we special case here and have a separate
1270                // return type.
1271                let system_auth_result = shadow.auth_step(cred_handler, pam_next_req);
1272
1273                let next = match system_auth_result {
1274                    SystemAuthResult::Denied => {
1275                        info!(?account_id, "Authentication Denied");
1276
1277                        *auth_session = AuthSession::Denied;
1278
1279                        Ok(PamAuthResponse::Denied)
1280                    }
1281                    SystemAuthResult::Success => {
1282                        info!(?account_id, "Authentication Success");
1283
1284                        *auth_session = AuthSession::Success;
1285
1286                        Ok(PamAuthResponse::Success)
1287                    }
1288                    SystemAuthResult::Next(req) => Ok(req.into()),
1289                };
1290
1291                // We shortcut here
1292                return next;
1293            }
1294            &mut AuthSession::Success | &mut AuthSession::Denied => Err(IdpError::BadRequest),
1295        };
1296
1297        match maybe_err {
1298            // What did the provider direct us to do next?
1299            Ok(AuthResult::Success) => {
1300                *auth_session = AuthSession::Success;
1301                Ok(PamAuthResponse::Success)
1302            }
1303            Ok(AuthResult::SuccessUpdate { mut new_token }) => {
1304                self.set_cache_usertoken(&mut new_token, hsm_lock.deref_mut())
1305                    .await?;
1306                *auth_session = AuthSession::Success;
1307
1308                Ok(PamAuthResponse::Success)
1309            }
1310            Ok(AuthResult::Denied) => {
1311                *auth_session = AuthSession::Denied;
1312
1313                Ok(PamAuthResponse::Denied)
1314            }
1315            Ok(AuthResult::Next(req)) => Ok(req.into()),
1316            Err(IdpError::NotFound) => {
1317                *auth_session = AuthSession::Denied;
1318
1319                Ok(PamAuthResponse::Unknown)
1320            }
1321            Err(err) => {
1322                *auth_session = AuthSession::Denied;
1323
1324                error!(?err, "Unable to proceed, failing the session");
1325                Err(())
1326            }
1327        }
1328    }
1329
1330    // Can this be cfg debug/test?
1331    #[instrument(level = "debug", skip(self, password, current_time))]
1332    pub async fn pam_account_authenticate(
1333        &self,
1334        account_id: &str,
1335        current_time: OffsetDateTime,
1336        password: &str,
1337    ) -> Result<Option<bool>, ()> {
1338        let (_shutdown_tx, shutdown_rx) = broadcast::channel(1);
1339
1340        let pam_info = PamServiceInfo {
1341            service: "kanidm-unix-test".to_string(),
1342            tty: Some("/dev/null".to_string()),
1343            rhost: None,
1344        };
1345
1346        let mut auth_session = match self
1347            .pam_account_authenticate_init(account_id, &pam_info, current_time, shutdown_rx)
1348            .await?
1349        {
1350            (auth_session, PamAuthResponse::Password) => {
1351                // Can continue!
1352                auth_session
1353            }
1354            (auth_session, PamAuthResponse::DeviceAuthorizationGrant { .. }) => {
1355                // Can continue!
1356                auth_session
1357            }
1358            (auth_session, PamAuthResponse::MFACode { .. }) => {
1359                // Can continue!
1360                auth_session
1361            }
1362            (auth_session, PamAuthResponse::MFAPoll { .. }) => {
1363                // Can continue!
1364                auth_session
1365            }
1366            (auth_session, PamAuthResponse::MFAPollWait) => {
1367                // Can continue!
1368                auth_session
1369            }
1370            (auth_session, PamAuthResponse::SetupPin { .. }) => {
1371                // Can continue!
1372                auth_session
1373            }
1374            (auth_session, PamAuthResponse::Pin) => {
1375                // Can continue!
1376                auth_session
1377            }
1378            (_, PamAuthResponse::Unknown) => return Ok(None),
1379            (_, PamAuthResponse::Denied) => return Ok(Some(false)),
1380            (_, PamAuthResponse::Success) => {
1381                // Should never get here "off the rip".
1382                debug_assert!(false);
1383                return Ok(Some(true));
1384            }
1385        };
1386
1387        // Now we can make the next step.
1388        let pam_next_req = PamAuthRequest::Password {
1389            cred: password.to_string(),
1390        };
1391        match self
1392            .pam_account_authenticate_step(&mut auth_session, pam_next_req)
1393            .await?
1394        {
1395            PamAuthResponse::Success => Ok(Some(true)),
1396            PamAuthResponse::Denied => Ok(Some(false)),
1397            _ => {
1398                // Should not be able to get here, if the user was unknown they should
1399                // be out. If it wants more mechanisms, we can't proceed here.
1400                // debug_assert!(false);
1401                Ok(None)
1402            }
1403        }
1404    }
1405
1406    #[instrument(level = "debug", skip(self))]
1407    pub async fn pam_account_beginsession(
1408        &self,
1409        account_id: &str,
1410    ) -> Result<Option<HomeDirectoryInfo>, ()> {
1411        let current_time = SystemTime::now();
1412        let id = Id::Name(account_id.to_string());
1413
1414        match self.system_provider.begin_session(&id).await {
1415            SystemProviderSession::Start => {
1416                return Ok(None);
1417            }
1418            /*
1419            SystemProviderSession::StartCreateHome(
1420                info
1421            ) => {
1422                return Ok(Some(info));
1423            }
1424            */
1425            SystemProviderSession::Ignore => {}
1426        };
1427
1428        // Not a system account, check based on the token and resolve.
1429        let token = self.get_usertoken(&id, current_time).await?;
1430        Ok(token.as_ref().map(|tok| HomeDirectoryInfo {
1431            uid: tok.gidnumber,
1432            gid: tok.gidnumber,
1433            name: self.token_homedirectory_attr(tok),
1434            aliases: self
1435                .token_homedirectory_alias(tok)
1436                .map(|s| vec![s])
1437                .unwrap_or_default(),
1438        }))
1439    }
1440
1441    #[instrument(level = "debug", skip_all)]
1442    pub async fn provider_status(&self) -> Vec<ProviderStatus> {
1443        let now = SystemTime::now();
1444        let mut hsm_lock = self.hsm.lock().await;
1445
1446        let mut results = Vec::with_capacity(self.clients.len() + 1);
1447
1448        results.push(ProviderStatus {
1449            name: "system".to_string(),
1450            online: true,
1451        });
1452
1453        for client in self.clients.iter() {
1454            let online = client.attempt_online(hsm_lock.deref_mut(), now).await;
1455
1456            let name = client.origin().to_string();
1457
1458            results.push(ProviderStatus { name, online })
1459        }
1460
1461        results
1462    }
1463
1464    #[instrument(level = "debug", skip_all)]
1465    pub async fn test_connection(&self) -> bool {
1466        let now = SystemTime::now();
1467        let mut hsm_lock = self.hsm.lock().await;
1468
1469        for client in self.clients.iter() {
1470            let status = client.attempt_online(hsm_lock.deref_mut(), now).await;
1471
1472            if !status {
1473                return false;
1474            }
1475        }
1476
1477        // All online
1478        true
1479    }
1480}