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            // Can the auth proceed offline? This is important for us to make choices about
1058            // if we attempt to force an online state check.
1059            let can_proceed_offline = client.unix_user_can_offline_auth(&token).await;
1060
1061            // We can use is_online here because the earlier get_usertoken call leading up to this
1062            // will have taken the provider online if possible. This way if we are already in an
1063            // offline state, we don't needlessly delay the authentication operation.
1064            // In addition as we now have background prefetching of accounts, this will also
1065            // be checking frequently enough if we are online and will assist us to be online as
1066            // much as possible.
1067            let online_at_init = if can_proceed_offline {
1068                // This is only for accounts we have previously resolved and authenticated.
1069                client.is_online().await
1070            } else {
1071                // We know who the user is, but don't have cached credentials, so we need to try and
1072                // force online if possible.
1073                client.attempt_online(hsm_lock.deref_mut(), now).await
1074            };
1075
1076            // We're online, go for it.
1077            if online_at_init {
1078                let init_result = client
1079                    .unix_user_online_auth_init(
1080                        account_id,
1081                        &token,
1082                        hsm_lock.deref_mut(),
1083                        &shutdown_rx,
1084                    )
1085                    .await;
1086
1087                match init_result {
1088                    Ok((next_req, cred_handler)) => {
1089                        let auth_session = AuthSession::Online {
1090                            client,
1091                            account_id: account_id.to_string(),
1092                            id,
1093                            cred_handler,
1094                            shutdown_rx,
1095                        };
1096                        Ok((auth_session, next_req.into()))
1097                    }
1098                    Err(err) => {
1099                        error!(?err, "Unable to start authentication");
1100                        Err(())
1101                    }
1102                }
1103            } else {
1104                let init_result = client.unix_user_offline_auth_init(&token).await;
1105                match init_result {
1106                    Ok((next_req, cred_handler)) => {
1107                        let auth_session = AuthSession::Offline {
1108                            account_id: account_id.to_string(),
1109                            id,
1110                            client,
1111                            session_token: Box::new(token),
1112                            cred_handler,
1113                        };
1114                        Ok((auth_session, next_req.into()))
1115                    }
1116                    Err(err) => {
1117                        error!(?err, "Unable to start authentication");
1118                        Err(())
1119                    }
1120                }
1121            }
1122        } else {
1123            // We don't know anything about this user. Can we try to auth them?
1124
1125            // TODO: If any provider is offline should we fail the auth? I can imagine a possible
1126            // issue where if we had provides A, B, C stacked, and A was offline, then B could
1127            // service an auth that A *should* have serviced.
1128
1129            for client in self.clients.iter() {
1130                let online_at_init = client.attempt_online(hsm_lock.deref_mut(), now).await;
1131                debug!(?online_at_init);
1132
1133                if !online_at_init {
1134                    warn!(?account_id, "Unable to proceed with authentication, all providers must be online for unknown user authentication.");
1135                    return Ok((AuthSession::Denied, PamAuthResponse::Unknown));
1136                }
1137            }
1138
1139            for client in self.clients.iter() {
1140                let init_result = client
1141                    .unix_unknown_user_online_auth_init(
1142                        account_id,
1143                        hsm_lock.deref_mut(),
1144                        &shutdown_rx,
1145                    )
1146                    .await;
1147
1148                match init_result {
1149                    Ok(Some((next_req, cred_handler))) => {
1150                        let auth_session = AuthSession::Online {
1151                            client: client.clone(),
1152                            account_id: account_id.to_string(),
1153                            id,
1154                            cred_handler,
1155                            shutdown_rx,
1156                        };
1157                        return Ok((auth_session, next_req.into()));
1158                    }
1159                    Ok(None) => {
1160                        // Not for us, check the next provider.
1161                    }
1162                    Err(err) => {
1163                        error!(?err, "Unable to start authentication");
1164                        return Err(());
1165                    }
1166                }
1167            }
1168
1169            // No module signaled that they want it, bail.
1170            warn!("No provider is willing to service authentication of unknown account.");
1171            Ok((AuthSession::Denied, PamAuthResponse::Unknown))
1172        }
1173    }
1174
1175    #[instrument(level = "debug", skip_all)]
1176    pub async fn pam_account_authenticate_step(
1177        &self,
1178        auth_session: &mut AuthSession,
1179        pam_next_req: PamAuthRequest,
1180    ) -> Result<PamAuthResponse, ()> {
1181        let current_time = SystemTime::now();
1182        let mut hsm_lock = self.hsm.lock().await;
1183
1184        let maybe_err = match &mut *auth_session {
1185            &mut AuthSession::Online {
1186                ref client,
1187                ref account_id,
1188                ref id,
1189                ref mut cred_handler,
1190                ref shutdown_rx,
1191            } => {
1192                // This is not used in the authentication, but is so that any new
1193                // extra keys or data on the token are updated correctly if the authentication
1194                // requests an update. Since we hold the hsm_lock, no other task can
1195                // update this token between now and completion of the fn.
1196                let current_token = self
1197                    .get_cached_usertoken(id, current_time)
1198                    .await
1199                    .map(|(_expired, option_token)| option_token)
1200                    .map_err(|err| {
1201                        debug!(?err, "get_usertoken error");
1202                    })?;
1203
1204                let result = client
1205                    .unix_user_online_auth_step(
1206                        account_id,
1207                        current_token.as_ref(),
1208                        cred_handler,
1209                        pam_next_req,
1210                        hsm_lock.deref_mut(),
1211                        shutdown_rx,
1212                    )
1213                    .await;
1214
1215                match result {
1216                    Ok(AuthResult::SuccessUpdate { .. } | AuthResult::Success) => {
1217                        info!(?account_id, "Authentication Success");
1218                    }
1219                    Ok(AuthResult::Denied) => {
1220                        info!(?account_id, "Authentication Denied");
1221                    }
1222                    Ok(AuthResult::Next(_)) => {
1223                        info!(?account_id, "Authentication Continue");
1224                    }
1225                    _ => {}
1226                };
1227
1228                result
1229            }
1230            &mut AuthSession::Offline {
1231                ref account_id,
1232                ref id,
1233                ref client,
1234                ref session_token,
1235                ref mut cred_handler,
1236            } => {
1237                // This is not used in the authentication, but is so that any new
1238                // extra keys or data on the token are updated correctly if the authentication
1239                // requests an update. Since we hold the hsm_lock, no other task can
1240                // update this token between now and completion of the fn.
1241                let current_token = self
1242                    .get_cached_usertoken(id, current_time)
1243                    .await
1244                    .map(|(_expired, option_token)| option_token)
1245                    .map_err(|err| {
1246                        debug!(?err, "get_usertoken error");
1247                    })?;
1248
1249                // We are offline, continue. Remember, authsession should have
1250                // *everything you need* to proceed here!
1251                let result = client
1252                    .unix_user_offline_auth_step(
1253                        current_token.as_ref(),
1254                        session_token,
1255                        cred_handler,
1256                        pam_next_req,
1257                        hsm_lock.deref_mut(),
1258                    )
1259                    .await;
1260
1261                match result {
1262                    Ok(AuthResult::SuccessUpdate { .. } | AuthResult::Success) => {
1263                        info!(?account_id, "Authentication Success");
1264                    }
1265                    Ok(AuthResult::Denied) => {
1266                        info!(?account_id, "Authentication Denied");
1267                    }
1268                    Ok(AuthResult::Next(_)) => {
1269                        info!(?account_id, "Authentication Continue");
1270                    }
1271                    _ => {}
1272                };
1273
1274                result
1275            }
1276            &mut AuthSession::System {
1277                ref account_id,
1278                id: _,
1279                ref mut cred_handler,
1280                ref shadow,
1281            } => {
1282                // I had a lot of thoughts here, but I think system auth is
1283                // not the same as provider, so I think we special case here and have a separate
1284                // return type.
1285                let system_auth_result = shadow.auth_step(cred_handler, pam_next_req);
1286
1287                let next = match system_auth_result {
1288                    SystemAuthResult::Denied => {
1289                        info!(?account_id, "Authentication Denied");
1290
1291                        *auth_session = AuthSession::Denied;
1292
1293                        Ok(PamAuthResponse::Denied)
1294                    }
1295                    SystemAuthResult::Success => {
1296                        info!(?account_id, "Authentication Success");
1297
1298                        *auth_session = AuthSession::Success;
1299
1300                        Ok(PamAuthResponse::Success)
1301                    }
1302                    SystemAuthResult::Next(req) => Ok(req.into()),
1303                };
1304
1305                // We shortcut here
1306                return next;
1307            }
1308            &mut AuthSession::Success | &mut AuthSession::Denied => Err(IdpError::BadRequest),
1309        };
1310
1311        match maybe_err {
1312            // What did the provider direct us to do next?
1313            Ok(AuthResult::Success) => {
1314                *auth_session = AuthSession::Success;
1315                Ok(PamAuthResponse::Success)
1316            }
1317            Ok(AuthResult::SuccessUpdate { mut new_token }) => {
1318                self.set_cache_usertoken(&mut new_token, hsm_lock.deref_mut())
1319                    .await?;
1320                *auth_session = AuthSession::Success;
1321
1322                Ok(PamAuthResponse::Success)
1323            }
1324            Ok(AuthResult::Denied) => {
1325                *auth_session = AuthSession::Denied;
1326
1327                Ok(PamAuthResponse::Denied)
1328            }
1329            Ok(AuthResult::Next(req)) => Ok(req.into()),
1330            Err(IdpError::NotFound) => {
1331                *auth_session = AuthSession::Denied;
1332
1333                Ok(PamAuthResponse::Unknown)
1334            }
1335            Err(err) => {
1336                *auth_session = AuthSession::Denied;
1337
1338                error!(?err, "Unable to proceed, failing the session");
1339                Err(())
1340            }
1341        }
1342    }
1343
1344    // Can this be cfg debug/test?
1345    #[instrument(level = "debug", skip(self, password, current_time))]
1346    pub async fn pam_account_authenticate(
1347        &self,
1348        account_id: &str,
1349        current_time: OffsetDateTime,
1350        password: &str,
1351    ) -> Result<Option<bool>, ()> {
1352        let (_shutdown_tx, shutdown_rx) = broadcast::channel(1);
1353
1354        let pam_info = PamServiceInfo {
1355            service: "kanidm-unix-test".to_string(),
1356            tty: Some("/dev/null".to_string()),
1357            rhost: None,
1358        };
1359
1360        let mut auth_session = match self
1361            .pam_account_authenticate_init(account_id, &pam_info, current_time, shutdown_rx)
1362            .await?
1363        {
1364            (auth_session, PamAuthResponse::Password) => {
1365                // Can continue!
1366                auth_session
1367            }
1368            (auth_session, PamAuthResponse::DeviceAuthorizationGrant { .. }) => {
1369                // Can continue!
1370                auth_session
1371            }
1372            (auth_session, PamAuthResponse::MFACode { .. }) => {
1373                // Can continue!
1374                auth_session
1375            }
1376            (auth_session, PamAuthResponse::MFAPoll { .. }) => {
1377                // Can continue!
1378                auth_session
1379            }
1380            (auth_session, PamAuthResponse::MFAPollWait) => {
1381                // Can continue!
1382                auth_session
1383            }
1384            (auth_session, PamAuthResponse::SetupPin { .. }) => {
1385                // Can continue!
1386                auth_session
1387            }
1388            (auth_session, PamAuthResponse::Pin) => {
1389                // Can continue!
1390                auth_session
1391            }
1392            (_, PamAuthResponse::Unknown) => return Ok(None),
1393            (_, PamAuthResponse::Denied) => return Ok(Some(false)),
1394            (_, PamAuthResponse::Success) => {
1395                // Should never get here "off the rip".
1396                debug_assert!(false);
1397                return Ok(Some(true));
1398            }
1399        };
1400
1401        // Now we can make the next step.
1402        let pam_next_req = PamAuthRequest::Password {
1403            cred: password.to_string(),
1404        };
1405        match self
1406            .pam_account_authenticate_step(&mut auth_session, pam_next_req)
1407            .await?
1408        {
1409            PamAuthResponse::Success => Ok(Some(true)),
1410            PamAuthResponse::Denied => Ok(Some(false)),
1411            _ => {
1412                // Should not be able to get here, if the user was unknown they should
1413                // be out. If it wants more mechanisms, we can't proceed here.
1414                // debug_assert!(false);
1415                Ok(None)
1416            }
1417        }
1418    }
1419
1420    #[instrument(level = "debug", skip(self))]
1421    pub async fn pam_account_beginsession(
1422        &self,
1423        account_id: &str,
1424    ) -> Result<Option<HomeDirectoryInfo>, ()> {
1425        let current_time = SystemTime::now();
1426        let id = Id::Name(account_id.to_string());
1427
1428        match self.system_provider.begin_session(&id).await {
1429            SystemProviderSession::Start => {
1430                return Ok(None);
1431            }
1432            /*
1433            SystemProviderSession::StartCreateHome(
1434                info
1435            ) => {
1436                return Ok(Some(info));
1437            }
1438            */
1439            SystemProviderSession::Ignore => {}
1440        };
1441
1442        // Not a system account, check based on the token and resolve.
1443        let token = self.get_usertoken(&id, current_time).await?;
1444        Ok(token.as_ref().map(|tok| HomeDirectoryInfo {
1445            uid: tok.gidnumber,
1446            gid: tok.gidnumber,
1447            name: self.token_homedirectory_attr(tok),
1448            aliases: self
1449                .token_homedirectory_alias(tok)
1450                .map(|s| vec![s])
1451                .unwrap_or_default(),
1452        }))
1453    }
1454
1455    #[instrument(level = "debug", skip_all)]
1456    pub async fn provider_status(&self) -> Vec<ProviderStatus> {
1457        let now = SystemTime::now();
1458        let mut hsm_lock = self.hsm.lock().await;
1459
1460        let mut results = Vec::with_capacity(self.clients.len() + 1);
1461
1462        results.push(ProviderStatus {
1463            name: "system".to_string(),
1464            online: true,
1465        });
1466
1467        for client in self.clients.iter() {
1468            let online = client.attempt_online(hsm_lock.deref_mut(), now).await;
1469
1470            let name = client.origin().to_string();
1471
1472            results.push(ProviderStatus { name, online })
1473        }
1474
1475        results
1476    }
1477
1478    #[instrument(level = "debug", skip_all)]
1479    pub async fn test_connection(&self) -> bool {
1480        let now = SystemTime::now();
1481        let mut hsm_lock = self.hsm.lock().await;
1482
1483        for client in self.clients.iter() {
1484            let status = client.attempt_online(hsm_lock.deref_mut(), now).await;
1485
1486            if !status {
1487                return false;
1488            }
1489        }
1490
1491        // All online
1492        true
1493    }
1494}