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, MasterPasswordPolicyData, 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, MasterPasswordPolicyData::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, MasterPasswordPolicyData::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, MasterPasswordPolicyData::default());
475 }
476
477 fn typed_view(org: OrganizationId, policy_type: PolicyType, data: &str) -> PolicyView {
480 PolicyView {
481 id: Uuid::new_v4(),
482 organization_id: org,
483 r#type: policy_type,
484 data: Some(data.to_owned()),
485 enabled: true,
486 revision_date: None,
487 }
488 }
489
490 #[test]
491 fn maximum_vault_timeout_data_is_parsed() {
492 use crate::{MaximumVaultTimeoutPolicy, VaultTimeoutAction, VaultTimeoutType};
493
494 let org = OrganizationId::new_v4();
495 let views = [typed_view(
496 org,
497 PolicyType::MaximumVaultTimeout,
498 r#"{"type":"custom","minutes":480,"action":"logOut"}"#,
499 )];
500 let decision =
501 MaximumVaultTimeoutPolicy.get_enforced(org, &views, &[confirmed_member(org)]);
502 assert!(decision.enforced);
503 assert_eq!(decision.data.timeout_type, Some(VaultTimeoutType::Custom));
504 assert_eq!(decision.data.minutes, Some(480));
505 assert_eq!(decision.data.action, Some(VaultTimeoutAction::LogOut));
506 }
507
508 #[test]
509 fn password_generator_data_is_parsed() {
510 use crate::{PasswordGeneratorPolicy, PasswordGeneratorType};
511
512 let org = OrganizationId::new_v4();
513 let views = [typed_view(
514 org,
515 PolicyType::PasswordGenerator,
516 r#"{"overridePasswordType":"passphrase","minLength":14,"capitalize":true}"#,
517 )];
518 let decision =
519 PasswordGeneratorPolicy.get_enforced(org, &views, &[confirmed_member(org)]);
520 assert!(decision.enforced);
521 assert_eq!(
522 decision.data.override_password_type,
523 Some(PasswordGeneratorType::Passphrase)
524 );
525 assert_eq!(decision.data.min_length, Some(14));
526 assert_eq!(decision.data.capitalize, Some(true));
527 }
528 }
529
530 mod get_all_enforced {
531 use super::*;
532
533 #[test]
534 fn resolves_each_org_independently() {
535 let org_a = OrganizationId::new_v4();
536 let org_b = OrganizationId::new_v4();
537 let views = [
538 policy_view(org_a, PolicyType::SingleOrg, true),
539 policy_view(org_b, PolicyType::SingleOrg, true),
540 ];
541 let contexts = [
543 confirmed_member(org_a),
544 OrganizationUserPolicyContext {
545 role: OrganizationUserType::Owner,
546 ..confirmed_member(org_b)
547 },
548 ];
549
550 assert!(TestPolicy.get_enforced(org_a, &views, &contexts).enforced);
553 assert!(!TestPolicy.get_enforced(org_b, &views, &contexts).enforced);
554
555 let all = TestPolicy.get_all_enforced(&views, &contexts);
557 assert_eq!(all.len(), 2);
558 assert!(
559 all.iter()
560 .find(|d| d.organization_id == org_a)
561 .expect("a decision for org_a")
562 .enforced
563 );
564 assert!(
565 !all.iter()
566 .find(|d| d.organization_id == org_b)
567 .expect("a decision for org_b")
568 .enforced
569 );
570 }
571
572 #[test]
573 fn given_organization_without_policy_returns_unenforced_policy() {
574 let org_a = OrganizationId::new_v4();
575 let org_b = OrganizationId::new_v4();
576 let org_c = OrganizationId::new_v4();
577
578 let views = [
581 policy_view(org_a, PolicyType::SingleOrg, true),
582 policy_view(org_b, PolicyType::MasterPassword, true),
583 ];
584
585 let contexts = [
586 confirmed_member(org_a),
587 confirmed_member(org_b),
588 confirmed_member(org_c),
589 ];
590
591 let result = TestPolicy.get_all_enforced(&views, &contexts);
592 assert!(result.len() == 3);
593 assert!(
594 result
595 .iter()
596 .find(|p| p.organization_id == org_a)
597 .expect("a decision for org_a")
598 .enforced
599 );
600 assert!(
601 !result
602 .iter()
603 .find(|p| p.organization_id == org_b)
604 .expect("a decision for org_b")
605 .enforced
606 );
607 assert!(
608 !result
609 .iter()
610 .find(|p| p.organization_id == org_c)
611 .expect("a decision for org_c")
612 .enforced
613 );
614 }
615 }
616}