Skip to main content

bitwarden_policies/
policy_client.rs

1//! [`PolicyClient`] and its associated extension trait.
2
3use bitwarden_core::Client;
4use bitwarden_organizations::ProfileOrganization;
5#[cfg(feature = "wasm")]
6use wasm_bindgen::prelude::wasm_bindgen;
7
8use crate::{
9    filter::{PolicyType, PolicyView},
10    policy_overrides::*,
11    registry::PolicyRegistry,
12};
13
14fn build_policy_registry() -> PolicyRegistry {
15    PolicyRegistry::builder()
16        .register(MasterPasswordPolicy)
17        .register(PasswordGeneratorPolicy)
18        .register(MaximumVaultTimeoutPolicy)
19        .register(FreeFamiliesSponsorshipPolicy)
20        .register(RemoveUnlockWithPinPolicy)
21        .register(RestrictedItemTypesPolicy)
22        .register(AutomaticUserConfirmationPolicy)
23        .build()
24}
25
26/// Client for policy domain operations.
27///
28/// Obtained via [`PoliciesClientExt::policies`] on a [`Client`].
29#[cfg_attr(feature = "wasm", wasm_bindgen)]
30pub struct PolicyClient {
31    registry: PolicyRegistry,
32}
33
34impl Default for PolicyClient {
35    fn default() -> Self {
36        Self::new()
37    }
38}
39
40impl PolicyClient {
41    /// Creates a new [`PolicyClient`] with a freshly built registry.
42    pub fn new() -> Self {
43        Self {
44            registry: build_policy_registry(),
45        }
46    }
47}
48
49#[cfg_attr(feature = "wasm", wasm_bindgen)]
50impl PolicyClient {
51    /// Filter policies of the given type for the current user.
52    ///
53    /// Untyped FFI path: native/WASM callers pass a runtime `policy_type` integer.
54    /// Delegates to the registry, falling back to default rules for unknown types.
55    pub fn filter_by_type(
56        &self,
57        policies: Vec<PolicyView>,
58        organizations: Vec<ProfileOrganization>,
59        policy_type: PolicyType,
60    ) -> Vec<PolicyView> {
61        self.registry
62            .filter_by_type(&policies, &organizations, policy_type)
63            .into_iter()
64            .cloned()
65            .collect()
66    }
67}
68
69/// Extension trait that adds a [`policies`](PoliciesClientExt::policies) method to [`Client`].
70pub trait PoliciesClientExt {
71    /// Creates a new [PolicyClient] instance.
72    fn policies(&self) -> PolicyClient;
73}
74
75impl PoliciesClientExt for Client {
76    fn policies(&self) -> PolicyClient {
77        PolicyClient::new()
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use bitwarden_organizations::{OrganizationUserStatusType, OrganizationUserType};
84    use uuid::Uuid;
85
86    use super::*;
87    use crate::filter::Policy;
88
89    fn policy_view(organization_id: Uuid, policy_type: PolicyType, enabled: bool) -> PolicyView {
90        PolicyView {
91            id: Uuid::new_v4(),
92            organization_id,
93            r#type: policy_type,
94            data: None,
95            enabled,
96        }
97    }
98
99    fn organization(id: Uuid) -> ProfileOrganization {
100        ProfileOrganization {
101            id,
102            r#type: OrganizationUserType::User,
103            status: OrganizationUserStatusType::Confirmed,
104            use_policies: true,
105            is_provider_user: false,
106            ..Default::default()
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(1), true),
115            policy_view(org_id, PolicyType(2), 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(1));
121
122        assert_eq!(result.len(), 1);
123        assert_eq!(result[0].r#type, PolicyType(1));
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(1), true)];
130        let orgs = vec![organization(org_id)];
131
132        let client = PolicyClient::new();
133        let result = client.filter_by_type(policies, orgs, PolicyType(99));
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(1)
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(1), true)];
153        let orgs = vec![ProfileOrganization {
154            id: org_id,
155            r#type: OrganizationUserType::Owner,
156            status: OrganizationUserStatusType::Confirmed,
157            use_policies: true,
158            is_provider_user: false,
159            ..Default::default()
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(1));
167
168        assert_eq!(result.len(), 1);
169    }
170}