Skip to main content

signstar_config/config/
traits.rs

1//! Traits for configuration use.
2
3use std::{collections::HashSet, fs::write};
4
5use nix::unistd::User;
6use signstar_common::ssh::get_ssh_authorized_key_base_dir;
7use signstar_crypto::{
8    NonAdministrativeSecretHandling,
9    passphrase::Passphrase,
10    secret_file::{load_passphrase_from_secrets_file, write_passphrase_to_secrets_file},
11    traits::UserWithPassphrase,
12};
13
14use crate::{
15    config::{AuthorizedKeyEntry, SystemUserData, SystemUserId},
16    utils::{fail_if_not_root, get_current_system_user},
17};
18
19/// An error that may occur when using signstar-config traits.
20#[derive(Debug, thiserror::Error)]
21pub enum Error {
22    /// A backend user ID does not match.
23    #[error("Expected the backend user ID {expected}, but found {actual} instead")]
24    BackendUserIdMismatch {
25        /// The expected backend user ID.
26        expected: String,
27
28        /// The actually found backend user ID.
29        actual: String,
30    },
31}
32
33/// An interface for returning an optional [`SystemUserId`] or a [`User`].
34///
35/// It is implemented by mapping implementations, that track system user data.
36///
37/// # Example
38///
39/// ```
40/// use signstar_config::config::{MappingSystemUserId, SystemUserId};
41/// use signstar_crypto::{passphrase::Passphrase, traits::UserWithPassphrase};
42///
43/// #[derive(Debug)]
44/// enum ExampleUserMapping {
45///     Admin {
46///         backend_id: u8,
47///     },
48///     Backup {
49///         backend_id: u8,
50///         system_user: SystemUserId,
51///     },
52///     Metrics {
53///         backend_id: u8,
54///         system_user: SystemUserId,
55///     },
56///     Signer {
57///         backend_id: u8,
58///         system_user: SystemUserId,
59///     },
60/// }
61///
62/// impl ExampleUserMapping {
63///     pub fn backend_user_id(&self) -> u8 {
64///         match self {
65///             Self::Admin { backend_id }
66///             | Self::Backup { backend_id, .. }
67///             | Self::Metrics { backend_id, .. }
68///             | Self::Signer { backend_id, .. } => *backend_id,
69///         }
70///     }
71/// }
72///
73/// impl MappingSystemUserId for ExampleUserMapping {
74///     fn system_user_id(&self) -> Option<&SystemUserId> {
75///         match self {
76///             Self::Admin { .. } => None,
77///             Self::Backup { system_user, .. }
78///             | Self::Metrics { system_user, .. }
79///             | Self::Signer { system_user, .. } => Some(system_user),
80///         }
81///     }
82/// }
83/// ```
84pub trait MappingSystemUserId {
85    /// Returns a reference to the [`SystemUserId`].
86    ///
87    /// # Note
88    ///
89    /// Should return [`None`], if the user mapping implementation does not track a system user.
90    fn system_user_id(&self) -> Option<&SystemUserId>;
91
92    /// Returns the tracked system user ID as [`User`] if it exists.
93    ///
94    /// This is a default implementation and should require no specific implementation.
95    ///
96    /// # Note
97    ///
98    /// Returns `Ok(None)`, if [`MappingSystemUserId::system_user_id`] returns [`None`] (the user
99    /// mapping implementation tracks no system user).
100    ///
101    /// # Errors
102    ///
103    /// Returns an error if no Unix user of the mapping's system user name exists.
104    fn system_user_id_as_existing_unix_user(&self) -> Result<Option<User>, crate::Error> {
105        let Some(system_user_id) = self.system_user_id() else {
106            return Ok(None);
107        };
108
109        // NOTE: We ignore the potential `None` return value of `User::from_name` because it would
110        // mean an invalid system user name (which cannot happen due to validation).
111        Ok(User::from_name(system_user_id.as_ref()).map_err(|source| {
112            crate::utils::Error::SystemUserLookup {
113                user: crate::utils::NameOrUid::Name(system_user_id.clone()),
114                source,
115            }
116        })?)
117    }
118
119    /// Returns the tracked system user ID as the current [`User`] if it exists.
120    ///
121    /// This is a default implementation and should require no specific implementation.
122    ///
123    /// # Note
124    ///
125    /// Returns `Ok(None)`, if [`MappingSystemUserId::system_user_id`] returns [`None`] (the user
126    /// mapping implementation tracks no system user).
127    ///
128    /// # Errors
129    ///
130    /// Returns an error if
131    ///
132    /// - retrieving the effective User ID of the current Unix user fails,
133    /// - the currently calling system user does not match the one returned by
134    ///   [`MappingSystemUserId::system_user_id`],
135    fn system_user_id_as_current_unix_user(&self) -> Result<Option<User>, crate::Error> {
136        let Some(system_user_id) = self.system_user_id() else {
137            return Ok(None);
138        };
139        let current_system_user = get_current_system_user()?;
140
141        if current_system_user.name != system_user_id.as_ref() {
142            return Err(crate::utils::Error::SystemUserMismatch {
143                target_user: system_user_id.to_string(),
144                current_user: current_system_user.name,
145            }
146            .into());
147        }
148
149        Ok(Some(current_system_user))
150    }
151}
152
153/// The kind of backend user.
154///
155/// This distinguishes between the different access rights levels (i.e. administrative and
156/// non-administrative) and roles (e.g. backup, metrics, signing) of a backend user.
157#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
158pub enum BackendUserIdKind {
159    /// Any user.
160    #[default]
161    Any,
162
163    /// Administrative user.
164    Admin,
165
166    /// Backup user.
167    Backup,
168
169    /// Metrics user.
170    Metrics,
171
172    /// Any non-administrative user.
173    NonAdmin,
174
175    /// User used to observe keys, without access to them.
176    Observer,
177
178    /// Signing user.
179    Signing,
180}
181
182/// A filter for user mapping variants.
183#[derive(Clone, Debug, Default, Eq, PartialEq)]
184pub struct BackendUserIdFilter {
185    /// The kind of backend user.
186    pub backend_user_id_kind: BackendUserIdKind,
187}
188
189/// An interface for returning a list of backend users based on a filter.
190///
191/// # Example
192///
193/// ```
194/// use signstar_config::{
195///     Error,
196///     config::{BackendUserIdFilter, BackendUserIdKind, MappingBackendUserIds, SystemUserId},
197/// };
198/// use signstar_crypto::{passphrase::Passphrase, traits::UserWithPassphrase};
199///
200/// #[derive(Debug)]
201/// struct ExampleCreds {
202///     pub id: u8,
203///     pub passphrase: Passphrase,
204/// }
205///
206/// impl UserWithPassphrase for ExampleCreds {
207///     fn user(&self) -> String {
208///         self.id.to_string()
209///     }
210///
211///     fn passphrase(&self) -> &Passphrase {
212///         &self.passphrase
213///     }
214/// }
215///
216/// #[derive(Debug)]
217/// enum ExampleUserMapping {
218///     Admin {
219///         backend_id: u8,
220///     },
221///     Backup {
222///         backend_id: u8,
223///         system_user: SystemUserId,
224///     },
225///     Metrics {
226///         backend_id: u8,
227///         system_user: SystemUserId,
228///     },
229///     Signer {
230///         backend_id: u8,
231///         system_user: SystemUserId,
232///     },
233/// }
234///
235/// impl ExampleUserMapping {
236///     pub fn backend_user_id(&self) -> u8 {
237///         match self {
238///             Self::Admin { backend_id }
239///             | Self::Backup { backend_id, .. }
240///             | Self::Metrics { backend_id, .. }
241///             | Self::Signer { backend_id, .. } => *backend_id,
242///         }
243///     }
244/// }
245///
246/// impl MappingBackendUserIds for ExampleUserMapping {
247///     fn backend_user_ids(&self, filter: BackendUserIdFilter) -> Vec<String> {
248///         match self {
249///             Self::Admin { backend_id, .. } => {
250///                 if [BackendUserIdKind::Admin, BackendUserIdKind::Any]
251///                     .contains(&filter.backend_user_id_kind)
252///                 {
253///                     return vec![backend_id.to_string()];
254///                 }
255///             }
256///             Self::Backup { backend_id, .. } => {
257///                 if [
258///                     BackendUserIdKind::Any,
259///                     BackendUserIdKind::Backup,
260///                     BackendUserIdKind::NonAdmin,
261///                 ]
262///                 .contains(&filter.backend_user_id_kind)
263///                 {
264///                     return vec![backend_id.to_string()];
265///                 }
266///             }
267///             Self::Metrics { backend_id, .. } => {
268///                 if [
269///                     BackendUserIdKind::Any,
270///                     BackendUserIdKind::Metrics,
271///                     BackendUserIdKind::NonAdmin,
272///                 ]
273///                 .contains(&filter.backend_user_id_kind)
274///                 {
275///                     return vec![backend_id.to_string()];
276///                 }
277///             }
278///             Self::Signer { backend_id, .. } => {
279///                 if [
280///                     BackendUserIdKind::Any,
281///                     BackendUserIdKind::Signing,
282///                     BackendUserIdKind::NonAdmin,
283///                 ]
284///                 .contains(&filter.backend_user_id_kind)
285///                 {
286///                     return vec![backend_id.to_string()];
287///                 }
288///             }
289///         }
290///
291///         Vec::new()
292///     }
293///
294///     fn backend_user_with_passphrase(
295///         &self,
296///         name: &str,
297///         passphrase: Passphrase,
298///     ) -> Result<Box<dyn UserWithPassphrase>, Error> {
299///         let backend_user_id = self.backend_user_id();
300///         if backend_user_id.to_string() != name {
301///             return Err(
302///                 signstar_config::config::TraitsError::BackendUserIdMismatch {
303///                     expected: name.to_string(),
304///                     actual: backend_user_id.to_string(),
305///                 }
306///                 .into(),
307///             );
308///         }
309///
310///         Ok(Box::new(ExampleCreds {
311///             id: backend_user_id,
312///             passphrase,
313///         }))
314///     }
315///
316///     fn backend_users_with_new_passphrase(
317///         &self,
318///         filter: BackendUserIdFilter,
319///     ) -> Vec<Box<dyn UserWithPassphrase>> {
320///         if let Some(backend_id) = match self {
321///             Self::Admin { backend_id, .. } => {
322///                 if [BackendUserIdKind::Admin, BackendUserIdKind::Any]
323///                     .contains(&filter.backend_user_id_kind)
324///                 {
325///                     Some(*backend_id)
326///                 } else {
327///                     None
328///                 }
329///             }
330///             Self::Backup { backend_id, .. } => {
331///                 if [
332///                     BackendUserIdKind::Any,
333///                     BackendUserIdKind::Backup,
334///                     BackendUserIdKind::NonAdmin,
335///                 ]
336///                 .contains(&filter.backend_user_id_kind)
337///                 {
338///                     Some(*backend_id)
339///                 } else {
340///                     None
341///                 }
342///             }
343///             Self::Metrics { backend_id, .. } => {
344///                 if [
345///                     BackendUserIdKind::Any,
346///                     BackendUserIdKind::Metrics,
347///                     BackendUserIdKind::NonAdmin,
348///                 ]
349///                 .contains(&filter.backend_user_id_kind)
350///                 {
351///                     Some(*backend_id)
352///                 } else {
353///                     None
354///                 }
355///             }
356///             Self::Signer { backend_id, .. } => {
357///                 if [
358///                     BackendUserIdKind::Any,
359///                     BackendUserIdKind::Signing,
360///                     BackendUserIdKind::NonAdmin,
361///                 ]
362///                 .contains(&filter.backend_user_id_kind)
363///                 {
364///                     Some(*backend_id)
365///                 } else {
366///                     None
367///                 }
368///             }
369///         } {
370///             vec![Box::new(ExampleCreds {
371///                 id: backend_id,
372///                 passphrase: Passphrase::generate(None),
373///             })]
374///         } else {
375///             Vec::new()
376///         }
377///     }
378/// }
379///
380/// # fn main() -> testresult::TestResult {
381/// let backend_id = 1;
382/// let mapping = ExampleUserMapping::Backup {
383///     backend_id,
384///     system_user: "backup".parse()?,
385/// };
386///
387/// // Find backend user IDs based on the kind of user.
388/// assert!(
389///     mapping
390///         .backend_user_ids(BackendUserIdFilter {
391///             backend_user_id_kind: BackendUserIdKind::Backup
392///         })
393///         .first()
394///         .is_some_and(|id| *id == backend_id.to_string())
395/// );
396/// // This returns an empty list if there are no matches.
397/// assert!(
398///     mapping
399///         .backend_user_ids(BackendUserIdFilter {
400///             backend_user_id_kind: BackendUserIdKind::Admin
401///         })
402///         .is_empty()
403/// );
404///
405/// // Create credentials based on a user and a passphrase.
406/// let passphrase = Passphrase::generate(None);
407/// let creds = mapping.backend_user_with_passphrase("1", passphrase.clone())?;
408/// assert_eq!(creds.user(), backend_id.to_string());
409/// assert_eq!(
410///     creds.passphrase().expose_borrowed(),
411///     passphrase.expose_borrowed()
412/// );
413/// // The user ID has to match when creating credentials!
414/// assert!(
415///     mapping
416///         .backend_user_with_passphrase("foo", passphrase.clone())
417///         .is_err()
418/// );
419///
420/// // Create new credentials with new passphrase based on backend user ID.
421/// assert!(
422///     mapping
423///         .backend_users_with_new_passphrase(BackendUserIdFilter {
424///             backend_user_id_kind: BackendUserIdKind::Backup
425///         })
426///         .first()
427///         .is_some_and(|creds| creds.user() == backend_id.to_string())
428/// );
429/// # Ok(())
430/// # }
431/// ```
432pub trait MappingBackendUserIds {
433    /// Returns a list of [`String`]s representing backend User IDs according to a `filter`.
434    fn backend_user_ids(&self, filter: BackendUserIdFilter) -> Vec<String>;
435
436    /// Returns a specific [`UserWithPassphrase`] implementation for a backend user.
437    ///
438    /// # Errors
439    ///
440    /// Returns an error if `user` matches no backend user of the user mapping.
441    /// Note, that implementations may use [`Error::BackendUserIdMismatch`] for this, if they do not
442    /// wish to create their own error variant for this purpose.
443    fn backend_user_with_passphrase(
444        &self,
445        name: &str,
446        passphrase: Passphrase,
447    ) -> Result<Box<dyn UserWithPassphrase>, crate::Error>;
448
449    /// Returns a list of [`UserWithPassphrase`] implementations according to a `filter`.
450    ///
451    /// For each returned backend user a new [`Passphrase`] is generated using the default settings
452    /// of [`Passphrase::generate`].
453    ///
454    /// With an implementation of [`BackendUserIdFilter`] it is possible to target specific kinds of
455    /// backend users.
456    fn backend_users_with_new_passphrase(
457        &self,
458        filter: BackendUserIdFilter,
459    ) -> Vec<Box<dyn UserWithPassphrase>>;
460}
461
462/// An interface for returning an optional SSH `authorized_keys` entry.
463///
464/// # Example
465///
466/// ```
467/// use signstar_config::config::{
468///     AuthorizedKeyEntry,
469///     MappingAuthorizedKeyEntry,
470///     MappingSystemUserId,
471///     SystemUserId,
472/// };
473/// use signstar_crypto::{passphrase::Passphrase, traits::UserWithPassphrase};
474///
475/// #[derive(Debug)]
476/// enum ExampleUserMapping {
477///     Admin {
478///         backend_id: u8,
479///     },
480///     Backup {
481///         backend_id: u8,
482///         ssh_authorized_key: AuthorizedKeyEntry,
483///         system_user: SystemUserId,
484///     },
485///     Metrics {
486///         backend_id: u8,
487///         ssh_authorized_key: AuthorizedKeyEntry,
488///         system_user: SystemUserId,
489///     },
490///     Signer {
491///         backend_id: u8,
492///         ssh_authorized_key: AuthorizedKeyEntry,
493///         system_user: SystemUserId,
494///     },
495/// }
496///
497/// impl MappingSystemUserId for ExampleUserMapping {
498///     fn system_user_id(&self) -> Option<&SystemUserId> {
499///         match self {
500///             Self::Admin { .. } => None,
501///             Self::Backup { system_user, .. }
502///             | Self::Metrics { system_user, .. }
503///             | Self::Signer { system_user, .. } => Some(system_user),
504///         }
505///     }
506/// }
507///
508/// impl MappingAuthorizedKeyEntry for ExampleUserMapping {
509///     fn authorized_key_entry(&self) -> Option<&AuthorizedKeyEntry> {
510///         match self {
511///             Self::Admin { .. } => None,
512///             Self::Backup {
513///                 ssh_authorized_key, ..
514///             }
515///             | Self::Metrics {
516///                 ssh_authorized_key, ..
517///             }
518///             | Self::Signer {
519///                 ssh_authorized_key, ..
520///             } => Some(ssh_authorized_key),
521///         }
522///     }
523/// }
524///
525/// # fn main() -> testresult::TestResult {
526/// let ssh_authorized_key: AuthorizedKeyEntry = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?;
527/// let mapping = ExampleUserMapping::Backup{
528///     backend_id: 1,
529///     ssh_authorized_key: ssh_authorized_key.clone(),
530///     system_user: "backup".parse()?,
531/// };
532/// assert!(mapping.authorized_key_entry().is_some_and(|key| key == &ssh_authorized_key));
533/// # Ok(())
534/// # }
535/// ```
536pub trait MappingAuthorizedKeyEntry: MappingSystemUserId {
537    /// Returns an optional SSH `authorized_keys` entry.
538    ///
539    /// Implementations must return [`None`] if the specific mapping does not provide any
540    /// [`AuthorizedKeyEntry`].
541    fn authorized_key_entry(&self) -> Option<&AuthorizedKeyEntry>;
542
543    /// Writes an optional SSH `authorized_keys` entry to the location configured by Signstar.
544    ///
545    /// # Note
546    ///
547    /// Returns `Ok(true)`, if the SSH authorized keys file has been written successfully.
548    /// Returns `Ok(false)`, if either the mapping implementation does not track an SSH authorized
549    /// key, or does not track a system user.
550    ///
551    /// # Errors
552    ///
553    /// Returns an error if
554    ///
555    /// - the system user of the mapping does not exist
556    /// - the currently calling user is not `root`
557    /// - the SSH authorized key cannot be written to file
558    fn write_authorized_key_entry(&self) -> Result<bool, crate::Error> {
559        let Some(authorized_key) = self.authorized_key_entry() else {
560            return Ok(false);
561        };
562        let path = if let Some(user) = self.system_user_id_as_existing_unix_user()? {
563            fail_if_not_root(&get_current_system_user()?)?;
564            get_ssh_authorized_key_base_dir()
565                .join(format!("signstar-user-{}.authorized_keys", user.name))
566        } else {
567            return Ok(false);
568        };
569
570        write(&path, authorized_key.to_string()).map_err(|source| crate::Error::IoPath {
571            path,
572            context: "writing SSH authorized key to file",
573            source,
574        })?;
575
576        Ok(true)
577    }
578}
579
580/// An interface to define a generic filter when evaluating the key IDs of a backend.
581pub trait BackendKeyIdFilter: Clone {}
582
583/// An interface for returning a list of key IDs based on a filter.
584///
585/// The associated filter is an implementation of [`BackendKeyIdFilter`] and should target the
586/// inherent details of a cryptographic key in the backend.
587/// A key ID must only be returned, if the filter fully matches.
588///
589/// # Example
590///
591/// ```
592/// use signstar_config::config::{BackendKeyIdFilter, MappingBackendKeyId, SystemUserId};
593/// use signstar_crypto::{passphrase::Passphrase, traits::UserWithPassphrase};
594///
595/// #[derive(Clone, Debug, PartialEq)]
596/// enum KeyFeature {
597///     V1,
598///     V2,
599/// }
600///
601/// #[derive(Debug)]
602/// struct KeySetup {
603///     pub feature: KeyFeature,
604///     pub id: u8,
605/// }
606///
607/// #[derive(Debug)]
608/// enum ExampleUserMapping {
609///     Admin {
610///         backend_id: u8,
611///     },
612///     Backup {
613///         backend_id: u8,
614///         system_user: SystemUserId,
615///     },
616///     Metrics {
617///         backend_id: u8,
618///         system_user: SystemUserId,
619///     },
620///     Signer {
621///         backend_id: u8,
622///         key_setup: KeySetup,
623///         system_user: SystemUserId,
624///     },
625/// }
626///
627/// #[derive(Clone, Debug)]
628/// struct KeyFilter {
629///     pub feature: KeyFeature,
630/// }
631///
632/// impl BackendKeyIdFilter for KeyFilter {}
633///
634/// impl MappingBackendKeyId<KeyFilter> for ExampleUserMapping {
635///     fn backend_key_id(&self, filter: &KeyFilter) -> Option<String> {
636///         match self {
637///             Self::Admin { .. } | Self::Backup { .. } | Self::Metrics { .. } => None,
638///             Self::Signer { key_setup, .. } => {
639///                 if key_setup.feature == filter.feature {
640///                     Some(key_setup.id.to_string())
641///                 } else {
642///                     None
643///                 }
644///             }
645///         }
646///     }
647/// }
648///
649/// # fn main() -> testresult::TestResult {
650/// let key_id = 1;
651/// let mapping = ExampleUserMapping::Signer {
652///     backend_id: 1,
653///     key_setup: KeySetup {
654///         feature: KeyFeature::V1,
655///         id: key_id,
656///     },
657///     system_user: "backup".parse()?,
658/// };
659///
660/// // A key ID is only returned, if the custom filter matches.
661/// assert!(
662///     mapping
663///         .backend_key_id(&KeyFilter {
664///             feature: KeyFeature::V1
665///         })
666///         .is_some_and(|id| id == key_id.to_string())
667/// );
668/// assert!(
669///     mapping
670///         .backend_key_id(&KeyFilter {
671///             feature: KeyFeature::V2
672///         })
673///         .is_none()
674/// );
675/// # Ok(())
676/// # }
677/// ```
678pub trait MappingBackendKeyId<T>
679where
680    T: BackendKeyIdFilter,
681{
682    /// Returns an optional [`String`] representing a backend key ID according to a `filter`.
683    fn backend_key_id(&self, filter: &T) -> Option<String>;
684}
685
686/// An interface to define a generic filter when evaluating the domains of a backend.
687pub trait BackendDomainFilter {}
688
689/// An interface for returning a backend domain based on an optional filter.
690///
691/// A backend domain usually describes a form of access restriction for a backend.
692/// With them restrictions to the access of cryptographic key material is enforced for backend
693/// users.
694///
695/// If a filter is provided, the domain ID must only be returned if the filter matches.
696///
697/// # Example
698///
699/// ```
700/// use signstar_config::config::{BackendDomainFilter, MappingBackendDomain, SystemUserId};
701/// use signstar_crypto::{passphrase::Passphrase, traits::UserWithPassphrase};
702///
703/// #[derive(Clone, Debug, PartialEq)]
704/// enum KeyFeature {
705///     V1,
706///     V2,
707/// }
708///
709/// #[derive(Debug)]
710/// struct KeySetup {
711///     pub feature: KeyFeature,
712///     pub id: u8,
713/// }
714///
715/// #[derive(Debug)]
716/// struct Domain {
717///     pub id: u8,
718///     pub special: bool,
719/// }
720///
721/// #[derive(Debug)]
722/// enum ExampleUserMapping {
723///     Admin {
724///         backend_id: u8,
725///     },
726///     Backup {
727///         backend_id: u8,
728///         system_user: SystemUserId,
729///     },
730///     Metrics {
731///         backend_id: u8,
732///         system_user: SystemUserId,
733///     },
734///     Signer {
735///         backend_id: u8,
736///         domain: Domain,
737///         key_setup: KeySetup,
738///         system_user: SystemUserId,
739///     },
740/// }
741///
742/// #[derive(Debug)]
743/// struct DomainFilter {
744///     pub special: bool,
745/// }
746///
747/// impl BackendDomainFilter for DomainFilter {}
748///
749/// impl MappingBackendDomain<DomainFilter> for ExampleUserMapping {
750///     fn backend_domain(&self, filter: Option<&DomainFilter>) -> Option<String> {
751///         match self {
752///             Self::Admin { .. } | Self::Backup { .. } | Self::Metrics { .. } => None,
753///             Self::Signer { domain, .. } => {
754///                 if let Some(filter) = filter {
755///                     if domain.special == filter.special {
756///                         Some(domain.id.to_string())
757///                     } else {
758///                         None
759///                     }
760///                 } else {
761///                     Some(domain.id.to_string())
762///                 }
763///             }
764///         }
765///     }
766/// }
767///
768/// # fn main() -> testresult::TestResult {
769/// let domain_id = 1;
770/// let mapping = ExampleUserMapping::Signer {
771///     backend_id: 1,
772///     domain: Domain {
773///         id: domain_id,
774///         special: true,
775///     },
776///     key_setup: KeySetup {
777///         feature: KeyFeature::V1,
778///         id: 1,
779///     },
780///     system_user: "backup".parse()?,
781/// };
782///
783/// // A domain ID is only returned, if the custom filter matches.
784/// assert!(
785///     mapping
786///         .backend_domain(Some(&DomainFilter { special: true }))
787///         .is_some_and(|id| id == domain_id.to_string())
788/// );
789/// // .. or if no filter is provided and a backend user with a domain is found.
790/// assert!(
791///     mapping
792///         .backend_domain(Some(&DomainFilter { special: true }))
793///         .is_some_and(|id| id == domain_id.to_string())
794/// );
795/// assert!(
796///     mapping
797///         .backend_domain(Some(&DomainFilter { special: false }))
798///         .is_none()
799/// );
800/// # Ok(())
801/// # }
802/// ```
803pub trait MappingBackendDomain<T>
804where
805    T: BackendDomainFilter,
806{
807    /// Returns a [`String`] representing a backend domain according to an optional `filter`.
808    fn backend_domain(&self, filter: Option<&T>) -> Option<String>;
809}
810
811/// The kind of non-administrative backend user.
812///
813/// This distinguishes between the different access rights levels (i.e. backup, metrics, observer,
814/// signing) of a non-administrative backend user.
815#[derive(Clone, Copy, Debug, Default)]
816pub enum NonAdminBackendUserIdKind {
817    /// Any non-administrative user.
818    #[default]
819    Any,
820
821    /// Backup user.
822    Backup,
823
824    /// Metrics user.
825    Metrics,
826
827    /// User used to observe keys, without access to them.
828    Observer,
829
830    /// Signing user.
831    Signing,
832}
833
834impl From<NonAdminBackendUserIdKind> for BackendUserIdKind {
835    fn from(value: NonAdminBackendUserIdKind) -> Self {
836        match value {
837            NonAdminBackendUserIdKind::Any => Self::NonAdmin,
838            NonAdminBackendUserIdKind::Backup => Self::Backup,
839            NonAdminBackendUserIdKind::Metrics => Self::Metrics,
840            NonAdminBackendUserIdKind::Observer => Self::Observer,
841            NonAdminBackendUserIdKind::Signing => Self::Signing,
842        }
843    }
844}
845
846/// A filter for non-administrative user mapping variants.
847#[derive(Clone, Debug, Default)]
848pub struct NonAdminBackendUserIdFilter {
849    /// The kind of backend user.
850    pub backend_user_id_kind: NonAdminBackendUserIdKind,
851}
852
853impl From<NonAdminBackendUserIdFilter> for BackendUserIdFilter {
854    fn from(value: NonAdminBackendUserIdFilter) -> Self {
855        Self {
856            backend_user_id_kind: value.backend_user_id_kind.into(),
857        }
858    }
859}
860
861/// An interface to create and load secrets for backend users in user mapping implementations.
862///
863/// Implementations are required to also implement [`MappingBackendUserIds`] and
864/// [`MappingSystemUserId`]
865///
866/// # Example
867///
868/// ```
869/// use signstar_config::{
870///     Error,
871///     config::{
872///         BackendUserIdFilter,
873///         BackendUserIdKind,
874///         MappingBackendUserIds,
875///         MappingBackendUserSecrets,
876///         MappingSystemUserId,
877///         SystemUserId,
878///     },
879/// };
880/// use signstar_crypto::{passphrase::Passphrase, traits::UserWithPassphrase};
881///
882/// #[derive(Debug)]
883/// struct ExampleCreds {
884///     pub id: u8,
885///     pub passphrase: Passphrase,
886/// }
887///
888/// impl UserWithPassphrase for ExampleCreds {
889///     fn user(&self) -> String {
890///         self.id.to_string()
891///     }
892///
893///     fn passphrase(&self) -> &Passphrase {
894///         &self.passphrase
895///     }
896/// }
897///
898/// #[derive(Debug)]
899/// enum ExampleUserMapping {
900///     Admin {
901///         backend_id: u8,
902///     },
903///     Backup {
904///         backend_id: u8,
905///         system_user: SystemUserId,
906///     },
907///     Metrics {
908///         backend_id: u8,
909///         system_user: SystemUserId,
910///     },
911///     Signer {
912///         backend_id: u8,
913///         system_user: SystemUserId,
914///     },
915/// }
916///
917/// impl ExampleUserMapping {
918///     pub fn backend_user_id(&self) -> u8 {
919///         match self {
920///             Self::Admin { backend_id }
921///             | Self::Backup { backend_id, .. }
922///             | Self::Metrics { backend_id, .. }
923///             | Self::Signer { backend_id, .. } => *backend_id,
924///         }
925///     }
926/// }
927///
928/// impl MappingBackendUserIds for ExampleUserMapping {
929///     fn backend_user_ids(&self, filter: BackendUserIdFilter) -> Vec<String> {
930///         match self {
931///             Self::Admin { backend_id, .. } => {
932///                 if [BackendUserIdKind::Admin, BackendUserIdKind::Any]
933///                     .contains(&filter.backend_user_id_kind)
934///                 {
935///                     return vec![backend_id.to_string()];
936///                 }
937///             }
938///             Self::Backup { backend_id, .. } => {
939///                 if [
940///                     BackendUserIdKind::Any,
941///                     BackendUserIdKind::Backup,
942///                     BackendUserIdKind::NonAdmin,
943///                 ]
944///                 .contains(&filter.backend_user_id_kind)
945///                 {
946///                     return vec![backend_id.to_string()];
947///                 }
948///             }
949///             Self::Metrics { backend_id, .. } => {
950///                 if [
951///                     BackendUserIdKind::Any,
952///                     BackendUserIdKind::Metrics,
953///                     BackendUserIdKind::NonAdmin,
954///                 ]
955///                 .contains(&filter.backend_user_id_kind)
956///                 {
957///                     return vec![backend_id.to_string()];
958///                 }
959///             }
960///             Self::Signer { backend_id, .. } => {
961///                 if [
962///                     BackendUserIdKind::Any,
963///                     BackendUserIdKind::Signing,
964///                     BackendUserIdKind::NonAdmin,
965///                 ]
966///                 .contains(&filter.backend_user_id_kind)
967///                 {
968///                     return vec![backend_id.to_string()];
969///                 }
970///             }
971///         }
972///
973///         Vec::new()
974///     }
975///
976///     fn backend_user_with_passphrase(
977///         &self,
978///         name: &str,
979///         passphrase: Passphrase,
980///     ) -> Result<Box<dyn UserWithPassphrase>, Error> {
981///         let backend_user_id = self.backend_user_id();
982///         if backend_user_id.to_string() != name {
983///             return Err(
984///                 signstar_config::config::TraitsError::BackendUserIdMismatch {
985///                     expected: name.to_string(),
986///                     actual: backend_user_id.to_string(),
987///                 }
988///                 .into(),
989///             );
990///         }
991///
992///         Ok(Box::new(ExampleCreds {
993///             id: backend_user_id,
994///             passphrase,
995///         }))
996///     }
997///
998///     fn backend_users_with_new_passphrase(
999///         &self,
1000///         filter: BackendUserIdFilter,
1001///     ) -> Vec<Box<dyn UserWithPassphrase>> {
1002///         if let Some(backend_id) = match self {
1003///             Self::Admin { backend_id, .. } => {
1004///                 if [BackendUserIdKind::Admin, BackendUserIdKind::Any]
1005///                     .contains(&filter.backend_user_id_kind)
1006///                 {
1007///                     Some(*backend_id)
1008///                 } else {
1009///                     None
1010///                 }
1011///             }
1012///             Self::Backup { backend_id, .. } => {
1013///                 if [
1014///                     BackendUserIdKind::Any,
1015///                     BackendUserIdKind::Backup,
1016///                     BackendUserIdKind::NonAdmin,
1017///                 ]
1018///                 .contains(&filter.backend_user_id_kind)
1019///                 {
1020///                     Some(*backend_id)
1021///                 } else {
1022///                     None
1023///                 }
1024///             }
1025///             Self::Metrics { backend_id, .. } => {
1026///                 if [
1027///                     BackendUserIdKind::Any,
1028///                     BackendUserIdKind::Metrics,
1029///                     BackendUserIdKind::NonAdmin,
1030///                 ]
1031///                 .contains(&filter.backend_user_id_kind)
1032///                 {
1033///                     Some(*backend_id)
1034///                 } else {
1035///                     None
1036///                 }
1037///             }
1038///             Self::Signer { backend_id, .. } => {
1039///                 if [
1040///                     BackendUserIdKind::Any,
1041///                     BackendUserIdKind::Signing,
1042///                     BackendUserIdKind::NonAdmin,
1043///                 ]
1044///                 .contains(&filter.backend_user_id_kind)
1045///                 {
1046///                     Some(*backend_id)
1047///                 } else {
1048///                     None
1049///                 }
1050///             }
1051///         } {
1052///             vec![Box::new(ExampleCreds {
1053///                 id: backend_id,
1054///                 passphrase: Passphrase::generate(None),
1055///             })]
1056///         } else {
1057///             Vec::new()
1058///         }
1059///     }
1060/// }
1061///
1062/// impl MappingSystemUserId for ExampleUserMapping {
1063///     fn system_user_id(&self) -> Option<&SystemUserId> {
1064///         match self {
1065///             Self::Admin { .. } => None,
1066///             Self::Backup { system_user, .. }
1067///             | Self::Metrics { system_user, .. }
1068///             | Self::Signer { system_user, .. } => Some(system_user),
1069///         }
1070///     }
1071/// }
1072///
1073/// impl MappingBackendUserSecrets for ExampleUserMapping {}
1074/// ```
1075pub trait MappingBackendUserSecrets: MappingSystemUserId + MappingBackendUserIds {
1076    /// Creates on-disk secrets for non-administrative backend users of the mapping.
1077    ///
1078    /// Returns a list of the created credentials as [`UserWithPassphrase`] implementations.
1079    ///
1080    /// # Note
1081    ///
1082    /// Returns `Ok(None)`, if the mapping implementation tracks no system user.
1083    ///
1084    /// # Errors
1085    ///
1086    /// Returns an error if
1087    ///
1088    /// - the system user in the user mapping does not match an existing Unix user
1089    /// - the user calling this function is not root
1090    /// - [`write_passphrase_to_secrets_file`] fails for one of the newly generated passphrases
1091    fn create_non_admin_backend_user_secrets(
1092        &self,
1093        secret_handling: NonAdministrativeSecretHandling,
1094    ) -> Result<Option<Vec<Box<dyn UserWithPassphrase>>>, crate::Error> {
1095        let Some(user) = self.system_user_id_as_existing_unix_user()? else {
1096            // The mapping implementation does not track a system user.
1097            return Ok(None);
1098        };
1099
1100        // Get credentials for all non-admin backend users (with newly generated passphrases).
1101        let credentials = self.backend_users_with_new_passphrase(BackendUserIdFilter {
1102            backend_user_id_kind: BackendUserIdKind::NonAdmin,
1103        });
1104
1105        // Write the passphrase for each set of credentials to disk.
1106        for creds in credentials.iter() {
1107            write_passphrase_to_secrets_file(
1108                secret_handling,
1109                &user,
1110                &creds.user(),
1111                creds.passphrase(),
1112            )?
1113        }
1114
1115        Ok(Some(credentials))
1116    }
1117
1118    /// Loads secrets from on-disk files for each non-administrative backend user matching a
1119    /// `filter`.
1120    ///
1121    /// Returns a list of the loaded credentials as [`UserWithPassphrase`] implementations.
1122    ///
1123    /// # Notes
1124    ///
1125    /// Returns `Ok(None)`, if the mapping implementation tracks no system user.
1126    ///
1127    /// The system user of the user mapping implementation must match the effective user of the
1128    /// current process.
1129    ///
1130    /// Delegates to [`load_passphrase_from_secrets_file`] for the loading of a single
1131    /// [`Passphrase`] from a secrets file.
1132    ///
1133    /// # Errors
1134    ///
1135    /// Returns an error if
1136    ///
1137    /// - the system user in the user mapping does not match the currently calling Unix user
1138    /// - [`load_passphrase_from_secrets_file`] fails for one of the secret files of the system user
1139    ///   of the mapping
1140    fn load_non_admin_backend_user_secrets(
1141        &self,
1142        secret_handling: NonAdministrativeSecretHandling,
1143        filter: NonAdminBackendUserIdFilter,
1144    ) -> Result<Option<Vec<Box<dyn UserWithPassphrase>>>, crate::Error> {
1145        let Some(system_user) = self.system_user_id_as_current_unix_user()? else {
1146            // The mapping implementation does not track a system user.
1147            return Ok(None);
1148        };
1149
1150        let mut credentials = Vec::new();
1151
1152        for backend_user in self.backend_user_ids(filter.into()) {
1153            credentials.push(self.backend_user_with_passphrase(
1154                &backend_user,
1155                load_passphrase_from_secrets_file(secret_handling, &system_user, &backend_user)?,
1156            )?);
1157        }
1158
1159        Ok(Some(credentials))
1160    }
1161}
1162
1163/// An interface for returning all [`SystemUserId`]s tracked by a configuration implementation.
1164///
1165/// # Example
1166///
1167/// ```
1168/// use std::collections::HashSet;
1169///
1170/// use signstar_config::config::{ConfigSystemUserIds, MappingSystemUserId, SystemUserId};
1171/// use signstar_crypto::{passphrase::Passphrase, traits::UserWithPassphrase};
1172///
1173/// #[derive(Debug, Eq, Hash, PartialEq)]
1174/// enum ExampleUserMapping {
1175///     Admin {
1176///         backend_id: u8,
1177///     },
1178///     Backup {
1179///         backend_id: u8,
1180///         system_user: SystemUserId,
1181///     },
1182///     Metrics {
1183///         backend_id: u8,
1184///         system_user: SystemUserId,
1185///     },
1186///     Signer {
1187///         backend_id: u8,
1188///         system_user: SystemUserId,
1189///     },
1190/// }
1191///
1192/// impl ExampleUserMapping {
1193///     pub fn backend_user_id(&self) -> u8 {
1194///         match self {
1195///             Self::Admin { backend_id }
1196///             | Self::Backup { backend_id, .. }
1197///             | Self::Metrics { backend_id, .. }
1198///             | Self::Signer { backend_id, .. } => *backend_id,
1199///         }
1200///     }
1201/// }
1202///
1203/// impl MappingSystemUserId for ExampleUserMapping {
1204///     fn system_user_id(&self) -> Option<&SystemUserId> {
1205///         match self {
1206///             Self::Admin { .. } => None,
1207///             Self::Backup { system_user, .. }
1208///             | Self::Metrics { system_user, .. }
1209///             | Self::Signer { system_user, .. } => Some(system_user),
1210///         }
1211///     }
1212/// }
1213///
1214/// #[derive(Debug)]
1215/// struct Config {
1216///     pub mappings: HashSet<ExampleUserMapping>,
1217/// }
1218///
1219/// impl ConfigSystemUserIds for Config {
1220///     fn system_user_ids(&self) -> HashSet<&SystemUserId> {
1221///         self.mappings
1222///             .iter()
1223///             .filter_map(|mapping| mapping.system_user_id())
1224///             .collect::<HashSet<_>>()
1225///     }
1226/// }
1227///
1228/// # fn main() -> testresult::TestResult {
1229/// let config = Config {
1230///     mappings: HashSet::from_iter([
1231///         ExampleUserMapping::Admin { backend_id: 1 },
1232///         ExampleUserMapping::Backup {
1233///             backend_id: 2,
1234///             system_user: "backup".parse()?,
1235///         },
1236///         ExampleUserMapping::Metrics {
1237///             backend_id: 3,
1238///             system_user: "metrics".parse()?,
1239///         },
1240///         ExampleUserMapping::Signer {
1241///             backend_id: 3,
1242///             system_user: "signer".parse()?,
1243///         },
1244///     ]),
1245/// };
1246/// let system_users: HashSet<SystemUserId> =
1247///     HashSet::from_iter(["backup".parse()?, "metrics".parse()?, "signer".parse()?]);
1248///
1249/// assert_eq!(
1250///     config.system_user_ids(),
1251///     system_users.iter().collect::<HashSet<_>>()
1252/// );
1253/// # Ok(())
1254/// # }
1255/// ```
1256pub trait ConfigSystemUserIds {
1257    /// Returns the list of all [`SystemUserId`]s.
1258    fn system_user_ids(&self) -> HashSet<&SystemUserId>;
1259}
1260
1261/// An interface for returning all [`AuthorizedKeyEntry`]s tracked by a configuration
1262/// implementation.
1263///
1264/// # Example
1265///
1266/// ```
1267/// use std::collections::HashSet;
1268///
1269/// use signstar_config::config::{
1270///     AuthorizedKeyEntry,
1271///     ConfigAuthorizedKeyEntries,
1272///     MappingAuthorizedKeyEntry,
1273///     MappingSystemUserId,
1274///     SystemUserId,
1275/// };
1276/// use signstar_crypto::{passphrase::Passphrase, traits::UserWithPassphrase};
1277///
1278/// #[derive(Debug, Eq, Hash, PartialEq)]
1279/// enum ExampleUserMapping {
1280///     Admin {
1281///         backend_id: u8,
1282///     },
1283///     Backup {
1284///         backend_id: u8,
1285///         ssh_authorized_key: AuthorizedKeyEntry,
1286///         system_user: SystemUserId,
1287///     },
1288///     Metrics {
1289///         backend_id: u8,
1290///         ssh_authorized_key: AuthorizedKeyEntry,
1291///         system_user: SystemUserId,
1292///     },
1293///     Signer {
1294///         backend_id: u8,
1295///         ssh_authorized_key: AuthorizedKeyEntry,
1296///         system_user: SystemUserId,
1297///     },
1298/// }
1299///
1300/// impl MappingSystemUserId for ExampleUserMapping {
1301///     fn system_user_id(&self) -> Option<&SystemUserId> {
1302///         match self {
1303///             Self::Admin { .. } => None,
1304///             Self::Backup { system_user, .. }
1305///             | Self::Metrics { system_user, .. }
1306///             | Self::Signer { system_user, .. } => Some(system_user),
1307///         }
1308///     }
1309/// }
1310///
1311/// impl MappingAuthorizedKeyEntry for ExampleUserMapping {
1312///     fn authorized_key_entry(&self) -> Option<&AuthorizedKeyEntry> {
1313///         match self {
1314///             Self::Admin { .. } => None,
1315///             Self::Backup {
1316///                 ssh_authorized_key, ..
1317///             }
1318///             | Self::Metrics {
1319///                 ssh_authorized_key, ..
1320///             }
1321///             | Self::Signer {
1322///                 ssh_authorized_key, ..
1323///             } => Some(ssh_authorized_key),
1324///         }
1325///     }
1326/// }
1327///
1328/// #[derive(Debug)]
1329/// struct Config {
1330///     pub mappings: HashSet<ExampleUserMapping>,
1331/// }
1332///
1333/// impl ConfigAuthorizedKeyEntries for Config {
1334///     fn authorized_key_entries(&self) -> HashSet<&AuthorizedKeyEntry> {
1335///         self.mappings
1336///             .iter()
1337///             .filter_map(|mapping| mapping.authorized_key_entry())
1338///             .collect::<HashSet<_>>()
1339///     }
1340/// }
1341///
1342/// # fn main() -> testresult::TestResult {
1343/// let config = Config {
1344///     mappings: HashSet::from_iter([
1345///     ExampleUserMapping::Admin {
1346///         backend_id: 1,
1347///     },
1348///     ExampleUserMapping::Backup {
1349///         backend_id: 2,
1350///         ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1351///         system_user: "backup".parse()?,
1352///     },
1353///     ExampleUserMapping::Signer {
1354///         backend_id: 3,
1355///         ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1356///         system_user: "signer".parse()?,
1357///     },
1358///     ])
1359/// };
1360/// let ssh_authorized_keys: HashSet<AuthorizedKeyEntry> = HashSet::from_iter([
1361/// "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1362/// "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?
1363/// ]);
1364///
1365/// assert_eq!(config.authorized_key_entries(), ssh_authorized_keys.iter().collect::<HashSet<_>>());
1366/// # Ok(())
1367/// # }
1368/// ```
1369pub trait ConfigAuthorizedKeyEntries {
1370    /// Returns the list of all [`AuthorizedKeyEntry`]s.
1371    fn authorized_key_entries(&self) -> HashSet<&AuthorizedKeyEntry>;
1372}
1373
1374/// An interface for returning all [`SystemUserData`] tracked by a configuration
1375/// implementation.
1376pub trait ConfigSystemUserData<'a> {
1377    /// Returns the list of all [`SystemUserData`].
1378    fn system_user_data(&'a self) -> HashSet<SystemUserData<'a>>;
1379}
1380
1381#[cfg(test)]
1382mod tests {
1383    use rstest::rstest;
1384    use testresult::TestResult;
1385
1386    use super::*;
1387
1388    #[derive(Debug)]
1389    struct ExampleCreds {
1390        pub id: u8,
1391        pub passphrase: Passphrase,
1392    }
1393
1394    impl UserWithPassphrase for ExampleCreds {
1395        fn user(&self) -> String {
1396            self.id.to_string()
1397        }
1398
1399        fn passphrase(&self) -> &Passphrase {
1400            &self.passphrase
1401        }
1402    }
1403
1404    #[derive(Debug)]
1405    enum ExampleUserMapping {
1406        Admin { backend_id: u8 },
1407        Backup { backend_id: u8 },
1408        Metrics { backend_id: u8 },
1409        Observer { backend_id: u8 },
1410        Signer { backend_id: u8 },
1411    }
1412
1413    impl ExampleUserMapping {
1414        pub fn backend_user_id(&self) -> u8 {
1415            match self {
1416                Self::Admin { backend_id }
1417                | Self::Backup { backend_id }
1418                | Self::Metrics { backend_id }
1419                | Self::Observer { backend_id }
1420                | Self::Signer { backend_id } => *backend_id,
1421            }
1422        }
1423    }
1424
1425    impl MappingBackendUserIds for ExampleUserMapping {
1426        fn backend_user_ids(&self, filter: BackendUserIdFilter) -> Vec<String> {
1427            match self {
1428                Self::Admin { backend_id } => {
1429                    if [BackendUserIdKind::Admin, BackendUserIdKind::Any]
1430                        .contains(&filter.backend_user_id_kind)
1431                    {
1432                        return vec![backend_id.to_string()];
1433                    }
1434                }
1435                Self::Backup { backend_id } => {
1436                    if [
1437                        BackendUserIdKind::Backup,
1438                        BackendUserIdKind::NonAdmin,
1439                        BackendUserIdKind::Any,
1440                    ]
1441                    .contains(&filter.backend_user_id_kind)
1442                    {
1443                        return vec![backend_id.to_string()];
1444                    }
1445                }
1446                Self::Metrics { backend_id } => {
1447                    if [
1448                        BackendUserIdKind::Metrics,
1449                        BackendUserIdKind::NonAdmin,
1450                        BackendUserIdKind::Any,
1451                    ]
1452                    .contains(&filter.backend_user_id_kind)
1453                    {
1454                        return vec![backend_id.to_string()];
1455                    }
1456                }
1457                Self::Observer { backend_id } => {
1458                    if [
1459                        BackendUserIdKind::Observer,
1460                        BackendUserIdKind::NonAdmin,
1461                        BackendUserIdKind::Any,
1462                    ]
1463                    .contains(&filter.backend_user_id_kind)
1464                    {
1465                        return vec![backend_id.to_string()];
1466                    }
1467                }
1468                Self::Signer { backend_id } => {
1469                    if [
1470                        BackendUserIdKind::Signing,
1471                        BackendUserIdKind::NonAdmin,
1472                        BackendUserIdKind::Any,
1473                    ]
1474                    .contains(&filter.backend_user_id_kind)
1475                    {
1476                        return vec![backend_id.to_string()];
1477                    }
1478                }
1479            }
1480
1481            Vec::new()
1482        }
1483
1484        fn backend_user_with_passphrase(
1485            &self,
1486            name: &str,
1487            passphrase: Passphrase,
1488        ) -> Result<Box<dyn UserWithPassphrase>, crate::Error> {
1489            let backend_user_id = self.backend_user_id();
1490            if backend_user_id.to_string() != name {
1491                return Err(Error::BackendUserIdMismatch {
1492                    expected: name.to_string(),
1493                    actual: backend_user_id.to_string(),
1494                }
1495                .into());
1496            }
1497
1498            Ok(Box::new(ExampleCreds {
1499                id: backend_user_id,
1500                passphrase,
1501            }))
1502        }
1503
1504        fn backend_users_with_new_passphrase(
1505            &self,
1506            filter: BackendUserIdFilter,
1507        ) -> Vec<Box<dyn UserWithPassphrase>> {
1508            if let Some(backend_id) = match self {
1509                Self::Admin { backend_id } => {
1510                    if [BackendUserIdKind::Admin, BackendUserIdKind::Any]
1511                        .contains(&filter.backend_user_id_kind)
1512                    {
1513                        Some(*backend_id)
1514                    } else {
1515                        None
1516                    }
1517                }
1518                Self::Backup { backend_id } => {
1519                    if [
1520                        BackendUserIdKind::Backup,
1521                        BackendUserIdKind::NonAdmin,
1522                        BackendUserIdKind::Any,
1523                    ]
1524                    .contains(&filter.backend_user_id_kind)
1525                    {
1526                        Some(*backend_id)
1527                    } else {
1528                        None
1529                    }
1530                }
1531                Self::Metrics { backend_id } => {
1532                    if [
1533                        BackendUserIdKind::Metrics,
1534                        BackendUserIdKind::NonAdmin,
1535                        BackendUserIdKind::Any,
1536                    ]
1537                    .contains(&filter.backend_user_id_kind)
1538                    {
1539                        Some(*backend_id)
1540                    } else {
1541                        None
1542                    }
1543                }
1544                Self::Observer { backend_id } => {
1545                    if [
1546                        BackendUserIdKind::Observer,
1547                        BackendUserIdKind::NonAdmin,
1548                        BackendUserIdKind::Any,
1549                    ]
1550                    .contains(&filter.backend_user_id_kind)
1551                    {
1552                        Some(*backend_id)
1553                    } else {
1554                        None
1555                    }
1556                }
1557                Self::Signer { backend_id } => {
1558                    if [
1559                        BackendUserIdKind::Signing,
1560                        BackendUserIdKind::NonAdmin,
1561                        BackendUserIdKind::Any,
1562                    ]
1563                    .contains(&filter.backend_user_id_kind)
1564                    {
1565                        Some(*backend_id)
1566                    } else {
1567                        None
1568                    }
1569                }
1570            } {
1571                vec![Box::new(ExampleCreds {
1572                    id: backend_id,
1573                    passphrase: Passphrase::generate(None),
1574                })]
1575            } else {
1576                Vec::new()
1577            }
1578        }
1579    }
1580
1581    // NonAdminBackendUserIdFilter
1582    #[rstest]
1583    #[case(NonAdminBackendUserIdKind::Any, BackendUserIdKind::NonAdmin)]
1584    #[case(NonAdminBackendUserIdKind::Backup, BackendUserIdKind::Backup)]
1585    #[case(NonAdminBackendUserIdKind::Observer, BackendUserIdKind::Observer)]
1586    #[case(NonAdminBackendUserIdKind::Metrics, BackendUserIdKind::Metrics)]
1587    #[case(NonAdminBackendUserIdKind::Signing, BackendUserIdKind::Signing)]
1588    fn non_admin_backend_user_id_kind_to_backend_user_id_kind(
1589        #[case] input: NonAdminBackendUserIdKind,
1590        #[case] output: BackendUserIdKind,
1591    ) {
1592        let transform: BackendUserIdKind = input.into();
1593
1594        assert_eq!(transform, output)
1595    }
1596
1597    #[rstest]
1598    #[case(NonAdminBackendUserIdFilter{ backend_user_id_kind: NonAdminBackendUserIdKind::Any }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin })]
1599    #[case(NonAdminBackendUserIdFilter{ backend_user_id_kind: NonAdminBackendUserIdKind::Backup }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup })]
1600    #[case(NonAdminBackendUserIdFilter{ backend_user_id_kind: NonAdminBackendUserIdKind::Observer }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer })]
1601    #[case(NonAdminBackendUserIdFilter{ backend_user_id_kind: NonAdminBackendUserIdKind::Metrics }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics })]
1602    #[case(NonAdminBackendUserIdFilter{ backend_user_id_kind: NonAdminBackendUserIdKind::Signing }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing })]
1603    fn backend_user_id_filter_from_non_admin_backend_user_id_filter(
1604        #[case] input: NonAdminBackendUserIdFilter,
1605        #[case] output: BackendUserIdFilter,
1606    ) {
1607        let transform: BackendUserIdFilter = input.into();
1608
1609        assert_eq!(transform, output)
1610    }
1611
1612    #[rstest]
1613    #[case(ExampleUserMapping::Admin{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any })]
1614    #[case(ExampleUserMapping::Backup{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any })]
1615    #[case(ExampleUserMapping::Observer{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any })]
1616    #[case(ExampleUserMapping::Metrics{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any })]
1617    #[case(ExampleUserMapping::Signer{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any })]
1618    #[case(ExampleUserMapping::Admin{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin })]
1619    #[case(ExampleUserMapping::Backup{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup })]
1620    #[case(ExampleUserMapping::Observer{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer })]
1621    #[case(ExampleUserMapping::Metrics{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics })]
1622    #[case(ExampleUserMapping::Signer{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing })]
1623    #[case(ExampleUserMapping::Backup{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin })]
1624    #[case(ExampleUserMapping::Observer{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin })]
1625    #[case(ExampleUserMapping::Metrics{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin })]
1626    #[case(ExampleUserMapping::Signer{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin })]
1627    fn backend_user_ids_matches(
1628        #[case] mapping: ExampleUserMapping,
1629        #[case] filter: BackendUserIdFilter,
1630    ) {
1631        assert_eq!(mapping.backend_user_ids(filter), ["1"])
1632    }
1633
1634    #[test]
1635    fn backend_user_with_passphrase_succeeds() -> TestResult {
1636        let mapping = ExampleUserMapping::Admin { backend_id: 1 };
1637        let passphrase = Passphrase::generate(None);
1638        let creds = mapping.backend_user_with_passphrase("1", passphrase.clone())?;
1639        assert_eq!(creds.user(), "1");
1640        assert_eq!(
1641            creds.passphrase().expose_borrowed(),
1642            passphrase.expose_borrowed()
1643        );
1644
1645        Ok(())
1646    }
1647
1648    #[test]
1649    fn backend_user_with_passphrase_fails() {
1650        let mapping = ExampleUserMapping::Admin { backend_id: 1 };
1651        assert!(
1652            mapping
1653                .backend_user_with_passphrase("2", Passphrase::generate(None))
1654                .is_err()
1655        );
1656    }
1657
1658    #[rstest]
1659    #[case(ExampleUserMapping::Admin{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any })]
1660    #[case(ExampleUserMapping::Backup{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any })]
1661    #[case(ExampleUserMapping::Observer{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any })]
1662    #[case(ExampleUserMapping::Metrics{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any })]
1663    #[case(ExampleUserMapping::Signer{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any })]
1664    #[case(ExampleUserMapping::Admin{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin })]
1665    #[case(ExampleUserMapping::Backup{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup })]
1666    #[case(ExampleUserMapping::Observer{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer })]
1667    #[case(ExampleUserMapping::Metrics{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics })]
1668    #[case(ExampleUserMapping::Signer{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing })]
1669    #[case(ExampleUserMapping::Backup{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin })]
1670    #[case(ExampleUserMapping::Observer{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin })]
1671    #[case(ExampleUserMapping::Metrics{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin })]
1672    #[case(ExampleUserMapping::Signer{ backend_id: 1 }, BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin })]
1673    fn backend_users_with_new_passphrase_applies(
1674        #[case] mapping: ExampleUserMapping,
1675        #[case] filter: BackendUserIdFilter,
1676    ) {
1677        let creds = mapping.backend_users_with_new_passphrase(filter);
1678        assert!(creds.first().is_some_and(|creds| creds.user() == "1"))
1679    }
1680}