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