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