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