Browse Source

Merge e924a212f0 into 0cefa4cca7

pull/7519/merge
Tom 6 days ago
committed by GitHub
parent
commit
0aee7ee3ff
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  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

@ -1441,6 +1441,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