Skip to main content

bitwarden_policies/
registry.rs

1#![allow(dead_code)]
2
3//! Policy registry for managing [`Policy`] implementations.
4//!
5//! The [`PolicyRegistry`] maps policy types to their definitions
6//! and provides an interface for filtering policies according to their registered definition.
7//! Unregistered policy types fall back to [`DefaultPolicy`].
8
9use std::collections::HashMap;
10
11use bitwarden_organizations::ProfileOrganization;
12
13use crate::filter::{Policy, PolicyFilter, PolicyType, PolicyView};
14
15/// A [`Policy`] that uses the default filtering behavior for any policy type.
16struct DefaultPolicy(PolicyType);
17
18impl Policy for DefaultPolicy {
19    fn policy_type(&self) -> PolicyType {
20        self.0
21    }
22}
23
24/// A registry mapping each [`PolicyType`] to its [`Policy`] implementation.
25///
26/// This is for FFI callers where the [`Policy`] implementation is unknown.
27/// Rust callers should call [`filter`](PolicyFilter::filter)
28/// directly on their desired [`Policy`].
29///
30/// Use [`PolicyRegistry::builder`] to construct an instance.
31pub struct PolicyRegistry {
32    policies: HashMap<PolicyType, Box<dyn PolicyFilter>>,
33}
34
35impl PolicyRegistry {
36    /// Returns a [`PolicyRegistryBuilder`] for constructing a registry.
37    pub fn builder() -> PolicyRegistryBuilder {
38        PolicyRegistryBuilder {
39            policies: HashMap::new(),
40        }
41    }
42
43    /// Filters `policies` to those of `policy_type` that should be enforced.
44    ///
45    /// Uses the registered [`Policy`] for `policy_type` if one exists,
46    /// otherwise falls back to [`DefaultPolicy`].
47    pub(crate) fn filter_by_type<'a>(
48        &self,
49        policies: &'a [PolicyView],
50        organizations: &[ProfileOrganization],
51        policy_type: PolicyType,
52    ) -> Vec<&'a PolicyView> {
53        match self.policies.get(&policy_type) {
54            Some(p) => p.filter(policies, organizations),
55            None => DefaultPolicy(policy_type).filter(policies, organizations),
56        }
57    }
58}
59
60/// Builder for [`PolicyRegistry`].
61pub struct PolicyRegistryBuilder {
62    policies: HashMap<PolicyType, Box<dyn PolicyFilter>>,
63}
64
65impl PolicyRegistryBuilder {
66    /// Registers a [`Policy`] for its policy type.
67    ///
68    /// # Panics
69    ///
70    /// Panics if a [`Policy`] for the same [`PolicyType`] has already been registered.
71    pub fn register<P: Policy>(mut self, policy: P) -> Self {
72        let policy_type = policy.policy_type();
73        if self.policies.contains_key(&policy_type) {
74            panic!("policy already registered for type {:?}", policy_type);
75        }
76        self.policies.insert(policy_type, Box::new(policy));
77        self
78    }
79
80    /// Builds the [`PolicyRegistry`].
81    pub fn build(self) -> PolicyRegistry {
82        PolicyRegistry {
83            policies: self.policies,
84        }
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use bitwarden_organizations::{OrganizationUserStatusType, OrganizationUserType};
91    use uuid::Uuid;
92
93    use super::*;
94
95    fn policy_view(organization_id: Uuid, policy_type: u8, enabled: bool) -> PolicyView {
96        PolicyView {
97            id: Uuid::new_v4(),
98            organization_id,
99            r#type: PolicyType(policy_type),
100            data: None,
101            enabled,
102        }
103    }
104
105    fn organization(
106        id: Uuid,
107        user_type: OrganizationUserType,
108        status: OrganizationUserStatusType,
109        provider: bool,
110    ) -> ProfileOrganization {
111        ProfileOrganization {
112            id,
113            r#type: user_type,
114            status,
115            use_policies: true,
116            is_provider_user: provider,
117            ..Default::default()
118        }
119    }
120
121    #[test]
122    #[should_panic(expected = "policy already registered for type")]
123    fn registry_panics_on_duplicate_registration() {
124        struct AnyPolicy;
125        impl Policy for AnyPolicy {
126            fn policy_type(&self) -> PolicyType {
127                PolicyType(1)
128            }
129        }
130
131        PolicyRegistry::builder()
132            .register(AnyPolicy)
133            .register(AnyPolicy)
134            .build();
135    }
136
137    #[test]
138    fn registry_uses_registered_definition() {
139        let org_id = Uuid::new_v4();
140        let policies = [policy_view(org_id, 1, true)];
141        // Owner — exempt under default rules, not exempt under NoExemptionPolicy
142        let orgs = [organization(
143            org_id,
144            OrganizationUserType::Owner,
145            OrganizationUserStatusType::Confirmed,
146            false,
147        )];
148
149        struct NoExemptionPolicy;
150        impl Policy for NoExemptionPolicy {
151            fn policy_type(&self) -> PolicyType {
152                PolicyType(1)
153            }
154            fn exempt_roles(&self) -> &[OrganizationUserType] {
155                &[]
156            }
157        }
158
159        let registry = PolicyRegistry::builder()
160            .register(NoExemptionPolicy)
161            .build();
162
163        let result = registry.filter_by_type(&policies, &orgs, PolicyType(1));
164
165        assert_eq!(result.len(), 1);
166    }
167
168    #[test]
169    fn registry_uses_default_policy_definition() {
170        let org_id = Uuid::new_v4();
171        let policies = [policy_view(org_id, 1, true)];
172        let orgs = [organization(
173            org_id,
174            OrganizationUserType::User,
175            OrganizationUserStatusType::Confirmed,
176            false,
177        )];
178
179        // empty registry
180        let registry = PolicyRegistry::builder().build();
181
182        let result = registry.filter_by_type(&policies, &orgs, PolicyType(1));
183
184        assert_eq!(result.len(), 1);
185    }
186}