1#![allow(dead_code)]
2
3use std::collections::HashMap;
9
10use bitwarden_organizations::{
11 OrganizationUserStatusType, OrganizationUserType, ProfileOrganization,
12};
13use serde::{Deserialize, Serialize};
14#[cfg(feature = "wasm")]
15use tsify::Tsify;
16use uuid::Uuid;
17
18#[derive(PartialEq, Eq, Hash, Serialize, Deserialize, Debug, Copy, Clone)]
20#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
21pub struct PolicyType(
22 pub u8,
24);
25
26#[allow(missing_docs)]
28#[derive(Serialize, Deserialize, Debug, Clone)]
29#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
30#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
31pub struct PolicyView {
32 pub id: Uuid,
33 pub organization_id: Uuid,
34 pub r#type: PolicyType,
35 pub data: Option<String>,
37 pub enabled: bool,
38}
39
40pub trait Policy: Send + Sync + 'static {
44 fn policy_type(&self) -> PolicyType;
46
47 fn exempt_roles(&self) -> &[OrganizationUserType] {
52 &[OrganizationUserType::Owner, OrganizationUserType::Admin]
53 }
54
55 fn exempt_providers(&self) -> bool {
59 true
60 }
61
62 fn applicable_statuses(&self) -> &[OrganizationUserStatusType] {
67 &[
68 OrganizationUserStatusType::Accepted,
69 OrganizationUserStatusType::Confirmed,
70 ]
71 }
72}
73
74pub trait PolicyFilter: Policy {
78 fn filter<'a>(
85 &self,
86 policies: &'a [PolicyView],
87 organizations: &[ProfileOrganization],
88 ) -> Vec<&'a PolicyView> {
89 let org_map: HashMap<&Uuid, &ProfileOrganization> =
90 organizations.iter().map(|o| (&o.id, o)).collect();
91
92 policies
93 .iter()
94 .filter(|p| p.r#type == self.policy_type())
95 .filter(|p| p.enabled)
96 .filter(|p| {
97 match org_map.get(&p.organization_id) {
98 Some(org) => {
99 org.enabled
100 && org.use_policies
101 && self.applicable_statuses().contains(&org.status)
102 && !self.exempt_roles().contains(&org.r#type)
103 && !(org.is_provider_user && self.exempt_providers())
104 }
105 None => true, }
107 })
108 .collect()
109 }
110}
111
112impl<T: Policy> PolicyFilter for T {}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 fn policy_view(organization_id: Uuid, policy_type: u8, enabled: bool) -> PolicyView {
119 PolicyView {
120 id: Uuid::new_v4(),
121 organization_id,
122 r#type: PolicyType(policy_type),
123 data: None,
124 enabled,
125 }
126 }
127
128 fn organization(
129 id: Uuid,
130 user_type: OrganizationUserType,
131 status: OrganizationUserStatusType,
132 provider: bool,
133 ) -> ProfileOrganization {
134 ProfileOrganization {
135 id,
136 r#type: user_type,
137 status,
138 use_policies: true,
139 is_provider_user: provider,
140 ..Default::default()
141 }
142 }
143
144 struct TestPolicy;
145 impl Policy for TestPolicy {
146 fn policy_type(&self) -> PolicyType {
147 PolicyType(1)
148 }
149
150 fn exempt_roles(&self) -> &[OrganizationUserType] {
153 &[OrganizationUserType::Owner, OrganizationUserType::Admin]
154 }
155
156 fn exempt_providers(&self) -> bool {
157 true
158 }
159
160 fn applicable_statuses(&self) -> &[OrganizationUserStatusType] {
161 &[
162 OrganizationUserStatusType::Accepted,
163 OrganizationUserStatusType::Confirmed,
164 ]
165 }
166 }
167
168 #[test]
169 fn matching_policy_is_returned() {
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 let result = TestPolicy.filter(&policies, &orgs);
180 assert_eq!(result.len(), 1);
181 }
182
183 #[test]
184 fn disabled_organization_is_filtered_out() {
185 let org_id = Uuid::new_v4();
186 let orgs = [ProfileOrganization {
187 enabled: false,
188 id: org_id,
189 r#type: OrganizationUserType::User,
190 status: OrganizationUserStatusType::Confirmed,
191 use_policies: true,
192 is_provider_user: false,
193 ..Default::default()
194 }];
195 let policies = [policy_view(org_id, 1, true)];
196
197 let result = TestPolicy.filter(&policies, &orgs);
198 assert!(result.is_empty());
199 }
200
201 #[test]
202 fn disabled_policy_is_filtered_out() {
203 let org_id = Uuid::new_v4();
204 let policies = [policy_view(org_id, 1, false)];
205 let orgs = [organization(
206 org_id,
207 OrganizationUserType::User,
208 OrganizationUserStatusType::Confirmed,
209 false,
210 )];
211
212 let result = TestPolicy.filter(&policies, &orgs);
213 assert!(result.is_empty());
214 }
215
216 #[test]
217 fn wrong_policy_type_is_filtered_out() {
218 let org_id = Uuid::new_v4();
219 let policies = [policy_view(org_id, 2, true)];
220 let orgs = [organization(
221 org_id,
222 OrganizationUserType::User,
223 OrganizationUserStatusType::Confirmed,
224 false,
225 )];
226
227 let result = TestPolicy.filter(&policies, &orgs);
228 assert!(result.is_empty());
229 }
230
231 #[test]
232 fn use_policies_false_is_filtered_out() {
233 let org_id = Uuid::new_v4();
234 let orgs = [ProfileOrganization {
235 id: org_id,
236 r#type: OrganizationUserType::User,
237 status: OrganizationUserStatusType::Confirmed,
238 use_policies: false,
239 is_provider_user: false,
240 ..Default::default()
241 }];
242 let policies = [policy_view(org_id, 1, true)];
243
244 let result = TestPolicy.filter(&policies, &orgs);
245 assert!(result.is_empty());
246 }
247
248 #[test]
249 fn exempt_role_is_filtered_out() {
250 let org_id = Uuid::new_v4();
251 let policies = [policy_view(org_id, 1, true)];
252 let orgs = [organization(
253 org_id,
254 OrganizationUserType::Owner,
255 OrganizationUserStatusType::Confirmed,
256 false,
257 )];
258
259 let result = TestPolicy.filter(&policies, &orgs);
260 assert!(result.is_empty());
261 }
262
263 #[test]
264 fn non_applicable_status_is_filtered_out() {
265 let org_id = Uuid::new_v4();
266 let policies = [policy_view(org_id, 1, true)];
267 let orgs = [organization(
268 org_id,
269 OrganizationUserType::User,
270 OrganizationUserStatusType::Revoked,
271 false,
272 )];
273
274 let result = TestPolicy.filter(&policies, &orgs);
275 assert!(result.is_empty());
276 }
277
278 #[test]
279 fn provider_is_filtered_out() {
280 let org_id = Uuid::new_v4();
281 let policies = [policy_view(org_id, 1, true)];
282 let orgs = [organization(
283 org_id,
284 OrganizationUserType::User,
285 OrganizationUserStatusType::Confirmed,
286 true,
287 )];
288
289 let result = TestPolicy.filter(&policies, &orgs);
290 assert!(result.is_empty());
291 }
292
293 #[test]
294 fn missing_org_enforces_by_default() {
295 let policies = [policy_view(Uuid::new_v4(), 1, true)];
296
297 let result = TestPolicy.filter(&policies, &[]);
298 assert_eq!(result.len(), 1);
299 }
300}