Skip to main content

signstar_config/config/
traits.rs

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