1use std::collections::{HashMap, HashSet};
4
5use bitwarden_core::OrganizationId;
6use bitwarden_organizations::{OrganizationUserStatusType, OrganizationUserType};
7use serde::de::DeserializeOwned;
8
9use crate::{
10 OrganizationUserPolicyContext, PolicyView,
11 models::{EnforcedPolicy, EnforcedPolicyErased, ResolvedPolicyView},
12 policy_type::{PolicyDataType, PolicyType},
13};
14
15pub(crate) trait Policy: Send + Sync + 'static {
25 fn policy_type(&self) -> PolicyType;
27
28 fn to_erased(&self, data: Self::Data) -> PolicyDataType;
31
32 type Data: Default + DeserializeOwned;
36
37 fn exempt_roles(&self) -> &[OrganizationUserType] {
42 &[OrganizationUserType::Owner, OrganizationUserType::Admin]
43 }
44
45 fn exempt_providers(&self) -> bool {
49 true
50 }
51
52 fn enforced_statuses(&self) -> &[OrganizationUserStatusType] {
57 &[
58 OrganizationUserStatusType::Accepted,
59 OrganizationUserStatusType::Confirmed,
60 ]
61 }
62}
63
64pub(crate) trait EnforceablePolicy: Policy {
66 fn get_enforced(
73 &self,
74 organization_id: OrganizationId,
75 policy_views: &[PolicyView],
76 organization_user_policy_contexts: &[OrganizationUserPolicyContext],
77 ) -> EnforcedPolicy<Self>
78 where
79 Self: Sized;
80
81 fn get_all_enforced(
86 &self,
87 policy_views: &[PolicyView],
88 organization_user_policy_contexts: &[OrganizationUserPolicyContext],
89 ) -> Vec<EnforcedPolicy<Self>>
90 where
91 Self: Sized;
92}
93
94impl<P: Policy> EnforceablePolicy for P {
95 fn get_enforced(
96 &self,
97 organization_id: OrganizationId,
98 policy_views: &[PolicyView],
99 organization_user_policy_contexts: &[OrganizationUserPolicyContext],
100 ) -> EnforcedPolicy<P> {
101 let resolved = policy_views
102 .iter()
103 .filter(|v| v.organization_id == organization_id)
104 .find_map(|v| ResolvedPolicyView::resolve(self, v));
105
106 match resolved {
107 Some(resolved) => {
109 let contexts: HashMap<OrganizationId, &OrganizationUserPolicyContext> =
110 organization_user_policy_contexts
111 .iter()
112 .map(|ctx| (ctx.id, ctx))
113 .collect();
114
115 resolved.into_enforced(self, &contexts)
116 }
117 None => EnforcedPolicy::not_enforced(organization_id),
119 }
120 }
121
122 fn get_all_enforced(
123 &self,
124 policy_views: &[PolicyView],
125 organization_user_policy_contexts: &[OrganizationUserPolicyContext],
126 ) -> Vec<EnforcedPolicy<P>> {
127 let contexts: HashMap<OrganizationId, &OrganizationUserPolicyContext> =
129 organization_user_policy_contexts
130 .iter()
131 .map(|ctx| (ctx.id, ctx))
132 .collect();
133
134 let mut enforced_policies: Vec<EnforcedPolicy<P>> = policy_views
135 .iter()
136 .filter_map(|v| ResolvedPolicyView::resolve(self, v))
137 .map(|resolved| resolved.into_enforced(self, &contexts))
138 .collect();
139
140 let context_organization_ids: HashSet<OrganizationId> = organization_user_policy_contexts
144 .iter()
145 .map(|c| c.id)
146 .collect();
147 let policy_organization_ids: HashSet<OrganizationId> = enforced_policies
148 .iter()
149 .map(|p| p.organization_id)
150 .collect();
151 let organizations_without_policies = context_organization_ids
152 .difference(&policy_organization_ids)
153 .map(|id| EnforcedPolicy::not_enforced(*id));
154
155 enforced_policies.extend(organizations_without_policies);
156 enforced_policies
157 }
158}
159
160pub(crate) trait ErasedPolicy {
166 fn get_enforced_erased(
168 &self,
169 organization_id: OrganizationId,
170 policy_views: &[PolicyView],
171 organization_user_policy_contexts: &[OrganizationUserPolicyContext],
172 ) -> EnforcedPolicyErased;
173
174 fn get_all_enforced_erased(
176 &self,
177 policy_views: &[PolicyView],
178 organization_user_policy_contexts: &[OrganizationUserPolicyContext],
179 ) -> Vec<EnforcedPolicyErased>;
180}
181
182impl<P: Policy> ErasedPolicy for P {
183 fn get_enforced_erased(
184 &self,
185 organization_id: OrganizationId,
186 policy_views: &[PolicyView],
187 organization_user_policy_contexts: &[OrganizationUserPolicyContext],
188 ) -> EnforcedPolicyErased {
189 self.get_enforced(
190 organization_id,
191 policy_views,
192 organization_user_policy_contexts,
193 )
194 .into_erased(self)
195 }
196
197 fn get_all_enforced_erased(
198 &self,
199 policy_views: &[PolicyView],
200 organization_user_policy_contexts: &[OrganizationUserPolicyContext],
201 ) -> Vec<EnforcedPolicyErased> {
202 self.get_all_enforced(policy_views, organization_user_policy_contexts)
203 .into_iter()
204 .map(|decision| decision.into_erased(self))
205 .collect()
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use bitwarden_core::OrganizationId;
212 use uuid::Uuid;
213
214 use super::*;
215 use crate::{MasterPasswordPolicy, MasterPasswordPolicyResponse, policy_type::PolicyDataType};
216
217 struct TestPolicy;
221 impl Policy for TestPolicy {
222 type Data = ();
223
224 fn policy_type(&self) -> PolicyType {
225 PolicyType::SingleOrg
226 }
227
228 fn to_erased(&self, _data: Self::Data) -> PolicyDataType {
229 PolicyDataType::SingleOrg
230 }
231
232 fn exempt_roles(&self) -> &[OrganizationUserType] {
233 &[OrganizationUserType::Owner, OrganizationUserType::Admin]
234 }
235
236 fn exempt_providers(&self) -> bool {
237 true
238 }
239
240 fn enforced_statuses(&self) -> &[OrganizationUserStatusType] {
241 &[
242 OrganizationUserStatusType::Accepted,
243 OrganizationUserStatusType::Confirmed,
244 ]
245 }
246 }
247
248 fn policy_view(
249 organization_id: OrganizationId,
250 policy_type: PolicyType,
251 enabled: bool,
252 ) -> PolicyView {
253 PolicyView {
254 id: Uuid::new_v4(),
255 organization_id,
256 r#type: policy_type,
257 data: None,
258 enabled,
259 revision_date: None,
260 }
261 }
262
263 fn confirmed_member(id: OrganizationId) -> OrganizationUserPolicyContext {
267 OrganizationUserPolicyContext {
268 id,
269 role: OrganizationUserType::User,
270 status: OrganizationUserStatusType::Confirmed,
271 enabled: true,
272 use_policies: true,
273 is_provider_user: false,
274 }
275 }
276
277 mod get_enforced {
278 use super::*;
279
280 fn is_enforced(
284 org_id: OrganizationId,
285 views: &[PolicyView],
286 contexts: &[OrganizationUserPolicyContext],
287 ) -> bool {
288 TestPolicy.get_enforced(org_id, views, contexts).enforced
289 }
290
291 #[test]
292 fn enforced_for_confirmed_member() {
293 let org = OrganizationId::new_v4();
294 let views = [policy_view(org, PolicyType::SingleOrg, true)];
295 assert!(is_enforced(org, &views, &[confirmed_member(org)]));
296 }
297
298 #[test]
299 fn not_enforced_when_policy_disabled() {
300 let org = OrganizationId::new_v4();
301 let views = [policy_view(org, PolicyType::SingleOrg, false)];
302 assert!(!is_enforced(org, &views, &[confirmed_member(org)]));
303 }
304
305 #[test]
306 fn not_enforced_when_org_disabled() {
307 let org = OrganizationId::new_v4();
308 let views = [policy_view(org, PolicyType::SingleOrg, true)];
309 let ctx = OrganizationUserPolicyContext {
310 enabled: false,
311 ..confirmed_member(org)
312 };
313 assert!(!is_enforced(org, &views, &[ctx]));
314 }
315
316 #[test]
317 fn not_enforced_when_use_policies_false() {
318 let org = OrganizationId::new_v4();
319 let views = [policy_view(org, PolicyType::SingleOrg, true)];
320 let ctx = OrganizationUserPolicyContext {
321 use_policies: false,
322 ..confirmed_member(org)
323 };
324 assert!(!is_enforced(org, &views, &[ctx]));
325 }
326
327 #[test]
328 fn not_enforced_for_exempt_role() {
329 let org = OrganizationId::new_v4();
330 let views = [policy_view(org, PolicyType::SingleOrg, true)];
331 for (label, role) in [
332 ("Owner", OrganizationUserType::Owner),
333 ("Admin", OrganizationUserType::Admin),
334 ] {
335 let ctx = OrganizationUserPolicyContext {
336 role,
337 ..confirmed_member(org)
338 };
339 assert!(
340 !is_enforced(org, &views, &[ctx]),
341 "role {label} should be exempt"
342 );
343 }
344 }
345
346 #[test]
347 fn not_enforced_for_non_applicable_status() {
348 let org = OrganizationId::new_v4();
349 let views = [policy_view(org, PolicyType::SingleOrg, true)];
350 for (label, status) in [
351 ("Invited", OrganizationUserStatusType::Invited),
352 ("Revoked", OrganizationUserStatusType::Revoked),
353 ("Staged", OrganizationUserStatusType::Staged),
354 ] {
355 let ctx = OrganizationUserPolicyContext {
356 status,
357 ..confirmed_member(org)
358 };
359 assert!(
360 !is_enforced(org, &views, &[ctx]),
361 "status {label} should not be applicable"
362 );
363 }
364 }
365
366 #[test]
367 fn enforced_for_applicable_status() {
368 let org = OrganizationId::new_v4();
369 let views = [policy_view(org, PolicyType::SingleOrg, true)];
370 for (label, status) in [
371 ("Accepted", OrganizationUserStatusType::Accepted),
372 ("Confirmed", OrganizationUserStatusType::Confirmed),
373 ] {
374 let ctx = OrganizationUserPolicyContext {
375 status,
376 ..confirmed_member(org)
377 };
378 assert!(
379 is_enforced(org, &views, &[ctx]),
380 "status {label} should apply"
381 );
382 }
383 }
384
385 #[test]
386 fn not_enforced_for_provider_user() {
387 let org = OrganizationId::new_v4();
388 let views = [policy_view(org, PolicyType::SingleOrg, true)];
389 let ctx = OrganizationUserPolicyContext {
390 is_provider_user: true,
391 ..confirmed_member(org)
392 };
393 assert!(!is_enforced(org, &views, &[ctx]));
394 }
395
396 #[test]
397 fn wrong_policy_type_is_not_enforced() {
398 let org = OrganizationId::new_v4();
399 let views = [policy_view(org, PolicyType::PasswordGenerator, true)];
401 assert!(!is_enforced(org, &views, &[confirmed_member(org)]));
402 }
403
404 #[test]
405 fn missing_org_context_enforces_enabled_policy_by_default() {
406 let org = OrganizationId::new_v4();
407 let views = [policy_view(org, PolicyType::SingleOrg, true)];
408 assert!(is_enforced(org, &views, &[]));
409 }
410
411 #[test]
412 fn missing_org_context_does_not_enforce_disabled_policy() {
413 let org = OrganizationId::new_v4();
414 let views = [policy_view(org, PolicyType::SingleOrg, false)];
415 assert!(!is_enforced(org, &views, &[]));
416 }
417
418 fn mp_view(org: OrganizationId, data: Option<&str>) -> PolicyView {
422 PolicyView {
423 id: Uuid::new_v4(),
424 organization_id: org,
425 r#type: PolicyType::MasterPassword,
426 data: data.map(str::to_owned),
427 enabled: true,
428 revision_date: None,
429 }
430 }
431
432 #[test]
433 fn valid_data_is_parsed() {
434 let org = OrganizationId::new_v4();
435 let views = [mp_view(org, Some(r#"{"minComplexity":3,"minLength":12}"#))];
436 let decision = MasterPasswordPolicy.get_enforced(org, &views, &[confirmed_member(org)]);
437 assert!(decision.enforced);
438 assert_eq!(decision.data.min_complexity, Some(3));
439 assert_eq!(decision.data.min_length, Some(12));
440 }
441
442 #[test]
443 fn missing_data_falls_back_to_default() {
444 let org = OrganizationId::new_v4();
445 let views = [mp_view(org, None)];
446 let decision = MasterPasswordPolicy.get_enforced(org, &views, &[confirmed_member(org)]);
447 assert!(decision.enforced);
448 assert_eq!(decision.data, MasterPasswordPolicyResponse::default());
449 }
450
451 #[test]
452 fn malformed_data_falls_back_to_default_without_panicking() {
453 let org = OrganizationId::new_v4();
454 let views = [mp_view(org, Some("not json"))];
455 let decision = MasterPasswordPolicy.get_enforced(org, &views, &[confirmed_member(org)]);
458 assert!(decision.enforced);
459 assert_eq!(decision.data, MasterPasswordPolicyResponse::default());
460 }
461
462 #[test]
463 fn data_is_defaulted_when_not_enforced() {
464 let org = OrganizationId::new_v4();
465 let views = [mp_view(org, Some(r#"{"minComplexity":3}"#))];
466 let ctx = OrganizationUserPolicyContext {
469 status: OrganizationUserStatusType::Revoked,
470 ..confirmed_member(org)
471 };
472 let decision = MasterPasswordPolicy.get_enforced(org, &views, &[ctx]);
473 assert!(!decision.enforced);
474 assert_eq!(decision.data, MasterPasswordPolicyResponse::default());
475 }
476 }
477
478 mod get_all_enforced {
479 use super::*;
480
481 #[test]
482 fn resolves_each_org_independently() {
483 let org_a = OrganizationId::new_v4();
484 let org_b = OrganizationId::new_v4();
485 let views = [
486 policy_view(org_a, PolicyType::SingleOrg, true),
487 policy_view(org_b, PolicyType::SingleOrg, true),
488 ];
489 let contexts = [
491 confirmed_member(org_a),
492 OrganizationUserPolicyContext {
493 role: OrganizationUserType::Owner,
494 ..confirmed_member(org_b)
495 },
496 ];
497
498 assert!(TestPolicy.get_enforced(org_a, &views, &contexts).enforced);
501 assert!(!TestPolicy.get_enforced(org_b, &views, &contexts).enforced);
502
503 let all = TestPolicy.get_all_enforced(&views, &contexts);
505 assert_eq!(all.len(), 2);
506 assert!(
507 all.iter()
508 .find(|d| d.organization_id == org_a)
509 .expect("a decision for org_a")
510 .enforced
511 );
512 assert!(
513 !all.iter()
514 .find(|d| d.organization_id == org_b)
515 .expect("a decision for org_b")
516 .enforced
517 );
518 }
519
520 #[test]
521 fn given_organization_without_policy_returns_unenforced_policy() {
522 let org_a = OrganizationId::new_v4();
523 let org_b = OrganizationId::new_v4();
524 let org_c = OrganizationId::new_v4();
525
526 let views = [
529 policy_view(org_a, PolicyType::SingleOrg, true),
530 policy_view(org_b, PolicyType::MasterPassword, true),
531 ];
532
533 let contexts = [
534 confirmed_member(org_a),
535 confirmed_member(org_b),
536 confirmed_member(org_c),
537 ];
538
539 let result = TestPolicy.get_all_enforced(&views, &contexts);
540 assert!(result.len() == 3);
541 assert!(
542 result
543 .iter()
544 .find(|p| p.organization_id == org_a)
545 .expect("a decision for org_a")
546 .enforced
547 );
548 assert!(
549 !result
550 .iter()
551 .find(|p| p.organization_id == org_b)
552 .expect("a decision for org_b")
553 .enforced
554 );
555 assert!(
556 !result
557 .iter()
558 .find(|p| p.organization_id == org_c)
559 .expect("a decision for org_c")
560 .enforced
561 );
562 }
563 }
564}