Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use bitwarden_core::{
},
require,
};
use bitwarden_policies::MasterPasswordPolicyResponse;
use bitwarden_policies::MasterPasswordPolicy;
use thiserror::Error;

use crate::login::{api::response::LoginSuccessApiResponse, models::UserDecryptionOptionsResponse};
Expand Down Expand Up @@ -71,7 +71,7 @@ pub struct LoginSuccessResponse {

/// If the user is subject to an organization master password policy,
/// this field contains the requirements of that policy.
pub master_password_policy: Option<MasterPasswordPolicyResponse>,
pub master_password_policy: Option<MasterPasswordPolicy>,

/// The user's account cryptographic keys (wrapped with the user key).
pub wrapped_account_crypto_state: Option<WrappedAccountCryptographicState>,
Expand Down
3 changes: 3 additions & 0 deletions crates/bitwarden-policies/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@ wasm = [
bitwarden-api-api = { workspace = true }
bitwarden-core = { workspace = true }
bitwarden-organizations = { workspace = true }
bitwarden-send = { workspace = true }
bitwarden-vault = { workspace = true }
chrono = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_repr = { workspace = true }
tsify = { workspace = true, optional = true }
uniffi = { workspace = true, optional = true }
Expand Down
71 changes: 71 additions & 0 deletions crates/bitwarden-policies/src/enriched_policy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//! The [`EnrichedPolicy`] model.
//!
//! An [`EnrichedPolicy`] is the strongly-typed counterpart to a
//! [`PolicyView`]: it carries the deserialized `policy.data`
//! payload (via [`EnrichedPolicyType`]) and knows how to evaluate whether it is
//! enforced against a given user.

use std::collections::HashMap;

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[cfg(feature = "wasm")]
use tsify::Tsify;
use uuid::Uuid;

use crate::{EnrichedPolicyType, OrganizationUserPolicyContext, PolicyView};

/// An organization policy - strongly typed with its data.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
pub struct EnrichedPolicy {
/// The policy's unique ID.
pub id: Uuid,
/// The organization this policy belongs to.
pub organization_id: Uuid,
/// The type of policy, with its policy definition if applicable.
pub r#type: EnrichedPolicyType,
/// Whether the policy is enabled.
pub enabled: bool,
/// When the policy was last modified.
pub revision_date: Option<DateTime<Utc>>,
}

impl EnrichedPolicy {
/// Builds an [`EnrichedPolicy`] from a raw [`PolicyView`], deserializing its
/// `data` payload into the strongly-typed [`EnrichedPolicyType`].
pub fn from_policy_view(view: &PolicyView) -> EnrichedPolicy {
EnrichedPolicy {
id: view.id,
organization_id: view.organization_id,
enabled: view.enabled,
revision_date: view.revision_date,
r#type: EnrichedPolicyType::from_policy_type(view.r#type, view.data.as_deref()),
}
}

/// Returns whether this policy is enforced against the user described by the
/// given organization contexts, applying the policy definition's exemption
/// and applicability rules.
pub fn enforced(
&self,
organization_user_policy_contexts: &HashMap<Uuid, OrganizationUserPolicyContext>,
) -> bool {
let org = organization_user_policy_contexts.get(&self.organization_id);
let definition = self.r#type.to_policy_definition();

self.enabled
&& match org {
Some(org) => {
org.enabled
&& org.use_policies
&& definition.applicable_statuses().contains(&org.status)
&& !definition.exempt_roles().contains(&org.role)
&& !(org.is_provider_user && definition.exempt_providers())
}
None => true, // Unknown org: enforce by default
}
}
}
150 changes: 150 additions & 0 deletions crates/bitwarden-policies/src/enriched_policy_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
//! The [`EnrichedPolicyType`] enum.
//!
//! [`PolicyType`] is a bare discriminant that matches the
//! server's numeric wire format. `EnrichedPolicyType` mirrors every variant of
//! that enum but additionally carries the strongly-typed `policy.data` payload
//! (see [`policy_definitions`](crate::policy_definitions)) for the policies that have one.
//! Toggle-only policies (whose `data` is always `null`) are unit variants.

use serde::{Deserialize, Serialize};
#[cfg(feature = "wasm")]
use tsify::Tsify;

use crate::{
PolicyType,
policy_definition::{DefaultPolicyDefinition, PolicyDefinition},
policy_definitions::{
AutomaticAppLoginPolicy, AutomaticUserConfirmationPolicy, FreeFamiliesSponsorshipPolicy,
MasterPasswordPolicy, MaximumSessionTimeoutPolicy, OrganizationDataOwnershipPolicy,
OrganizationUserNotificationPolicy, PasswordGeneratorPolicy, RemoveUnlockWithPinPolicy,
ResetPasswordPolicy, RestrictedItemTypesPolicy, SendControlsPolicy, SendOptionsPolicy,
UriMatchDefaultPolicy,
},
};

/// Helper function to parse policy data.
fn parse_data<T: serde::de::DeserializeOwned + Default>(data: Option<&str>) -> T {
match data {
Some(d) => serde_json::from_str(d).unwrap_or_default(), /* TODO: log deserialization */
// failures
None => T::default(),
}
}

/// A [`PolicyType`] paired with its strongly-typed
/// `policy.data` payload.
///
/// Variants mirror [`PolicyType`] one-to-one. Policies that
/// carry configuration wrap their payload struct; toggle-only policies (whose
/// `data` is always `null`) are unit variants.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
pub enum EnrichedPolicyType {
/// Requires members to have two-step login enabled on their account.
TwoFactorAuthentication,
/// Sets minimum requirements for members' master passwords.
MasterPassword(MasterPasswordPolicy),
/// Sets minimum requirements for the password generator.
PasswordGenerator(PasswordGeneratorPolicy),
/// Restricts members to being part of a single organization.
SingleOrg,
/// Requires members to authenticate with single sign-on.
RequireSso,
/// Forces newly added or cloned items to be owned by the organization.
OrganizationDataOwnership(OrganizationDataOwnershipPolicy),
/// Disables the ability to create and edit Bitwarden Sends.
DisableSend,
/// Sets restrictions or defaults for Bitwarden Sends.
SendOptions(SendOptionsPolicy),
/// Allows administrators to recover member accounts.
ResetPassword(ResetPasswordPolicy),
/// Sets the maximum allowed vault timeout for members.
MaximumVaultTimeout(MaximumSessionTimeoutPolicy),
/// Disables members' ability to export their personal vault.
DisablePersonalVaultExport,
/// Activates autofill on page load in the browser extension.
ActivateAutofill,
/// Automatically logs members into apps using single sign-on.
AutomaticAppLogIn(AutomaticAppLoginPolicy),
/// Removes members' access to the free Bitwarden Families sponsorship benefit.
FreeFamiliesSponsorship,
/// Prevents members from unlocking the app with a PIN.
RemoveUnlockWithPin,
/// Restricts the item types that members can create.
RestrictedItemTypes,
/// Sets the default URI match detection strategy for autofill.
UriMatchDefaults(UriMatchDefaultPolicy),
/// Sets the default behavior for the autotype feature.
AutotypeDefaultSetting,
/// Automatically confirms invited users into the organization.
AutomaticUserConfirmation,
/// Blocks account creation for users with email addresses on claimed domains.
BlockClaimedDomainAccountCreation,
/// Displays an organization-configured banner message to members.
OrganizationUserNotification(OrganizationUserNotificationPolicy),
/// Configures Send-related behavior (disabling Sends, email visibility,
/// access controls, Send types, and deletion).
SendControls(SendControlsPolicy),
}

impl EnrichedPolicyType {
/// Returns the [`PolicyDefinition`] trait
/// implementer for this policy type.
///
/// Policies with custom rules return their own definition; policies without custom rules fall
/// back to a default definition.
pub fn to_policy_definition(&self) -> &dyn PolicyDefinition {
match self {
EnrichedPolicyType::MasterPassword(p) => p,
EnrichedPolicyType::PasswordGenerator(p) => p,
EnrichedPolicyType::MaximumVaultTimeout(p) => p,
EnrichedPolicyType::FreeFamiliesSponsorship => &FreeFamiliesSponsorshipPolicy,
EnrichedPolicyType::RemoveUnlockWithPin => &RemoveUnlockWithPinPolicy,
EnrichedPolicyType::RestrictedItemTypes => &RestrictedItemTypesPolicy,
EnrichedPolicyType::AutomaticUserConfirmation => &AutomaticUserConfirmationPolicy,
EnrichedPolicyType::OrganizationUserNotification(p) => p,
// Policies without custom rules use the default definition
_ => &DefaultPolicyDefinition,
}
}

/// Constructs an `EnrichedPolicyType` from a `PolicyType` and optional JSON data.
///
/// For policies with configuration data, the JSON string is deserialized into the
/// appropriate data structure. If deserialization fails or data is missing, the
/// policy defaults are used.
pub fn from_policy_type(policy_type: PolicyType, data: Option<&str>) -> Self {
match policy_type {
PolicyType::TwoFactorAuthentication => Self::TwoFactorAuthentication,
PolicyType::MasterPassword => Self::MasterPassword(parse_data(data)),
PolicyType::PasswordGenerator => Self::PasswordGenerator(parse_data(data)),
PolicyType::SingleOrg => Self::SingleOrg,
PolicyType::RequireSso => Self::RequireSso,
PolicyType::OrganizationDataOwnership => {
Self::OrganizationDataOwnership(parse_data(data))
}
PolicyType::DisableSend => Self::DisableSend,
PolicyType::SendOptions => Self::SendOptions(parse_data(data)),
PolicyType::ResetPassword => Self::ResetPassword(parse_data(data)),
PolicyType::MaximumVaultTimeout => Self::MaximumVaultTimeout(parse_data(data)),
PolicyType::DisablePersonalVaultExport => Self::DisablePersonalVaultExport,
PolicyType::ActivateAutofill => Self::ActivateAutofill,
PolicyType::AutomaticAppLogIn => Self::AutomaticAppLogIn(parse_data(data)),
PolicyType::FreeFamiliesSponsorship => Self::FreeFamiliesSponsorship,
PolicyType::RemoveUnlockWithPin => Self::RemoveUnlockWithPin,
PolicyType::RestrictedItemTypes => Self::RestrictedItemTypes,
PolicyType::UriMatchDefaults => Self::UriMatchDefaults(parse_data(data)),
PolicyType::AutotypeDefaultSetting => Self::AutotypeDefaultSetting,
PolicyType::AutomaticUserConfirmation => Self::AutomaticUserConfirmation,
PolicyType::BlockClaimedDomainAccountCreation => {
Self::BlockClaimedDomainAccountCreation
}
PolicyType::OrganizationUserNotification => {
Self::OrganizationUserNotification(parse_data(data))
}
PolicyType::SendControls => Self::SendControls(parse_data(data)),
}
}
}
Loading
Loading