1use serde::{Deserialize, Serialize};
13
14use crate::crypto::{self, VaultKey};
15use crate::errors::{SafeError, SafeResult};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
18#[serde(rename_all = "snake_case")]
19pub enum RbacProfile {
20 ReadOnly,
21 #[default]
22 ReadWrite,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
26#[serde(rename_all = "snake_case")]
27pub enum RbacCapability {
28 Read,
29 Write,
30}
31
32impl RbacProfile {
33 pub fn as_str(self) -> &'static str {
34 match self {
35 Self::ReadOnly => "read_only",
36 Self::ReadWrite => "read_write",
37 }
38 }
39
40 pub fn capabilities(self) -> &'static [RbacCapability] {
41 match self {
42 Self::ReadOnly => &[RbacCapability::Read],
43 Self::ReadWrite => &[RbacCapability::Read, RbacCapability::Write],
44 }
45 }
46
47 pub fn allows_write(self) -> bool {
48 matches!(self, Self::ReadWrite)
49 }
50
51 pub fn derive_role_key(self, root_key: &VaultKey) -> SafeResult<VaultKey> {
57 crypto::derive_labeled_subkey(root_key, self.hkdf_label())
58 }
59
60 pub fn ensure_write_allowed(self) -> SafeResult<()> {
61 if self.allows_write() {
62 Ok(())
63 } else {
64 Err(SafeError::InvalidVault {
65 reason: "rbac access profile 'read_only' does not allow write operations".into(),
66 })
67 }
68 }
69
70 fn hkdf_label(self) -> &'static str {
71 match self {
72 Self::ReadOnly => "tsafe/rbac/read-only/v1",
73 Self::ReadWrite => "tsafe/rbac/read-write/v1",
74 }
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81 use crate::crypto::{
82 derive_key, random_salt, VAULT_KDF_M_COST, VAULT_KDF_P_COST, VAULT_KDF_T_COST,
83 };
84
85 fn test_root_key() -> VaultKey {
86 let salt = random_salt();
87 derive_key(
88 b"rbac-test-password",
89 &salt,
90 VAULT_KDF_M_COST,
91 VAULT_KDF_T_COST,
92 VAULT_KDF_P_COST,
93 )
94 .unwrap()
95 }
96
97 #[test]
98 fn read_only_profile_is_default_denied_for_write() {
99 assert!(!RbacProfile::ReadOnly.allows_write());
100 assert!(RbacProfile::ReadOnly.ensure_write_allowed().is_err());
101 assert!(RbacProfile::ReadWrite.ensure_write_allowed().is_ok());
102 }
103
104 #[test]
105 fn role_keys_are_domain_separated_and_deterministic() {
106 let root = test_root_key();
107
108 let ro_1 = RbacProfile::ReadOnly.derive_role_key(&root).unwrap();
109 let ro_2 = RbacProfile::ReadOnly.derive_role_key(&root).unwrap();
110 let rw = RbacProfile::ReadWrite.derive_role_key(&root).unwrap();
111
112 assert_eq!(ro_1.as_bytes(), ro_2.as_bytes());
113 assert_ne!(ro_1.as_bytes(), rw.as_bytes());
114 }
115
116 #[test]
117 fn profiles_expose_expected_capabilities() {
118 assert_eq!(
119 RbacProfile::ReadOnly.capabilities(),
120 &[RbacCapability::Read]
121 );
122 assert_eq!(
123 RbacProfile::ReadWrite.capabilities(),
124 &[RbacCapability::Read, RbacCapability::Write]
125 );
126 }
127}