1use std::collections::HashMap;
4
5use bitwarden_core::{Client, OrganizationId};
6
7use crate::{OrganizationUserPolicyContext, Policy, PolicyType, models::PolicyDecisionErased};
8#[cfg(test)]
11use crate::{PolicyDefinition, models::PolicyDecision, policy_definition::EnforceablePolicy};
12
13#[bitwarden_ffi::wasm_object]
17pub struct PolicyClient;
18
19impl Default for PolicyClient {
20 fn default() -> Self {
21 Self::new()
22 }
23}
24
25impl PolicyClient {
26 pub fn new() -> Self {
28 Self
29 }
30}
31
32#[bitwarden_ffi::wasm_export]
35impl PolicyClient {
36 #[cfg_attr(feature = "wasm", wasm_bindgen(js_name = "get_all_enforced"))]
42 pub fn get_all_enforced_erased(
43 &self,
44 policy_type: PolicyType,
45 policies: Vec<Policy>,
48 organization_user_policy_contexts: Vec<OrganizationUserPolicyContext>,
49 ) -> Vec<PolicyDecisionErased> {
50 policy_type
51 .resolve_policy()
52 .get_all_enforced_erased(&policies, &organization_user_policy_contexts)
53 }
54
55 #[cfg_attr(feature = "wasm", wasm_bindgen(js_name = "get_enforced"))]
61 pub fn get_enforced_erased(
62 &self,
63 policy_type: PolicyType,
64 organization_id: OrganizationId,
65 policies: Vec<Policy>,
68 organization_user_policy_contexts: Vec<OrganizationUserPolicyContext>,
69 ) -> PolicyDecisionErased {
70 policy_type.resolve_policy().get_enforced_erased(
71 organization_id,
72 &policies,
73 &organization_user_policy_contexts,
74 )
75 }
76
77 pub fn filter_by_type(
79 &self,
80 policies: Vec<Policy>,
81 organization_user_policy_contexts: Vec<OrganizationUserPolicyContext>,
82 policy_type: PolicyType,
83 ) -> Vec<Policy> {
84 let enforced: HashMap<OrganizationId, PolicyDecisionErased> = policy_type
87 .resolve_policy()
88 .get_all_enforced_erased(&policies, &organization_user_policy_contexts)
89 .into_iter()
90 .map(|e| (e.organization_id, e))
91 .collect();
92
93 policies
94 .into_iter()
95 .filter(|p| {
96 p.r#type == policy_type
97 && match enforced.get(&p.organization_id) {
98 Some(e) => e.enforced,
99 None => false,
100 }
101 })
102 .collect()
103 }
104}
105
106#[cfg(test)]
110impl PolicyClient {
111 fn get_all_enforced<P: PolicyDefinition>(
114 &self,
115 policy: P,
116 policies: &[Policy],
119 organization_user_policy_contexts: &[OrganizationUserPolicyContext],
120 ) -> Vec<PolicyDecision<P>> {
121 policy.get_all_enforced(policies, organization_user_policy_contexts)
122 }
123
124 fn get_enforced<P: PolicyDefinition>(
127 &self,
128 policy: P,
129 organization_id: OrganizationId,
130 policies: &[Policy],
133 organization_user_policy_contexts: &[OrganizationUserPolicyContext],
134 ) -> PolicyDecision<P> {
135 policy.get_enforced(organization_id, policies, organization_user_policy_contexts)
136 }
137}
138
139pub trait PoliciesClientExt {
141 fn policies(&self) -> PolicyClient;
143}
144
145impl PoliciesClientExt for Client {
146 fn policies(&self) -> PolicyClient {
147 PolicyClient::new()
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use bitwarden_organizations::{OrganizationUserStatusType, OrganizationUserType};
154
155 use super::*;
156 use crate::{
157 MasterPasswordPolicy, MasterPasswordPolicyData, PolicyId, policy_type::PolicyDataType,
158 };
159
160 fn policy(
161 organization_id: OrganizationId,
162 policy_type: PolicyType,
163 data: Option<&str>,
164 ) -> Policy {
165 Policy {
166 id: PolicyId::new_v4(),
167 organization_id,
168 r#type: policy_type,
169 data: data.map(str::to_owned),
170 enabled: true,
171 revision_date: None,
172 }
173 }
174
175 fn confirmed_member(organization_id: OrganizationId) -> OrganizationUserPolicyContext {
177 OrganizationUserPolicyContext {
178 id: organization_id,
179 status: OrganizationUserStatusType::Confirmed,
180 role: OrganizationUserType::User,
181 enabled: true,
182 use_policies: true,
183 is_provider_user: false,
184 }
185 }
186
187 mod get_enforced {
188 use super::*;
189
190 #[test]
191 fn returns_typed_decision() {
192 let org_id = OrganizationId::new_v4();
193 let views = [policy(
194 org_id,
195 PolicyType::MasterPassword,
196 Some(r#"{"minComplexity":3,"minLength":12}"#),
197 )];
198 let contexts = [confirmed_member(org_id)];
199
200 let result =
201 PolicyClient::new().get_enforced(MasterPasswordPolicy, org_id, &views, &contexts);
202
203 assert_eq!(result.organization_id, org_id);
204 assert!(result.enforced);
205 assert_eq!(
206 result.data,
207 MasterPasswordPolicyData {
208 min_complexity: Some(3),
209 min_length: Some(12),
210 ..Default::default()
211 }
212 );
213 }
214 }
215
216 mod get_all_enforced {
217 use super::*;
218
219 #[test]
220 fn returns_one_decision_per_view() {
221 let org_id = OrganizationId::new_v4();
222 let views = [policy(
223 org_id,
224 PolicyType::MasterPassword,
225 Some(r#"{"minComplexity":3}"#),
226 )];
227 let contexts = [confirmed_member(org_id)];
228
229 let results =
230 PolicyClient::new().get_all_enforced(MasterPasswordPolicy, &views, &contexts);
231
232 assert_eq!(results.len(), 1);
233 assert_eq!(results[0].organization_id, org_id);
234 assert!(results[0].enforced);
235 assert_eq!(results[0].data.min_complexity, Some(3));
236 }
237 }
238
239 mod get_enforced_erased {
240 use super::*;
241
242 #[test]
243 fn returns_erased_decision() {
244 let org_id = OrganizationId::new_v4();
245 let views = vec![policy(
246 org_id,
247 PolicyType::MasterPassword,
248 Some(r#"{"minComplexity":3}"#),
249 )];
250 let contexts = vec![confirmed_member(org_id)];
251
252 let result = PolicyClient::new().get_enforced_erased(
253 PolicyType::MasterPassword,
254 org_id,
255 views,
256 contexts,
257 );
258
259 assert_eq!(result.organization_id, org_id);
260 assert!(result.enforced);
261 assert_eq!(
262 result.data,
263 PolicyDataType::MasterPassword(MasterPasswordPolicyData {
264 min_complexity: Some(3),
265 ..Default::default()
266 })
267 );
268 }
269
270 #[test]
271 fn unit_variant_carries_no_data() {
272 let org_id = OrganizationId::new_v4();
273 let views = vec![policy(org_id, PolicyType::SingleOrg, None)];
274 let contexts = vec![confirmed_member(org_id)];
275
276 let result = PolicyClient::new().get_enforced_erased(
277 PolicyType::SingleOrg,
278 org_id,
279 views,
280 contexts,
281 );
282
283 assert!(result.enforced);
284 assert_eq!(result.data, PolicyDataType::SingleOrg);
285 }
286
287 #[test]
288 fn defaults_when_no_matching_view() {
289 let org_id = OrganizationId::new_v4();
290
291 let result = PolicyClient::new().get_enforced_erased(
294 PolicyType::MasterPassword,
295 org_id,
296 vec![],
297 vec![],
298 );
299
300 assert_eq!(result.organization_id, org_id);
301 assert!(!result.enforced);
302 assert_eq!(
303 result.data,
304 PolicyDataType::MasterPassword(MasterPasswordPolicyData::default())
305 );
306 }
307 }
308
309 mod get_all_enforced_erased {
310 use super::*;
311
312 #[test]
313 fn returns_one_decision_per_view() {
314 let org_id = OrganizationId::new_v4();
315 let views = vec![policy(org_id, PolicyType::MasterPassword, None)];
316 let contexts = vec![confirmed_member(org_id)];
317
318 let results = PolicyClient::new().get_all_enforced_erased(
319 PolicyType::MasterPassword,
320 views,
321 contexts,
322 );
323
324 assert_eq!(results.len(), 1);
325 assert_eq!(results[0].organization_id, org_id);
326 assert!(results[0].enforced);
327 assert_eq!(
328 results[0].data,
329 PolicyDataType::MasterPassword(MasterPasswordPolicyData::default())
330 );
331 }
332
333 #[test]
334 fn evaluates_each_org_independently() {
335 let org_a = OrganizationId::new_v4();
336 let org_b = OrganizationId::new_v4();
337 let views = vec![
338 policy(org_a, PolicyType::MaximumVaultTimeout, None),
339 policy(org_b, PolicyType::MaximumVaultTimeout, None),
340 ];
341 let contexts = vec![
344 confirmed_member(org_a),
345 OrganizationUserPolicyContext {
346 id: org_b,
347 status: OrganizationUserStatusType::Confirmed,
348 role: OrganizationUserType::Owner,
349 enabled: true,
350 use_policies: true,
351 is_provider_user: false,
352 },
353 ];
354
355 let results = PolicyClient::new().get_all_enforced_erased(
356 PolicyType::MaximumVaultTimeout,
357 views,
358 contexts,
359 );
360
361 assert_eq!(results.len(), 2);
362 let a = results
363 .iter()
364 .find(|r| r.organization_id == org_a)
365 .expect("a decision for org_a");
366 let b = results
367 .iter()
368 .find(|r| r.organization_id == org_b)
369 .expect("a decision for org_b");
370 assert!(a.enforced);
371 assert!(!b.enforced);
372 }
373 }
374
375 mod filter_by_type {
378 use super::*;
379
380 fn filter(
382 policies: Vec<Policy>,
383 orgs: Vec<OrganizationUserPolicyContext>,
384 policy_type: PolicyType,
385 ) -> Vec<Policy> {
386 PolicyClient::new().filter_by_type(policies, orgs, policy_type)
387 }
388
389 fn disabled_policy(organization_id: OrganizationId, policy_type: PolicyType) -> Policy {
391 Policy {
392 enabled: false,
393 ..policy(organization_id, policy_type, None)
394 }
395 }
396
397 #[test]
398 fn keeps_a_matching_enabled_policy_and_filters_to_the_requested_type() {
399 let org_id = OrganizationId::new_v4();
400 let policies = vec![
401 policy(org_id, PolicyType::MasterPassword, None),
402 policy(org_id, PolicyType::PasswordGenerator, None),
403 ];
404
405 let result = filter(
406 policies,
407 vec![confirmed_member(org_id)],
408 PolicyType::MasterPassword,
409 );
410
411 assert_eq!(result.len(), 1);
412 assert_eq!(result[0].r#type, PolicyType::MasterPassword);
413 }
414
415 #[test]
416 fn returns_empty_when_no_policy_of_the_requested_type_exists() {
417 let org_id = OrganizationId::new_v4();
418 let policies = vec![policy(org_id, PolicyType::MasterPassword, None)];
419
420 let result = filter(
421 policies,
422 vec![confirmed_member(org_id)],
423 PolicyType::TwoFactorAuthentication,
424 );
425
426 assert!(result.is_empty());
427 }
428
429 #[test]
430 fn drops_a_disabled_policy() {
431 let org_id = OrganizationId::new_v4();
432 let policies = vec![disabled_policy(org_id, PolicyType::MasterPassword)];
433
434 let result = filter(
435 policies,
436 vec![confirmed_member(org_id)],
437 PolicyType::MasterPassword,
438 );
439
440 assert!(result.is_empty());
441 }
442
443 #[test]
444 fn drops_the_policy_when_the_organization_is_disabled() {
445 let org_id = OrganizationId::new_v4();
446 let policies = vec![policy(org_id, PolicyType::MasterPassword, None)];
447 let orgs = vec![OrganizationUserPolicyContext {
448 enabled: false,
449 ..confirmed_member(org_id)
450 }];
451
452 let result = filter(policies, orgs, PolicyType::MasterPassword);
453
454 assert!(result.is_empty());
455 }
456
457 #[test]
458 fn drops_the_policy_when_the_organization_does_not_support_policies() {
459 let org_id = OrganizationId::new_v4();
460 let policies = vec![policy(org_id, PolicyType::MasterPassword, None)];
461 let orgs = vec![OrganizationUserPolicyContext {
462 use_policies: false,
463 ..confirmed_member(org_id)
464 }];
465
466 let result = filter(policies, orgs, PolicyType::MasterPassword);
467
468 assert!(result.is_empty());
469 }
470
471 #[test]
472 fn drops_the_policy_for_a_provider_user() {
473 let org_id = OrganizationId::new_v4();
474 let policies = vec![policy(org_id, PolicyType::MasterPassword, None)];
475 let orgs = vec![OrganizationUserPolicyContext {
476 is_provider_user: true,
477 ..confirmed_member(org_id)
478 }];
479
480 let result = filter(policies, orgs, PolicyType::MasterPassword);
481
482 assert!(result.is_empty());
483 }
484
485 #[test]
486 fn drops_the_policy_for_non_applicable_membership_statuses() {
487 let org_id = OrganizationId::new_v4();
488 for status in [
489 OrganizationUserStatusType::Invited,
490 OrganizationUserStatusType::Revoked,
491 OrganizationUserStatusType::Staged,
492 ] {
493 let label = format!("expected {status:?} to be dropped");
494 let policies = vec![policy(org_id, PolicyType::MasterPassword, None)];
495 let orgs = vec![OrganizationUserPolicyContext {
496 status,
497 ..confirmed_member(org_id)
498 }];
499
500 let result = filter(policies, orgs, PolicyType::MasterPassword);
501
502 assert!(result.is_empty(), "{label}");
503 }
504 }
505
506 #[test]
507 fn keeps_the_policy_for_applicable_membership_statuses() {
508 let org_id = OrganizationId::new_v4();
509 for status in [
510 OrganizationUserStatusType::Accepted,
511 OrganizationUserStatusType::Confirmed,
512 ] {
513 let label = format!("expected {status:?} to be kept");
514 let policies = vec![policy(org_id, PolicyType::MasterPassword, None)];
515 let orgs = vec![OrganizationUserPolicyContext {
516 status,
517 ..confirmed_member(org_id)
518 }];
519
520 let result = filter(policies, orgs, PolicyType::MasterPassword);
521
522 assert_eq!(result.len(), 1, "{label}");
523 }
524 }
525
526 #[test]
527 fn enforces_the_policy_by_default_when_the_org_is_absent_from_the_contexts() {
528 let org_a = OrganizationId::new_v4();
529 let org_b = OrganizationId::new_v4();
530 let policies = vec![policy(org_a, PolicyType::MasterPassword, None)];
531
532 let result = filter(
534 policies,
535 vec![confirmed_member(org_b)],
536 PolicyType::MasterPassword,
537 );
538
539 assert_eq!(result.len(), 1);
540 }
541
542 #[test]
543 fn enforces_the_policy_by_default_when_the_contexts_are_empty() {
544 let org_id = OrganizationId::new_v4();
545 let policies = vec![policy(org_id, PolicyType::MasterPassword, None)];
546
547 let result = filter(policies, vec![], PolicyType::MasterPassword);
548
549 assert_eq!(result.len(), 1);
550 }
551
552 #[test]
553 fn applies_master_password_to_an_owner() {
554 let org_id = OrganizationId::new_v4();
556 let policies = vec![policy(org_id, PolicyType::MasterPassword, None)];
557 let orgs = vec![OrganizationUserPolicyContext {
558 role: OrganizationUserType::Owner,
559 ..confirmed_member(org_id)
560 }];
561
562 let result = filter(policies, orgs, PolicyType::MasterPassword);
563
564 assert_eq!(result.len(), 1);
565 }
566
567 #[test]
568 fn applies_automatic_app_log_in_to_owners_and_admins() {
569 let org_id = OrganizationId::new_v4();
571 for role in [OrganizationUserType::Owner, OrganizationUserType::Admin] {
572 let label = format!("expected {role:?} to be subject");
573 let policies = vec![policy(org_id, PolicyType::AutomaticAppLogIn, None)];
574 let orgs = vec![OrganizationUserPolicyContext {
575 role,
576 ..confirmed_member(org_id)
577 }];
578
579 let result = filter(policies, orgs, PolicyType::AutomaticAppLogIn);
580
581 assert_eq!(result.len(), 1, "{label}");
582 }
583 }
584
585 #[test]
586 fn exempts_an_owner_from_maximum_vault_timeout() {
587 let org_id = OrganizationId::new_v4();
588 let policies = vec![policy(org_id, PolicyType::MaximumVaultTimeout, None)];
589 let orgs = vec![OrganizationUserPolicyContext {
590 role: OrganizationUserType::Owner,
591 ..confirmed_member(org_id)
592 }];
593
594 let result = filter(policies, orgs, PolicyType::MaximumVaultTimeout);
595
596 assert!(result.is_empty());
597 }
598
599 #[test]
600 fn applies_maximum_vault_timeout_to_admins_and_users() {
601 let org_id = OrganizationId::new_v4();
602 for role in [OrganizationUserType::Admin, OrganizationUserType::User] {
603 let label = format!("expected {role:?} to be subject");
604 let policies = vec![policy(org_id, PolicyType::MaximumVaultTimeout, None)];
605 let orgs = vec![OrganizationUserPolicyContext {
606 role,
607 ..confirmed_member(org_id)
608 }];
609
610 let result = filter(policies, orgs, PolicyType::MaximumVaultTimeout);
611
612 assert_eq!(result.len(), 1, "{label}");
613 }
614 }
615
616 #[test]
617 fn two_factor_authentication_exempts_owners_and_admins_via_default_impl() {
618 let org_id = OrganizationId::new_v4();
619 for role in [OrganizationUserType::Owner, OrganizationUserType::Admin] {
620 let label = format!("expected {role:?} to be exempt");
621 let policies = vec![policy(org_id, PolicyType::TwoFactorAuthentication, None)];
622 let orgs = vec![OrganizationUserPolicyContext {
623 role,
624 ..confirmed_member(org_id)
625 }];
626
627 let result = filter(policies, orgs, PolicyType::TwoFactorAuthentication);
628
629 assert!(result.is_empty(), "{label}");
630 }
631 }
632
633 #[test]
634 fn two_factor_authentication_applies_to_a_regular_user_via_default_impl() {
635 let org_id = OrganizationId::new_v4();
636 let policies = vec![policy(org_id, PolicyType::TwoFactorAuthentication, None)];
637
638 let result = filter(
639 policies,
640 vec![confirmed_member(org_id)],
641 PolicyType::TwoFactorAuthentication,
642 );
643
644 assert_eq!(result.len(), 1);
645 }
646
647 #[test]
648 fn filters_independently_across_multiple_organizations() {
649 let org_a = OrganizationId::new_v4();
652 let org_b = OrganizationId::new_v4();
653 let policies = vec![
654 policy(org_a, PolicyType::MaximumVaultTimeout, None),
655 policy(org_b, PolicyType::MaximumVaultTimeout, None),
656 ];
657 let orgs = vec![
658 confirmed_member(org_a),
659 OrganizationUserPolicyContext {
660 role: OrganizationUserType::Owner,
661 ..confirmed_member(org_b)
662 },
663 ];
664
665 let result = filter(policies, orgs, PolicyType::MaximumVaultTimeout);
666
667 assert_eq!(result.len(), 1);
668 assert_eq!(result[0].organization_id, org_a);
669 }
670 }
671}