Skip to main content

hyperion_vault_core/
rbac.rs

1pub const ACTIONS: [&str; 4] = ["create", "update", "delete", "rotate"];
2
3pub fn is_valid_action(action: &str) -> bool {
4    action == "*" || ACTIONS.contains(&action)
5}
6
7pub fn path_matches(pattern: &str, name: &str) -> bool {
8    match pattern.strip_suffix('*') {
9        Some(prefix) => name.starts_with(prefix),
10        None => pattern == name,
11    }
12}
13
14pub fn action_matches(rule_action: &str, action: &str) -> bool {
15    rule_action == "*" || rule_action == action
16}
17
18pub fn authorize(is_admin: bool, rules: &[(String, String)], action: &str, name: &str) -> bool {
19    is_admin
20        || rules
21            .iter()
22            .any(|(a, p)| action_matches(a, action) && path_matches(p, name))
23}
24
25pub fn visible(is_admin: bool, rules: &[(String, String)], name: &str) -> bool {
26    is_admin || rules.iter().any(|(_, p)| path_matches(p, name))
27}
28
29#[cfg(test)]
30mod tests {
31    use super::*;
32
33    fn rules(v: &[(&str, &str)]) -> Vec<(String, String)> {
34        v.iter()
35            .map(|(a, p)| (a.to_string(), p.to_string()))
36            .collect()
37    }
38
39    #[test]
40    fn glob_prefix_matches_under_path() {
41        assert!(path_matches("stripe/*", "stripe/secret-key"));
42        assert!(!path_matches("stripe/*", "db/root"));
43    }
44
45    #[test]
46    fn exact_pattern_requires_exact_name() {
47        assert!(path_matches("db/pw", "db/pw"));
48        assert!(!path_matches("db/pw", "db/pw2"));
49    }
50
51    #[test]
52    fn lone_star_matches_everything() {
53        assert!(path_matches("*", "anything/at/all"));
54    }
55
56    #[test]
57    fn action_wildcard_matches_any_action() {
58        assert!(action_matches("*", "create"));
59        assert!(action_matches("create", "create"));
60        assert!(!action_matches("create", "delete"));
61    }
62
63    #[test]
64    fn admin_bypasses_all_rules() {
65        assert!(authorize(true, &[], "delete", "anything"));
66    }
67
68    #[test]
69    fn payment_role_scoped_to_stripe() {
70        let r = rules(&[("create", "stripe/*"), ("rotate", "stripe/*")]);
71        assert!(authorize(false, &r, "create", "stripe/secret-key"));
72        assert!(authorize(false, &r, "rotate", "stripe/webhook"));
73        assert!(!authorize(false, &r, "delete", "stripe/secret-key"));
74        assert!(!authorize(false, &r, "create", "db/root"));
75    }
76
77    #[test]
78    fn visibility_follows_patterns() {
79        let r = rules(&[("create", "stripe/*")]);
80        assert!(visible(false, &r, "stripe/a"));
81        assert!(!visible(false, &r, "db/a"));
82        assert!(visible(true, &[], "db/a"));
83    }
84
85    #[test]
86    fn action_validation() {
87        assert!(is_valid_action("create"));
88        assert!(is_valid_action("*"));
89        assert!(!is_valid_action("read"));
90        assert!(!is_valid_action(""));
91    }
92}