Skip to main content

bitwarden_policies/
policy_client.rs

1//! [`PolicyClient`] and its associated extension trait.
2
3use bitwarden_core::Client;
4#[cfg(feature = "wasm")]
5use wasm_bindgen::prelude::wasm_bindgen;
6
7use crate::{
8    OrganizationUserPolicyContext, PolicyType, PolicyView, policy_overrides::*,
9    registry::PolicyRegistry,
10};
11
12fn build_policy_registry() -> PolicyRegistry {
13    PolicyRegistry::builder()
14        .register(MasterPasswordPolicy)
15        .register(PasswordGeneratorPolicy)
16        .register(MaximumVaultTimeoutPolicy)
17        .register(FreeFamiliesSponsorshipPolicy)
18        .register(RemoveUnlockWithPinPolicy)
19        .register(RestrictedItemTypesPolicy)
20        .register(AutomaticUserConfirmationPolicy)
21        .build()
22}
23
24/// Client for policy domain operations.
25///
26/// Obtained via [`PoliciesClientExt::policies`] on a [`Client`].
27#[cfg_attr(feature = "wasm", wasm_bindgen)]
28pub struct PolicyClient {
29    registry: PolicyRegistry,
30}
31
32impl Default for PolicyClient {
33    fn default() -> Self {
34        Self::new()
35    }
36}
37
38impl PolicyClient {
39    /// Creates a new [`PolicyClient`] with a freshly built registry.
40    pub fn new() -> Self {
41        Self {
42            registry: build_policy_registry(),
43        }
44    }
45}
46
47#[cfg_attr(feature = "wasm", wasm_bindgen)]
48impl PolicyClient {
49    /// Filter policies of the given type for the current user.
50    ///
51    /// Untyped FFI path: native/WASM callers pass a runtime `policy_type` integer.
52    /// Delegates to the registry, falling back to default rules for unknown types.
53    pub fn filter_by_type(
54        &self,
55        policies: Vec<PolicyView>,
56        organization_user_policy_contexts: Vec<OrganizationUserPolicyContext>,
57        policy_type: PolicyType,
58    ) -> Vec<PolicyView> {
59        self.registry
60            .filter_by_type(&policies, &organization_user_policy_contexts, policy_type)
61            .into_iter()
62            .cloned()
63            .collect()
64    }
65}
66
67/// Extension trait that adds a [`policies`](PoliciesClientExt::policies) method to [`Client`].
68pub trait PoliciesClientExt {
69    /// Creates a new [PolicyClient] instance.
70    fn policies(&self) -> PolicyClient;
71}
72
73impl PoliciesClientExt for Client {
74    fn policies(&self) -> PolicyClient {
75        PolicyClient::new()
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use bitwarden_organizations::{OrganizationUserStatusType, OrganizationUserType};
82    use uuid::Uuid;
83
84    use super::*;
85    use crate::filter::Policy;
86
87    fn policy_view(organization_id: Uuid, policy_type: PolicyType, enabled: bool) -> PolicyView {
88        PolicyView {
89            id: Uuid::new_v4(),
90            organization_id,
91            r#type: policy_type,
92            data: None,
93            enabled,
94            revision_date: Default::default(),
95        }
96    }
97
98    fn organization(id: Uuid) -> OrganizationUserPolicyContext {
99        OrganizationUserPolicyContext {
100            id,
101            role: OrganizationUserType::User,
102            status: OrganizationUserStatusType::Confirmed,
103            enabled: true,
104            use_policies: true,
105            is_provider_user: false,
106        }
107    }
108
109    #[test]
110    fn filter_by_type_delegates_to_registry() {
111        let org_id = Uuid::new_v4();
112        let policies = vec![
113            policy_view(org_id, PolicyType::MasterPassword, true),
114            policy_view(org_id, PolicyType::PasswordGenerator, true),
115        ];
116        let orgs = vec![organization(org_id)];
117
118        let client = PolicyClient::new();
119        let result = client.filter_by_type(policies, orgs, PolicyType::MasterPassword);
120
121        assert_eq!(result.len(), 1);
122        assert_eq!(result[0].r#type, PolicyType::MasterPassword);
123    }
124
125    #[test]
126    fn filter_by_type_returns_empty_for_no_match() {
127        let org_id = Uuid::new_v4();
128        let policies = vec![policy_view(org_id, PolicyType::MasterPassword, true)];
129        let orgs = vec![organization(org_id)];
130
131        let client = PolicyClient::new();
132        let result = client.filter_by_type(policies, orgs, PolicyType::TwoFactorAuthentication);
133
134        assert!(result.is_empty());
135    }
136
137    #[test]
138    fn filter_by_type_uses_registered_policy_definition() {
139        struct NoExemptionPolicy;
140        impl Policy for NoExemptionPolicy {
141            fn policy_type(&self) -> PolicyType {
142                PolicyType::MasterPassword
143            }
144            fn exempt_roles(&self) -> &[OrganizationUserType] {
145                &[]
146            }
147        }
148
149        let org_id = Uuid::new_v4();
150        // Owner — normally exempt, but NoExemptionPolicy removes the exemption
151        let policies = vec![policy_view(org_id, PolicyType::MasterPassword, true)];
152        let orgs = vec![OrganizationUserPolicyContext {
153            id: org_id,
154            role: OrganizationUserType::Owner,
155            status: OrganizationUserStatusType::Confirmed,
156            enabled: true,
157            use_policies: true,
158            is_provider_user: false,
159        }];
160
161        let registry = PolicyRegistry::builder()
162            .register(NoExemptionPolicy)
163            .build();
164        let client = PolicyClient { registry };
165        let result = client.filter_by_type(policies, orgs, PolicyType::MasterPassword);
166
167        assert_eq!(result.len(), 1);
168    }
169}