Browse Source

Add the Send controls policy

Bitwarden consolidated the Send policies into PolicyType.SendControls (21). It
absorbs DisableSend (6) and SendOptions (7) and adds restrictions of its own:
a required access type, allowed domains, allowed Send types and a maximum
lifetime.

Upstream keeps the two absorbed flags on the legacy policies and mirrors them
into the container in both directions with dedicated policy event handlers, so
that clients which only know the legacy policies keep enforcing the same rules
and so that a rollback stays safe. put_policy does the same, which also leaves
the existing enforce_disable_send_policy and enforce_disable_hide_email_policy
authoritative for those two flags. Only the new restrictions are enforced from
the container, in enforce_send_controls_policy.

Two of the new restrictions cannot be honoured here. Vaultwarden rejects Sends
that carry recipient emails, so requiring the specific people access type would
leave members unable to create any Send at all, and allowed domains only exist
together with that access type. Both are refused when the policy is saved
rather than silently ignored or silently locking an organization out. A non
positive maximum lifetime is refused for the same reason.

Removing the password of a Send is now refused as well while a policy requires
password protection, and an update keeps enforcing it even though the client
does not resend the existing password.

The clients hide the policy behind the pm-31885-send-controls feature flag, so
that flag is added to the supported experimental flags.

Refs: https://github.com/bitwarden/server/pull/7113
pull/7519/head
tom27052006 2 weeks ago
parent
commit
737f073c4e
  1. 156
      src/api/core/organizations.rs
  2. 71
      src/api/core/sends.rs
  3. 2
      src/config.rs
  4. 4
      src/db/models/mod.rs
  5. 118
      src/db/models/org_policy.rs

156
src/api/core/organizations.rs

