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