1use std::collections::{HashMap, HashSet};
4
5use bitwarden_core::OrganizationId;
6use bitwarden_organizations::{OrganizationUserStatusType, OrganizationUserType};
7use serde::de::DeserializeOwned;
8
9use crate::{
10 OrganizationUserPolicyContext, Policy,
11 models::{PolicyDecision, PolicyDecisionErased, PolicyView},
12 policy_type::{PolicyDataType, PolicyType},
13};
14
15pub(crate) trait PolicyDefinition: 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: PolicyDefinition {
66 fn get_enforced(
73 &self,
74 organization_id: OrganizationId,
75 policies: &[Policy],
76 organization_user_policy_contexts: &[OrganizationUserPolicyContext],
77 ) -> PolicyDecision<Self>
78 where
79 Self: Sized;
80
81 fn get_all_enforced(
86 &self,
87 policies: &[Policy],
88 organization_user_policy_contexts: &[OrganizationUserPolicyContext],
89 ) -> Vec<PolicyDecision<Self>>
90 where
91 Self: Sized;
92}
93
94impl<P: PolicyDefinition> EnforceablePolicy for P {
95 fn get_enforced(
96 &self,
97 organization_id: OrganizationId,
98 policies: &[Policy],
99 organization_user_policy_contexts: &[OrganizationUserPolicyContext],
100 ) -> PolicyDecision<P> {
101 let resolved = policies
102 .iter()
103 .filter(|v| v.organization_id == organization_id)
104 .find_map(|v| PolicyView::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 => PolicyDecision::not_enforced(organization_id),
119 }
120 }
121
122 fn get_all_enforced(
123 &self,
124 policies: &[Policy],
125 organization_user_policy_contexts: &[OrganizationUserPolicyContext],
126 ) -> Vec<PolicyDecision<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<PolicyDecision<P>> = policies
135 .iter()
136 .filter_map(|v| PolicyView::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| PolicyDecision::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 policies: &[Policy],
171 organization_user_policy_contexts: &[OrganizationUserPolicyContext],
172 ) -> PolicyDecisionErased;
173
174 fn get_all_enforced_erased(
176 &self,
177 policies: &[Policy],
178 organization_user_policy_contexts: &[OrganizationUserPolicyContext],
179 ) -> Vec<PolicyDecisionErased>;
180}
181
182impl<P: PolicyDefinition> ErasedPolicy for P {
183 fn get_enforced_erased(
184 &self,
185 organization_id: OrganizationId,
186 policies: &[Policy],
187 organization_user_policy_contexts: &[OrganizationUserPolicyContext],
188 ) -> PolicyDecisionErased {
189 self.get_enforced(organization_id, policies, organization_user_policy_contexts)
190 .into_erased(self)
191 }
192
193 fn get_all_enforced_erased(
194 &self,
195 policies: &[Policy],
196 organization_user_policy_contexts: &[OrganizationUserPolicyContext],
197 ) -> Vec<PolicyDecisionErased> {
198 self.get_all_enforced(policies, organization_user_policy_contexts)
199 .into_iter()
200 .map(|decision| decision.into_erased(self))
201 .collect()
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use bitwarden_core::OrganizationId;
208
209 use super::*;
210 use crate::{
211 MasterPasswordPolicy, MasterPasswordPolicyData, PolicyId, policy_type::PolicyDataType,
212 };
213
214 struct TestPolicy;
218 impl PolicyDefinition for TestPolicy {
219 type Data = ();
220
221 fn policy_type(&self) -> PolicyType {
222 PolicyType::SingleOrg
223 }
224
225 fn to_erased(&self, _data: Self::Data) -> PolicyDataType {
226 PolicyDataType::SingleOrg
227 }
228
229 fn exempt_roles(&self) -> &[OrganizationUserType] {
230 &[OrganizationUserType::Owner, OrganizationUserType::Admin]
231 }
232
233 fn exempt_providers(&self) -> bool {
234 true
235 }
236
237 fn enforced_statuses(&self) -> &[OrganizationUserStatusType] {
238 &[
239 OrganizationUserStatusType::Accepted,
240 OrganizationUserStatusType::Confirmed,
241 ]
242 }
243 }
244
245 fn policy(organization_id: OrganizationId, policy_type: PolicyType, enabled: bool) -> Policy {
246 Policy {
247 id: PolicyId::new_v4(),
248 organization_id,
249 r#type: policy_type,
250 data: None,
251 enabled,
252 revision_date: None,
253 }
254 }
255
256 fn confirmed_member(id: OrganizationId) -> OrganizationUserPolicyContext {
260 OrganizationUserPolicyContext {
261 id,
262 role: OrganizationUserType::User,
263 status: OrganizationUserStatusType::Confirmed,
264 enabled: true,
265 use_policies: true,
266 is_provider_user: false,
267 }
268 }
269
270 mod get_enforced {
271 use super::*;
272
273 fn is_enforced(
277 org_id: OrganizationId,
278 views: &[Policy],
279 contexts: &[OrganizationUserPolicyContext],
280 ) -> bool {
281 TestPolicy.get_enforced(org_id, views, contexts).enforced
282 }
283
284 #[test]
285 fn enforced_for_confirmed_member() {
286 let org = OrganizationId::new_v4();
287 let views = [policy(org, PolicyType::SingleOrg, true)];
288 assert!(is_enforced(org, &views, &[confirmed_member(org)]));
289 }
290
291 #[test]
292 fn not_enforced_when_policy_disabled() {
293 let org = OrganizationId::new_v4();
294 let views = [policy(org, PolicyType::SingleOrg, false)];
295 assert!(!is_enforced(org, &views, &[confirmed_member(org)]));
296 }
297
298 #[test]
299 fn not_enforced_when_org_disabled() {
300 let org = OrganizationId::new_v4();
301 let views = [policy(org, PolicyType::SingleOrg, true)];
302 let ctx = OrganizationUserPolicyContext {
303 enabled: false,
304 ..confirmed_member(org)
305 };
306 assert!(!is_enforced(org, &views, &[ctx]));
307 }
308
309 #[test]
310 fn not_enforced_when_use_policies_false() {
311 let org = OrganizationId::new_v4();
312 let views = [policy(org, PolicyType::SingleOrg, true)];
313 let ctx = OrganizationUserPolicyContext {
314 use_policies: false,
315 ..confirmed_member(org)
316 };
317 assert!(!is_enforced(org, &views, &[ctx]));
318 }
319
320 #[test]
321 fn not_enforced_for_exempt_role() {
322 let org = OrganizationId::new_v4();
323 let views = [policy(org, PolicyType::SingleOrg, true)];
324 for (label, role) in [
325 ("Owner", OrganizationUserType::Owner),
326 ("Admin", OrganizationUserType::Admin),
327 ] {
328 let ctx = OrganizationUserPolicyContext {
329 role,
330 ..confirmed_member(org)
331 };
332 assert!(
333 !is_enforced(org, &views, &[ctx]),
334 "role {label} should be exempt"
335 );
336 }
337 }
338
339 #[test]
340 fn not_enforced_for_non_applicable_status() {
341 let org = OrganizationId::new_v4();
342 let views = [policy(org, PolicyType::SingleOrg, true)];
343 for (label, status) in [
344 ("Invited", OrganizationUserStatusType::Invited),
345 ("Revoked", OrganizationUserStatusType::Revoked),
346 ("Staged", OrganizationUserStatusType::Staged),
347 ] {
348 let ctx = OrganizationUserPolicyContext {
349 status,
350 ..confirmed_member(org)
351 };
352 assert!(
353 !is_enforced(org, &views, &[ctx]),
354 "status {label} should not be applicable"
355 );
356 }
357 }
358
359 #[test]
360 fn enforced_for_applicable_status() {
361 let org = OrganizationId::new_v4();
362 let views = [policy(org, PolicyType::SingleOrg, true)];
363 for (label, status) in [
364 ("Accepted", OrganizationUserStatusType::Accepted),
365 ("Confirmed", OrganizationUserStatusType::Confirmed),
366 ] {
367 let ctx = OrganizationUserPolicyContext {
368 status,
369 ..confirmed_member(org)
370 };
371 assert!(
372 is_enforced(org, &views, &[ctx]),
373 "status {label} should apply"
374 );
375 }
376 }
377
378 #[test]
379 fn not_enforced_for_provider_user() {
380 let org = OrganizationId::new_v4();
381 let views = [policy(org, PolicyType::SingleOrg, true)];
382 let ctx = OrganizationUserPolicyContext {
383 is_provider_user: true,
384 ..confirmed_member(org)
385 };
386 assert!(!is_enforced(org, &views, &[ctx]));
387 }
388
389 #[test]
390 fn wrong_policy_type_is_not_enforced() {
391 let org = OrganizationId::new_v4();
392 let views = [policy(org, PolicyType::PasswordGenerator, true)];
394 assert!(!is_enforced(org, &views, &[confirmed_member(org)]));
395 }
396
397 #[test]
398 fn missing_org_context_enforces_enabled_policy_by_default() {
399 let org = OrganizationId::new_v4();
400 let views = [policy(org, PolicyType::SingleOrg, true)];
401 assert!(is_enforced(org, &views, &[]));
402 }
403
404 #[test]
405 fn missing_org_context_does_not_enforce_disabled_policy() {
406 let org = OrganizationId::new_v4();
407 let views = [policy(org, PolicyType::SingleOrg, false)];
408 assert!(!is_enforced(org, &views, &[]));
409 }
410
411 fn mp_view(org: OrganizationId, data: Option<&str>) -> Policy {
415 Policy {
416 id: PolicyId::new_v4(),
417 organization_id: org,
418 r#type: PolicyType::MasterPassword,
419 data: data.map(str::to_owned),
420 enabled: true,
421 revision_date: None,
422 }
423 }
424
425 #[test]
426 fn valid_data_is_parsed() {
427 let org = OrganizationId::new_v4();
428 let views = [mp_view(org, Some(r#"{"minComplexity":3,"minLength":12}"#))];
429 let decision = MasterPasswordPolicy.get_enforced(org, &views, &[confirmed_member(org)]);
430 assert!(decision.enforced);
431 assert_eq!(decision.data.min_complexity, Some(3));
432 assert_eq!(decision.data.min_length, Some(12));
433 }
434
435 #[test]
436 fn missing_data_falls_back_to_default() {
437 let org = OrganizationId::new_v4();
438 let views = [mp_view(org, None)];
439 let decision = MasterPasswordPolicy.get_enforced(org, &views, &[confirmed_member(org)]);
440 assert!(decision.enforced);
441 assert_eq!(decision.data, MasterPasswordPolicyData::default());
442 }
443
444 #[test]
445 fn malformed_data_falls_back_to_default_without_panicking() {
446 let org = OrganizationId::new_v4();
447 let views = [mp_view(org, Some("not json"))];
448 let decision = MasterPasswordPolicy.get_enforced(org, &views, &[confirmed_member(org)]);
451 assert!(decision.enforced);
452 assert_eq!(decision.data, MasterPasswordPolicyData::default());
453 }
454
455 #[test]
456 fn data_is_defaulted_when_not_enforced() {
457 let org = OrganizationId::new_v4();
458 let views = [mp_view(org, Some(r#"{"minComplexity":3}"#))];
459 let ctx = OrganizationUserPolicyContext {
462 status: OrganizationUserStatusType::Revoked,
463 ..confirmed_member(org)
464 };
465 let decision = MasterPasswordPolicy.get_enforced(org, &views, &[ctx]);
466 assert!(!decision.enforced);
467 assert_eq!(decision.data, MasterPasswordPolicyData::default());
468 }
469
470 fn typed_view(org: OrganizationId, policy_type: PolicyType, data: &str) -> Policy {
473 Policy {
474 id: PolicyId::new_v4(),
475 organization_id: org,
476 r#type: policy_type,
477 data: Some(data.to_owned()),
478 enabled: true,
479 revision_date: None,
480 }
481 }
482
483 #[test]
484 fn maximum_vault_timeout_data_is_parsed() {
485 use crate::{MaximumVaultTimeoutPolicy, VaultTimeoutAction, VaultTimeoutType};
486
487 let org = OrganizationId::new_v4();
488 let views = [typed_view(
489 org,
490 PolicyType::MaximumVaultTimeout,
491 r#"{"type":"custom","minutes":480,"action":"logOut"}"#,
492 )];
493 let decision =
494 MaximumVaultTimeoutPolicy.get_enforced(org, &views, &[confirmed_member(org)]);
495 assert!(decision.enforced);
496 assert_eq!(decision.data.timeout_type, Some(VaultTimeoutType::Custom));
497 assert_eq!(decision.data.minutes, Some(480));
498 assert_eq!(decision.data.action, Some(VaultTimeoutAction::LogOut));
499 }
500
501 #[test]
502 fn password_generator_data_is_parsed() {
503 use crate::{PasswordGeneratorPolicy, PasswordGeneratorType};
504
505 let org = OrganizationId::new_v4();
506 let views = [typed_view(
507 org,
508 PolicyType::PasswordGenerator,
509 r#"{"overridePasswordType":"passphrase","minLength":14,"capitalize":true}"#,
510 )];
511 let decision =
512 PasswordGeneratorPolicy.get_enforced(org, &views, &[confirmed_member(org)]);
513 assert!(decision.enforced);
514 assert_eq!(
515 decision.data.override_password_type,
516 Some(PasswordGeneratorType::Passphrase)
517 );
518 assert_eq!(decision.data.min_length, Some(14));
519 assert_eq!(decision.data.capitalize, Some(true));
520 }
521 }
522
523 mod get_all_enforced {
524 use super::*;
525
526 #[test]
527 fn resolves_each_org_independently() {
528 let org_a = OrganizationId::new_v4();
529 let org_b = OrganizationId::new_v4();
530 let views = [
531 policy(org_a, PolicyType::SingleOrg, true),
532 policy(org_b, PolicyType::SingleOrg, true),
533 ];
534 let contexts = [
536 confirmed_member(org_a),
537 OrganizationUserPolicyContext {
538 role: OrganizationUserType::Owner,
539 ..confirmed_member(org_b)
540 },
541 ];
542
543 assert!(TestPolicy.get_enforced(org_a, &views, &contexts).enforced);
546 assert!(!TestPolicy.get_enforced(org_b, &views, &contexts).enforced);
547
548 let all = TestPolicy.get_all_enforced(&views, &contexts);
550 assert_eq!(all.len(), 2);
551 assert!(
552 all.iter()
553 .find(|d| d.organization_id == org_a)
554 .expect("a decision for org_a")
555 .enforced
556 );
557 assert!(
558 !all.iter()
559 .find(|d| d.organization_id == org_b)
560 .expect("a decision for org_b")
561 .enforced
562 );
563 }
564
565 #[test]
566 fn given_organization_without_policy_returns_unenforced_policy() {
567 let org_a = OrganizationId::new_v4();
568 let org_b = OrganizationId::new_v4();
569 let org_c = OrganizationId::new_v4();
570
571 let views = [
574 policy(org_a, PolicyType::SingleOrg, true),
575 policy(org_b, PolicyType::MasterPassword, true),
576 ];
577
578 let contexts = [
579 confirmed_member(org_a),
580 confirmed_member(org_b),
581 confirmed_member(org_c),
582 ];
583
584 let result = TestPolicy.get_all_enforced(&views, &contexts);
585 assert!(result.len() == 3);
586 assert!(
587 result
588 .iter()
589 .find(|p| p.organization_id == org_a)
590 .expect("a decision for org_a")
591 .enforced
592 );
593 assert!(
594 !result
595 .iter()
596 .find(|p| p.organization_id == org_b)
597 .expect("a decision for org_b")
598 .enforced
599 );
600 assert!(
601 !result
602 .iter()
603 .find(|p| p.organization_id == org_c)
604 .expect("a decision for org_c")
605 .enforced
606 );
607 }
608 }
609}