@ -8,7 +8,7 @@ use crate::{
CONFIG, CONFIG,
api::admin::FAKE_ADMIN_UUID, api::admin::FAKE_ADMIN_UUID,
api::{ api::{
EmptyResult, JsonResult, Notify, PasswordOrOtpData, UpdateType, ApiResult, EmptyResult, JsonResult, Notify, PasswordOrOtpData, UpdateType,
core::{CipherSyncData, CipherSyncType, accept_org_invite, log_event, two_factor}, core::{CipherSyncData, CipherSyncType, accept_org_invite, log_event, two_factor},
}, },
auth::{AdminHeaders, Headers, ManagerHeaders, ManagerHeadersLoose, OrgMemberHeaders, OwnerHeaders, decode_invite}, auth::{AdminHeaders, Headers, ManagerHeaders, ManagerHeadersLoose, OrgMemberHeaders, OwnerHeaders, decode_invite},
@ -17,7 +17,8 @@ use crate::{
models::{ models::{
Cipher, CipherId, Collection, CollectionCipher, CollectionGroup, CollectionId, CollectionUser, EventType, Cipher, CipherId, Collection, CollectionCipher, CollectionGroup, CollectionId, CollectionUser, EventType,
Group, GroupId, GroupUser, Invitation, Membership, MembershipId, MembershipStatus, MembershipType, Group, GroupId, GroupUser, Invitation, Membership, MembershipId, MembershipStatus, MembershipType,
OrgPolicy, OrgPolicyType, Organization, OrganizationApiKey, OrganizationId, User, UserId, OrgPolicy, OrgPolicyType, Organization, OrganizationApiKey, OrganizationId, SendControlsPolicyData,
SendOptionsPolicyData, SendWhoCanAccessType, User, UserId,
}, },
}, },
mail, mail,
@ -2160,6 +2161,10 @@ async fn put_policy(
} }
} }
if pol_type_enum == OrgPolicyType::SendControls && data.enabled {
validate_send_controls(&parse_send_controls(data.data.as_ref())?)?;
}
let mut policy = match OrgPolicy::find_by_org_and_type(&org_id, pol_type_enum, &conn).await { let mut policy = match OrgPolicy::find_by_org_and_type(&org_id, pol_type_enum, &conn).await {
Some(p) => p, Some(p) => p,
None => OrgPolicy::new(org_id.clone(), pol_type_enum, false, "{}".to_owned()), None => OrgPolicy::new(org_id.clone(), pol_type_enum, false, "{}".to_owned()),
@ -2169,6 +2174,8 @@ async fn put_policy(
policy.data = serde_json::to_string(&data.data)?; policy.data = serde_json::to_string(&data.data)?;
policy.save(&conn).await?; policy.save(&conn).await?;
sync_send_policies(pol_type_enum, &policy, &org_id, &conn).await?;
log_event( log_event(
EventType::PolicyUpdated as i32, EventType::PolicyUpdated as i32,
policy.uuid.as_ref(), policy.uuid.as_ref(),
@ -2183,6 +2190,124 @@ async fn put_policy(
Ok(Json(policy.to_json())) Ok(Json(policy.to_json()))
} }
fn parse_send_controls(data: Option<&Value>) -> ApiResult<SendControlsPolicyData> {
match data {
None | Some(Value::Null) => Ok(SendControlsPolicyData::default()),
Some(value) => match serde_json::from_value::<SendControlsPolicyData>(value.clone()) {
Ok(parsed) => Ok(parsed),
Err(e) => err!(format!("Invalid Send controls policy data: {e}")),
},
}
}
fn validate_send_controls(data: &SendControlsPolicyData) -> EmptyResult {
// Vaultwarden rejects Sends that carry recipient emails, so requiring email verification would
// leave the members of this organization unable to create any Send at all.
if data.required_access_type() == Some(SendWhoCanAccessType::SpecificPeople) {
err!("Sends with email verification are not supported, so that access type cannot be required")
}
// Upstream only accepts allowed domains together with the specific people access type, which
// the check above already rules out.
if data.allowed_domains.is_some() {
err!("Allowed domains can only be set when the required access type is set to specific people")
}
// A non positive value would mean no Send could ever satisfy the policy.
if data.deletion_hours.is_some_and(|hours| hours < 1) {
err!("The maximum lifetime of a Send has to be at least one hour")
}
Ok(())
}
/// The `Send controls` policy is a container that absorbs the older `DisableSend` and
/// `Send Options` policies. Upstream mirrors changes in both directions with dedicated policy event
/// handlers, so that clients which only know the legacy policies keep enforcing the same rules and
/// so that a rollback stays safe. We do the same, which also keeps the existing enforcement in
/// `sends.rs` authoritative for those two flags.
///
/// Ref: https://github.com/bitwarden/server/blob/main/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs
async fn sync_send_policies(
pol_type: OrgPolicyType,
saved: &OrgPolicy,
org_id: &OrganizationId,
conn: &DbConn,
) -> EmptyResult {
match pol_type {
OrgPolicyType::SendControls => {
let data = saved.send_controls_data();
// Upstream leaves the data of the DisableSend policy untouched, it carries no options.
upsert_mirrored_policy(org_id, OrgPolicyType::DisableSend, saved.enabled && data.disable_send, None, conn)
.await?;
let send_options = SendOptionsPolicyData {
disable_hide_email: data.disable_hide_email,
};
upsert_mirrored_policy(
org_id,
OrgPolicyType::SendOptions,
saved.enabled && data.disable_hide_email,
Some(serde_json::to_string(&send_options)?),
conn,
)
.await?;
}
OrgPolicyType::DisableSend | OrgPolicyType::SendOptions => {
// Keep every restriction that only exists on the Send controls policy.
let mut data = match OrgPolicy::find_by_org_and_type(org_id, OrgPolicyType::SendControls, conn).await {
Some(p) => p.send_controls_data(),
None => SendControlsPolicyData::default(),
};
let disable_send = OrgPolicy::find_by_org_and_type(org_id, OrgPolicyType::DisableSend, conn)
.await
.is_some_and(|p| p.enabled);
let send_options = OrgPolicy::find_by_org_and_type(org_id, OrgPolicyType::SendOptions, conn).await;
data.disable_send = disable_send;
// Upstream reads this out of the data of the legacy policy regardless of whether that
// policy is enabled, the enabled flag is only folded into the container below.
data.disable_hide_email = send_options
.as_ref()
.and_then(|p| serde_json::from_str::<SendOptionsPolicyData>(&p.data).ok())
.is_some_and(|d| d.disable_hide_email);
let enabled = disable_send || send_options.is_some_and(|p| p.enabled);
upsert_mirrored_policy(
org_id,
OrgPolicyType::SendControls,
enabled,
Some(serde_json::to_string(&data)?),
conn,
)
.await?;
}
_ => (),
}
Ok(())
}
async fn upsert_mirrored_policy(
org_id: &OrganizationId,
pol_type: OrgPolicyType,
enabled: bool,
data: Option<String>,
conn: &DbConn,
) -> EmptyResult {
let mut policy = match OrgPolicy::find_by_org_and_type(org_id, pol_type, conn).await {
Some(p) => p,
None => OrgPolicy::new(org_id.clone(), pol_type, false, "null".to_owned()),
};
policy.enabled = enabled;
if let Some(data) = data {
policy.data = data;
}
policy.save(conn).await
}
// Deprecated with client v2026.5.0 // Deprecated with client v2026.5.0
#[put("/organizations/<org_id>/policies/<pol_type>/vnext", data = "<data>")] #[put("/organizations/<org_id>/policies/<pol_type>/vnext", data = "<data>")]
async fn put_policy_vnext( async fn put_policy_vnext(
@ -3251,3 +3376,30 @@ async fn rotate_api_key(
) -> JsonResult { ) -> JsonResult {
api_key(&org_id, data, true, headers, conn).await api_key(&org_id, data, true, headers, conn).await
} }
#[cfg(test)]
mod tests {
use super::*;
fn send_controls(json: &str) -> SendControlsPolicyData {
serde_json::from_str(json).expect("valid Send controls payload")
}
#[test]
fn send_controls_rejects_restrictions_vaultwarden_cannot_satisfy() {
// Requiring recipient emails would lock every member out of creating Sends.
assert!(validate_send_controls(&send_controls(r#"{"whoCanAccess":2}"#)).is_err());
assert!(validate_send_controls(&send_controls(r#"{"allowedDomains":"example.com"}"#)).is_err());
assert!(validate_send_controls(&send_controls(r#"{"deletionHours":0}"#)).is_err());
}
#[test]
fn send_controls_accepts_the_supported_restrictions() {
assert!(validate_send_controls(&send_controls(r#"{"disableSend":true,"disableHideEmail":true}"#)).is_ok());
assert!(
validate_send_controls(&send_controls(r#"{"whoCanAccess":1,"deletionHours":24,"allowedSendTypes":[0]}"#))
.is_ok()
);
assert!(validate_send_controls(&SendControlsPolicyData::default()).is_ok());
}
}

71
src/api/core/sends.rs

@ -16,7 +16,7 @@ use crate::{
config::PathType, config::PathType,
db::{ db::{
DbConn, DbPool, DbConn, DbPool,
models::{Device, OrgPolicy, OrgPolicyType, Send, SendFileId, SendId, SendType, UserId}, models::{Device, OrgPolicy, OrgPolicyType, Send, SendFileId, SendId, SendType, SendWhoCanAccessType, UserId},
}, },
util::{NumberOrString, save_temp_file}, util::{NumberOrString, save_temp_file},
}; };
@ -129,6 +129,60 @@ async fn enforce_disable_hide_email_policy(data: &SendData, headers: &Headers, c
Ok(()) Ok(())
} }
/// Enforces the restrictions that only exist on the `Send controls` policy.
///
/// `DisableSend` and `DisableHideEmail` are deliberately not checked here. Upstream keeps enforcing
/// those two through the legacy `DisableSend` and `Send Options` policies and mirrors them into
/// `Send controls`, which `put_policy` does as well, so the two functions above stay authoritative.
///
/// `has_password` and `creation_date` describe the Send as it will look after the request: on an
/// update the client may leave the password out to keep the existing one.
///
/// Ref: https://github.com/bitwarden/server/blob/main/src/Core/Tools/SendFeatures/Services/SendValidationService.cs
async fn enforce_send_controls_policy(
data: &SendData,
has_password: bool,
creation_date: DateTime<Utc>,
headers: &Headers,
conn: &DbConn,
) -> EmptyResult {
let controls = OrgPolicy::send_controls_for_user(&headers.user.uuid, conn).await;
match controls.required_access_type() {
Some(SendWhoCanAccessType::PasswordProtected) if !has_password => {
err!("Due to an Enterprise Policy, your Sends have to be protected by a password.")
}
// Vaultwarden rejects Sends that carry recipient emails, so this can never be satisfied.
// `put_policy` already refuses to store such a policy, this only guards rows that were
// written before or outside of that check.
Some(SendWhoCanAccessType::SpecificPeople) => {
err!(
"Due to an Enterprise Policy, your Sends have to be protected by email verification, \
which is not supported."
)
}
_ => (),
}
if let Some(allowed_types) = &controls.allowed_send_types
&& !allowed_types.contains(&data.r#type)
{
err!("Due to an Enterprise Policy, your Sends have to be of a type the organization allows.")
}
if let Some(hours) = controls.deletion_hours {
// Upstream allows for up to a minute of skew between the deletion and the creation date.
let lifetime = data.deletion_date - creation_date - TimeDelta::minutes(1);
if lifetime.num_minutes() > i64::from(hours) * 60 {
err!(format!(
"Due to an Enterprise Policy, the deletion date of your Sends has to be within {hours} hours of their creation date."
))
}
}
Ok(())
}
fn create_send(data: SendData, user_id: UserId) -> ApiResult<Send> { fn create_send(data: SendData, user_id: UserId) -> ApiResult<Send> {
let data_val = if data.r#type == SendType::Text as i32 { let data_val = if data.r#type == SendType::Text as i32 {
data.text data.text
@ -199,6 +253,7 @@ async fn post_send(data: Json<SendData>, headers: Headers, conn: DbConn, nt: Not
let data: SendData = data.into_inner(); let data: SendData = data.into_inner();
enforce_disable_hide_email_policy(&data, &headers, &conn).await?; enforce_disable_hide_email_policy(&data, &headers, &conn).await?;
enforce_send_controls_policy(&data, data.password.is_some(), Utc::now(), &headers, &conn).await?;
if data.r#type == SendType::File as i32 { if data.r#type == SendType::File as i32 {
err!("File sends should use /api/sends/file") err!("File sends should use /api/sends/file")
@ -252,6 +307,7 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, conn: DbCo
} }
enforce_disable_hide_email_policy(&model, &headers, &conn).await?; enforce_disable_hide_email_policy(&model, &headers, &conn).await?;
enforce_send_controls_policy(&model, model.password.is_some(), Utc::now(), &headers, &conn).await?;
let size_limit = match CONFIG.user_send_limit() { let size_limit = match CONFIG.user_send_limit() {
Some(0) => err!("File uploads are disabled"), Some(0) => err!("File uploads are disabled"),
@ -317,6 +373,7 @@ async fn post_send_file_v2(data: Json<SendData>, headers: Headers, conn: DbConn)
} }
enforce_disable_hide_email_policy(&data, &headers, &conn).await?; enforce_disable_hide_email_policy(&data, &headers, &conn).await?;
enforce_send_controls_policy(&data, data.password.is_some(), Utc::now(), &headers, &conn).await?;
let file_length = if let Some(m) = &data.file_length { let file_length = if let Some(m) = &data.file_length {
m.into_i64()? m.into_i64()?
@ -637,6 +694,11 @@ async fn put_send(send_id: SendId, data: Json<SendData>, headers: Headers, conn:
err!("Sends with email verification is not supported"); err!("Sends with email verification is not supported");
} }
// Leaving out the password keeps the existing one, and the deletion date is measured against
// the date the Send was originally created on.
let has_password = data.password.is_some() || send.password_hash.is_some();
enforce_send_controls_policy(&data, has_password, send.creation_date.and_utc(), &headers, &conn).await?;
update_send_from_data(&mut send, data, &headers, &conn, &nt, UpdateType::SyncSendUpdate).await?; update_send_from_data(&mut send, data, &headers, &conn, &nt, UpdateType::SyncSendUpdate).await?;
Ok(Json(send.to_json())) Ok(Json(send.to_json()))
@ -723,6 +785,13 @@ async fn delete_send(send_id: SendId, headers: Headers, conn: DbConn, nt: Notify
async fn put_remove_password(send_id: SendId, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult { async fn put_remove_password(send_id: SendId, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult {
enforce_disable_send_policy(&headers, &conn).await?; enforce_disable_send_policy(&headers, &conn).await?;
// Removing the password would leave the Send non compliant with a policy that requires one.
if OrgPolicy::send_controls_for_user(&headers.user.uuid, &conn).await.required_access_type()
== Some(SendWhoCanAccessType::PasswordProtected)
{
err!("Due to an Enterprise Policy, your Sends have to be protected by a password.")
}
let Some(mut send) = Send::find_by_uuid_and_user(&send_id, &headers.user.uuid, &conn).await else { let Some(mut send) = Send::find_by_uuid_and_user(&send_id, &headers.user.uuid, &conn).await else {
err!("Send not found", "Invalid send uuid, or does not belong to user") err!("Send not found", "Invalid send uuid, or does not belong to user")
}; };

2
src/config.rs

@ -1436,6 +1436,8 @@ pub const SUPPORTED_FEATURE_FLAGS: &[&str] = &[
"cxp-export-mobile", "cxp-export-mobile",
// Platform Team // Platform Team
"pm-30529-webauthn-related-origins", "pm-30529-webauthn-related-origins",
// Tools Team
"pm-31885-send-controls",
]; ];
impl Config { impl Config {

4
src/db/models/mod.rs

@ -29,7 +29,9 @@ pub use self::event::{Event, EventType};
pub use self::favorite::Favorite; pub use self::favorite::Favorite;
pub use self::folder::{Folder, FolderCipher, FolderId}; pub use self::folder::{Folder, FolderCipher, FolderId};
pub use self::group::{CollectionGroup, Group, GroupId, GroupUser}; pub use self::group::{CollectionGroup, Group, GroupId, GroupUser};
pub use self::org_policy::{OrgPolicy, OrgPolicyId, OrgPolicyType}; pub use self::org_policy::{
OrgPolicy, OrgPolicyId, OrgPolicyType, SendControlsPolicyData, SendOptionsPolicyData, SendWhoCanAccessType,
};
pub use self::organization::{ pub use self::organization::{
Membership, MembershipId, MembershipStatus, MembershipType, OrgApiKeyId, Organization, OrganizationApiKey, Membership, MembershipId, MembershipStatus, MembershipType, OrgApiKeyId, Organization, OrganizationApiKey,
OrganizationId, OrganizationId,

118
src/db/models/org_policy.rs

@ -49,16 +49,54 @@ pub enum OrgPolicyType {
// AutotypeDefaultSetting = 17, // Not supported yet // AutotypeDefaultSetting = 17, // Not supported yet
// AutoConfirm = 18, // Not supported (not implemented yet) // AutoConfirm = 18, // Not supported (not implemented yet)
// BlockClaimedDomainAccountCreation = 19, // Not supported (Not AGPLv3 Licensed) // BlockClaimedDomainAccountCreation = 19, // Not supported (Not AGPLv3 Licensed)
// OrganizationUserNotification = 20, // Not supported (not implemented yet)
SendControls = 21,
} }
// https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendOptionsPolicyData.cs#L5 // https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendOptionsPolicyData.cs#L5
#[derive(Deserialize)] #[derive(Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct SendOptionsPolicyData { pub struct SendOptionsPolicyData {
#[serde(rename = "disableHideEmail", alias = "DisableHideEmail")] #[serde(rename = "disableHideEmail", alias = "DisableHideEmail")]
pub disable_hide_email: bool, pub disable_hide_email: bool,
} }
// https://github.com/bitwarden/server/blob/main/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendControlsAllowedAccessControl.cs
#[derive(Copy, Clone, Eq, PartialEq, num_derive::FromPrimitive)]
pub enum SendWhoCanAccessType {
Any = 0,
PasswordProtected = 1,
SpecificPeople = 2,
}
// https://github.com/bitwarden/server/blob/main/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendControlsPolicyData.cs
//
// The `Send controls` policy absorbs `DisableSend` and `SendOptions` and adds restrictions of its
// own. The web vault we ship only renders the first four fields so far, the last two are already
// part of the data model upstream and are parsed and enforced here as well.
#[derive(Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SendControlsPolicyData {
#[serde(rename = "disableSend", alias = "DisableSend", default)]
pub disable_send: bool,
#[serde(rename = "disableHideEmail", alias = "DisableHideEmail", default)]
pub disable_hide_email: bool,
#[serde(rename = "whoCanAccess", alias = "WhoCanAccess", default)]
pub who_can_access: Option<i32>,
#[serde(rename = "allowedDomains", alias = "AllowedDomains", default)]
pub allowed_domains: Option<String>,
#[serde(rename = "deletionHours", alias = "DeletionHours", default)]
pub deletion_hours: Option<i32>,
#[serde(rename = "allowedSendTypes", alias = "AllowedSendTypes", default)]
pub allowed_send_types: Option<Vec<i32>>,
}
impl SendControlsPolicyData {
pub fn required_access_type(&self) -> Option<SendWhoCanAccessType> {
self.who_can_access.and_then(num_traits::FromPrimitive::from_i32)
}
}
// https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Core/AdminConsole/Models/Data/Organizations/Policies/ResetPasswordDataModel.cs // https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Core/AdminConsole/Models/Data/Organizations/Policies/ResetPasswordDataModel.cs
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -352,6 +390,43 @@ impl OrgPolicy {
false false
} }
/// Reads the `Send controls` data of this policy, falling back to the defaults when the stored
/// data is missing or unreadable. A policy row without data means "no restrictions", so a
/// broken payload must not accidentally lock users out of creating Sends.
pub fn send_controls_data(&self) -> SendControlsPolicyData {
if let Ok(data) = serde_json::from_str::<SendControlsPolicyData>(&self.data) {
return data;
}
if self.data != "null" && !self.data.is_empty() {
error!("Failed to deserialize SendControlsPolicyData: {}", self.data);
}
SendControlsPolicyData::default()
}
/// Combines the `Send controls` policies of every organization the user is a plain member of.
/// Mirrors upstreams `SendControlsPolicyRequirementFactory`: the two toggles are ORed, the
/// remaining restrictions are taken from the first organization that sets them.
pub async fn send_controls_for_user(user_uuid: &UserId, conn: &DbConn) -> SendControlsPolicyData {
let mut result = SendControlsPolicyData::default();
for policy in
OrgPolicy::find_confirmed_by_user_and_active_policy(user_uuid, OrgPolicyType::SendControls, conn).await
{
if let Some(user) = Membership::find_confirmed_by_user_and_org(user_uuid, &policy.org_uuid, conn).await
&& user.atype < MembershipType::Admin
{
let data = policy.send_controls_data();
result.disable_send |= data.disable_send;
result.disable_hide_email |= data.disable_hide_email;
result.who_can_access = result.who_can_access.or(data.who_can_access);
result.allowed_domains = result.allowed_domains.or(data.allowed_domains);
result.deletion_hours = result.deletion_hours.or(data.deletion_hours);
result.allowed_send_types = result.allowed_send_types.or(data.allowed_send_types);
}
}
result
}
pub async fn is_enabled_for_member(member_uuid: &MembershipId, policy_type: OrgPolicyType, conn: &DbConn) -> bool { pub async fn is_enabled_for_member(member_uuid: &MembershipId, policy_type: OrgPolicyType, conn: &DbConn) -> bool {
if let Some(member) = Membership::find_by_uuid(member_uuid, conn).await if let Some(member) = Membership::find_by_uuid(member_uuid, conn).await
&& let Some(policy) = OrgPolicy::find_by_org_and_type(&member.org_uuid, policy_type, conn).await && let Some(policy) = OrgPolicy::find_by_org_and_type(&member.org_uuid, policy_type, conn).await
@ -364,3 +439,44 @@ impl OrgPolicy {
#[derive(Clone, Debug, AsRef, DieselNewType, From, FromForm, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Clone, Debug, AsRef, DieselNewType, From, FromForm, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct OrgPolicyId(String); pub struct OrgPolicyId(String);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn send_controls_data_parses_the_payload_the_clients_send() {
let data: SendControlsPolicyData = serde_json::from_str(
r#"{"disableSend":true,"disableHideEmail":true,"whoCanAccess":1,"allowedDomains":null}"#,
)
.unwrap();
assert!(data.disable_send);
assert!(data.disable_hide_email);
assert!(data.required_access_type() == Some(SendWhoCanAccessType::PasswordProtected));
assert!(data.allowed_domains.is_none());
assert!(data.deletion_hours.is_none());
assert!(data.allowed_send_types.is_none());
}
#[test]
fn send_controls_data_accepts_the_pascal_case_aliases_and_fills_in_defaults() {
let data: SendControlsPolicyData = serde_json::from_str(r#"{"DisableSend":true}"#).unwrap();
assert!(data.disable_send);
assert!(!data.disable_hide_email);
assert!(data.required_access_type().is_none());
}
#[test]
fn a_policy_without_readable_data_restricts_nothing() {
let org_uuid = OrganizationId::from(String::from("00000000-0000-0000-0000-000000000000"));
let policy = OrgPolicy::new(org_uuid, OrgPolicyType::SendControls, true, "null".to_owned());
let data = policy.send_controls_data();
assert!(!data.disable_send);
assert!(!data.disable_hide_email);
assert!(data.required_access_type().is_none());
assert!(data.deletion_hours.is_none());
}
}

Loading…
Cancel
Save