Browse Source

Merge f78268e371 into 17b159beff

pull/7519/merge
Tom 2 days ago
committed by GitHub
parent
commit
6c29f2c70b
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      .env.template
  2. 14
      src/api/core/organizations.rs
  3. 59
      src/api/core/sends.rs
  4. 2
      src/config.rs
  5. 54
      src/db/models/org_policy.rs
  6. 289
      src/db/models/org_policy/send_controls.rs

2
.env.template

@ -406,6 +406,8 @@
## - "pm-30529-webauthn-related-origins": Allow passkeys stored in the vault to be used on related origins listed in a site's /.well-known/webauthn file (Browser >= 2026.2.0)
## - "pm-32009-new-item-types": Enable new item types: Bank Account, Driver's License, and Passport (Clients >= 2026.4.0)
## - "pm-34171-card-scanner": Enable the new card scanner feature on mobile (Android >= 2026.4.1, iOS >= 2026.4.1)
## - "pm-31885-send-controls": Use the Send controls policy, which replaces the Remove Send and Send options policies (Web vault >= 2026.4.0, Android >= 2026.8.0, iOS >= 2026.7.1)
## The web vault, browser, desktop and CLI clients after 2026.10 always use it, only older versions and the mobile apps still read this flag.
## - "enable-basic-auth-response": Enable HTTP Basic Auth autofill in the browser extension (Browser >= 2026.9.0)
## - "undetermined-cipher-scenario-logic": Enable the rewritten add/update login notification triggering logic in the browser extension (Browser >= 2026.2.0)
## - "biometrics-sdk-ipc": Enable biometric unlock over the SDK IPC framework (Desktop >= 2026.9.0, Browser >= 2026.9.0)

14
src/api/core/organizations.rs

@ -1963,7 +1963,10 @@ async fn list_policies(org_id: OrganizationId, headers: AdminHeaders, conn: DbCo
if org_id != headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
let policies = OrgPolicy::find_by_org(&org_id, &conn).await;
let mut policies = OrgPolicy::find_by_org(&org_id, &conn).await;
if !policies.iter().any(|p| p.has_type(OrgPolicyType::SendControls)) {
policies.push(OrgPolicy::synthesize_send_controls(&org_id, &policies));
}
let policies_json: Vec<Value> = policies.iter().map(OrgPolicy::to_json).collect();
Ok(Json(json!({
@ -2036,6 +2039,9 @@ async fn get_policy(org_id: OrganizationId, pol_type: i32, headers: AdminHeaders
let policy = match OrgPolicy::find_by_org_and_type(&org_id, pol_type_enum, &conn).await {
Some(p) => p,
None if pol_type_enum == OrgPolicyType::SendControls => {
OrgPolicy::synthesize_send_controls(&org_id, &OrgPolicy::find_by_org(&org_id, &conn).await)
}
None => OrgPolicy::new(org_id.clone(), pol_type_enum, false, "null".to_owned()),
};
@ -2207,13 +2213,15 @@ async fn put_policy(
}
}
let policy_data = OrgPolicy::send_policy_data(pol_type_enum, data.data)?;
let mut policy = match current_policy {
Some(p) => p,
None => OrgPolicy::new(org_id.clone(), pol_type_enum, false, "{}".to_owned()),
};
policy.enabled = data.enabled;
policy.data = serde_json::to_string(&data.data)?;
policy.data = serde_json::to_string(&policy_data)?;
policy.save(&conn).await?;
log_event(
@ -2227,6 +2235,8 @@ async fn put_policy(
)
.await;
OrgPolicy::sync_send_policies(&policy, &conn).await?;
Ok(Json(policy.to_json()))
}

59
src/api/core/sends.rs

@ -16,7 +16,7 @@ use crate::{
config::PathType,
db::{
DbConn, DbPool,
models::{Device, OrgPolicy, OrgPolicyType, Send, SendFileId, SendId, SendType, UserId},
models::{Device, OrgPolicy, Send, SendFileId, SendId, SendType, UserId},
},
util::{NumberOrString, save_temp_file},
};
@ -90,40 +90,16 @@ pub struct SendData {
pub id: Option<SendId>,
}
/// Enforces the `Disable Send` policy. A non-owner/admin user belonging to
/// an org with this policy enabled isn't allowed to create new Sends or
/// modify existing ones, but is allowed to delete them.
///
/// Ref: https://bitwarden.com/help/article/policies/#disable-send
/// Enforces the Send policies (`Send controls` and the legacy `Disable Send` and `Send Options` it
/// absorbs) on the Send as it would be saved. Deleting a Send is always allowed.
///
/// There is also a Vaultwarden-specific `sends_allowed` config setting that
/// controls this policy globally.
async fn enforce_disable_send_policy(headers: &Headers, conn: &DbConn) -> EmptyResult {
let user_id = &headers.user.uuid;
if !CONFIG.sends_allowed()
|| OrgPolicy::is_applicable_to_user(user_id, OrgPolicyType::DisableSend, None, conn).await
{
async fn enforce_send_policy(send: &Send, headers: &Headers, conn: &DbConn) -> EmptyResult {
if !CONFIG.sends_allowed() {
err!("Due to an Enterprise Policy, you are only able to delete an existing Send.")
}
Ok(())
}
/// Enforces the `DisableHideEmail` option of the `Send Options` policy.
/// A non-owner/admin user belonging to an org with this option enabled isn't
/// allowed to hide their email address from the recipient of a Bitwarden Send,
/// but is allowed to remove this option from an existing Send.
///
/// Ref: https://bitwarden.com/help/article/policies/#send-options
async fn enforce_disable_hide_email_policy(data: &SendData, headers: &Headers, conn: &DbConn) -> EmptyResult {
let user_id = &headers.user.uuid;
let hide_email = data.hide_email.unwrap_or(false);
if hide_email && OrgPolicy::is_hide_email_disabled(user_id, conn).await {
err!(
"Due to an Enterprise Policy, you are not allowed to hide your email address \
from recipients when creating or editing a Send."
)
}
Ok(())
OrgPolicy::check_send(&headers.user.uuid, send, conn).await
}
fn create_send(data: SendData, user_id: UserId) -> ApiResult<Send> {
@ -192,16 +168,14 @@ async fn get_send(send_id: SendId, headers: Headers, conn: DbConn) -> JsonResult
#[post("/sends", data = "<data>")]
async fn post_send(data: Json<SendData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult {
enforce_disable_send_policy(&headers, &conn).await?;
let data: SendData = data.into_inner();
enforce_disable_hide_email_policy(&data, &headers, &conn).await?;
if data.r#type == SendType::File as i32 {
err!("File sends should use /api/sends/file/v2")
}
let mut send = create_send(data, headers.user.uuid)?;
let mut send = create_send(data, headers.user.uuid.clone())?;
enforce_send_policy(&send, &headers, &conn).await?;
send.save(&conn).await?;
nt.send_send_update(
UpdateType::SyncSendCreate,
@ -223,16 +197,12 @@ struct UploadDataV2<'f> {
// Upstream: https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Api/Tools/Controllers/SendsController.cs#L165
#[post("/sends/file/v2", data = "<data>")]
async fn post_send_file_v2(data: Json<SendData>, headers: Headers, conn: DbConn) -> JsonResult {
enforce_disable_send_policy(&headers, &conn).await?;
let data = data.into_inner();
if data.r#type != SendType::File as i32 {
err!("Send content is not a file");
}
enforce_disable_hide_email_policy(&data, &headers, &conn).await?;
let file_length = if let Some(m) = &data.file_length {
m.into_i64()?
} else {
@ -263,7 +233,8 @@ async fn post_send_file_v2(data: Json<SendData>, headers: Headers, conn: DbConn)
err!("Send storage limit exceeded with this file");
}
let mut send = create_send(data, headers.user.uuid)?;
let mut send = create_send(data, headers.user.uuid.clone())?;
enforce_send_policy(&send, &headers, &conn).await?;
let file_id = crate::crypto::generate_send_file_id();
@ -303,8 +274,6 @@ async fn post_send_file_v2_data(
conn: DbConn,
nt: Notify<'_>,
) -> EmptyResult {
enforce_disable_send_policy(&headers, &conn).await?;
let data = data.into_inner();
let Some(send) = Send::find_by_uuid_and_user(&send_id, &headers.user.uuid, &conn).await else {
@ -314,6 +283,7 @@ async fn post_send_file_v2_data(
if send.atype != SendType::File as i32 {
err!("Send is not a file type send.");
}
enforce_send_policy(&send, &headers, &conn).await?;
let Ok(send_data) = serde_json::from_str::<SendFileData>(&send.data) else {
err!("Unable to decode send data as json.")
@ -465,10 +435,7 @@ async fn download_send(send_id: SendId, file_id: SendFileId, t: &str) -> Option<
#[put("/sends/<send_id>", data = "<data>")]
async fn put_send(send_id: SendId, data: Json<SendData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult {
enforce_disable_send_policy(&headers, &conn).await?;
let data: SendData = data.into_inner();
enforce_disable_hide_email_policy(&data, &headers, &conn).await?;
let Some(mut send) = Send::find_by_uuid_and_user(&send_id, &headers.user.uuid, &conn).await else {
err!("Send not found", "Send send_id is invalid or does not belong to user")
@ -534,6 +501,7 @@ async fn update_send_from_data(
send.set_password(Some(&password));
}
enforce_send_policy(send, headers, conn).await?;
send.save(conn).await?;
if ut != UpdateType::None {
nt.send_send_update(ut, send, &send.update_users_revision(conn).await, &headers.device, conn).await;
@ -562,13 +530,12 @@ async fn delete_send(send_id: SendId, headers: Headers, conn: DbConn, nt: Notify
#[put("/sends/<send_id>/remove-password")]
async fn put_remove_password(send_id: SendId, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult {
enforce_disable_send_policy(&headers, &conn).await?;
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")
};
send.set_password(None);
enforce_send_policy(&send, &headers, &conn).await?;
send.save(&conn).await?;
nt.send_send_update(
UpdateType::SyncSendUpdate,

2
src/config.rs

@ -1520,6 +1520,8 @@ pub const SUPPORTED_FEATURE_FLAGS: &[&str] = &[
"pm-34171-card-scanner",
// Platform Team
"pm-30529-webauthn-related-origins",
// Tools Team: read by the clients up to 2026.10 and by the Android and iOS apps
"pm-31885-send-controls",
// Vault Team
"pm-32009-new-item-types",
];

54
src/db/models/org_policy.rs

@ -17,7 +17,9 @@ use crate::{
use super::{Membership, MembershipId, MembershipStatus, MembershipType, OrganizationId, TwoFactor, UserId};
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
mod send_controls;
#[derive(Clone, Identifiable, Queryable, Insertable, AsChangeset)]
#[diesel(table_name = org_policies)]
#[diesel(primary_key(uuid))]
pub struct OrgPolicy {
@ -53,13 +55,13 @@ pub enum OrgPolicyType {
// AutoConfirm = 18, // Not supported (not implemented yet)
// BlockClaimedDomainAccountCreation = 19, // Not supported (Not AGPLv3 Licensed)
OrganizationUserNotification = 20,
// SendControls = 21, // Not supported yet
SendControls = 21,
FillAssist = 22,
}
// https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendOptionsPolicyData.cs#L5
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
#[derive(Default, Deserialize, Serialize)]
#[serde(default, rename_all = "camelCase")]
pub struct SendOptionsPolicyData {
#[serde(rename = "disableHideEmail", alias = "DisableHideEmail")]
pub disable_hide_email: bool,
@ -270,28 +272,6 @@ impl OrgPolicy {
.await
}
pub async fn find_confirmed_by_user_and_active_policy(
user_uuid: &UserId,
policy_type: OrgPolicyType,
conn: &DbConn,
) -> Vec<Self> {
conn.run(move |conn| {
org_policies::table
.inner_join(
users_organizations::table.on(users_organizations::org_uuid
.eq(org_policies::org_uuid)
.and(users_organizations::user_uuid.eq(user_uuid))),
)
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
.filter(org_policies::atype.eq(policy_type as i32))
.filter(org_policies::enabled.eq(true))
.select(org_policies::all_columns)
.load::<Self>(conn)
.expect("Error loading org_policy")
})
.await
}
/// Returns true if the user belongs to an org that has enabled the specified policy type,
/// and the user is not an owner or admin of that org. This is only useful for checking
/// applicability of policy types that have these particular semantics.
@ -375,28 +355,6 @@ impl OrgPolicy {
false
}
/// Returns true if the user belongs to an org that has enabled the `DisableHideEmail`
/// option of the `Send Options` policy, and the user is not an owner or admin of that org.
pub async fn is_hide_email_disabled(user_uuid: &UserId, conn: &DbConn) -> bool {
for policy in
OrgPolicy::find_confirmed_by_user_and_active_policy(user_uuid, OrgPolicyType::SendOptions, conn).await
{
if let Some(user) = Membership::find_confirmed_by_user_and_org(user_uuid, &policy.org_uuid, conn).await
&& user.atype < MembershipType::Admin
{
match serde_json::from_str::<SendOptionsPolicyData>(&policy.data) {
Ok(opts) => {
if opts.disable_hide_email {
return true;
}
}
_ => error!("Failed to deserialize SendOptionsPolicyData: {}", policy.data),
}
}
}
false
}
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
&& let Some(policy) = OrgPolicy::find_by_org_and_type(&member.org_uuid, policy_type, conn).await

289
src/db/models/org_policy/send_controls.rs

@ -0,0 +1,289 @@
//! The Send controls policy (21) absorbs the legacy `Disable Send` (6) and `Send Options` (7) policies.
//! Like upstream, the three are kept in sync in both directions, so clients that only know the legacy
//! policies keep enforcing the same rules, and Sends are checked against all of them.
//!
//! https://github.com/bitwarden/server/tree/04a4d7548b07d18ab7327ddaae81a05fd893a820/src/Core/AdminConsole/OrganizationFeatures/Policies
use chrono::TimeDelta;
use diesel::prelude::*;
use num_traits::FromPrimitive;
use serde::Deserialize;
use serde_json::Value;
use super::{OrgPolicy, OrgPolicyType, SendOptionsPolicyData};
use crate::{
api::{ApiResult, EmptyResult},
db::{
DbConn,
models::{MembershipStatus, MembershipType, OrganizationId, Send, SendType, UserId},
schema::{org_policies, users_organizations},
},
};
const SEND_POLICIES: [i32; 3] =
[OrgPolicyType::DisableSend as i32, OrgPolicyType::SendOptions as i32, OrgPolicyType::SendControls as i32];
// SendWhoCanAccessType. Specific people requires Sends with email verification, which are not supported.
const ACCESS_ANY: i32 = 0;
const ACCESS_PASSWORD: i32 = 1;
const ACCESS_SPECIFIC_PEOPLE: i32 = 2;
// https://github.com/bitwarden/server/blob/04a4d7548b07d18ab7327ddaae81a05fd893a820/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendControlsPolicyData.cs
#[derive(Default, Deserialize, Serialize)]
#[serde(default, rename_all = "camelCase")]
struct SendControlsPolicyData {
#[serde(alias = "DisableSend")]
disable_send: bool,
#[serde(alias = "DisableHideEmail")]
disable_hide_email: bool,
#[serde(alias = "WhoCanAccess")]
who_can_access: Option<i32>,
#[serde(alias = "AllowedDomains")]
allowed_domains: Option<String>,
#[serde(alias = "DeletionHours")]
deletion_hours: Option<i32>,
#[serde(alias = "AllowedSendTypes")]
allowed_send_types: Option<Vec<i32>>,
}
impl SendControlsPolicyData {
/// Validates the data an organization saves. Requiring email verification, which allowed domains
/// depend on, would leave members unable to create any Send, so it is refused.
fn parse(data: Option<&Value>) -> ApiResult<Self> {
let data = match Option::<Self>::deserialize(data.unwrap_or(&Value::Null)) {
Ok(data) => data.unwrap_or_default(),
Err(e) => err!(format!("Invalid Send controls policy data: {e}")),
};
match data.who_can_access {
None | Some(ACCESS_ANY | ACCESS_PASSWORD) => (),
Some(ACCESS_SPECIFIC_PEOPLE) => {
err!("Sends with email verification are not supported, so that access type cannot be required")
}
Some(_) => err!("Invalid Send controls access type"),
}
if data.allowed_domains.is_some() {
err!("Allowed domains require Sends with email verification, which are not supported")
}
if data.deletion_hours.is_some_and(|hours| hours < 1) {
err!("The maximum lifetime of a Send has to be at least one hour")
}
// Current clients may also list Item Sends (2), which can not be created here.
if let Some(types) = &data.allowed_send_types
&& (types.iter().any(|t| !(0..=2).contains(t))
|| !types.iter().any(|t| *t == SendType::Text as i32 || *t == SendType::File as i32))
{
err!("The allowed Send types have to include Text or File")
}
Ok(data)
}
/// Every organization's restrictions apply, so a less strict organization never relaxes a stricter
/// one, independent of the order the policies are loaded in.
fn merge(&mut self, other: Self) {
self.disable_send |= other.disable_send;
self.disable_hide_email |= other.disable_hide_email;
self.who_can_access = self.who_can_access.max(other.who_can_access);
self.deletion_hours = match (self.deletion_hours, other.deletion_hours) {
(Some(a), Some(b)) => Some(a.min(b)),
(a, b) => a.or(b),
};
self.allowed_send_types = match (self.allowed_send_types.take(), other.allowed_send_types) {
(Some(a), Some(b)) => Some(a.into_iter().filter(|t| b.contains(t)).collect()),
(a, b) => a.or(b),
};
}
/// Checks the Send as it would be saved, like upstream's `SendValidationService`.
fn check_send(&self, send: &Send) -> EmptyResult {
if self.disable_send {
err!("Due to an Enterprise Policy, you are only able to delete an existing Send.")
}
if self.disable_hide_email && send.hide_email.unwrap_or(false) {
err!(
"Due to an Enterprise Policy, you are not allowed to hide your email address \
from recipients when creating or editing a Send."
)
}
if self.who_can_access == Some(ACCESS_PASSWORD) && send.password_hash.is_none() {
err!("Due to an Enterprise policy your Sends must be protected by password")
}
if let Some(types) = &self.allowed_send_types
&& !types.contains(&send.atype)
{
let names: Vec<&str> = types
.iter()
.map(|t| match *t {
0 => "Text",
1 => "File",
_ => "Unknown",
})
.collect();
err!(format!("Due to an Enterprise policy your Sends must be of the following types: {}", names.join(", ")))
}
// Upstream allows for up to a minute of skew between the deletion and the creation date.
if let Some(hours) = self.deletion_hours
&& send.deletion_date - send.creation_date - TimeDelta::minutes(1) > TimeDelta::hours(i64::from(hours))
{
let (duration, unit) = if hours >= 24 {
(hours / 24, "day")
} else {
(hours, "hour")
};
let plural = if duration > 1 {
"s"
} else {
""
};
err!(format!(
"Due to an Enterprise policy your Sends must have deletion dates no more than {duration} {unit}{plural} from their creation dates"
))
}
Ok(())
}
}
/// Whether an enabled `Send Options` policy disables hiding the email. Unreadable data keeps the option on.
fn hide_email_disabled(policy: &OrgPolicy) -> bool {
policy.enabled
&& serde_json::from_str::<Option<SendOptionsPolicyData>>(&policy.data).map_or_else(
|_| {
error!("Failed to deserialize SendOptionsPolicyData: {}", policy.data);
true
},
|data| data.is_some_and(|d| d.disable_hide_email),
)
}
/// The options of the legacy policies, as Send controls holds them. A disabled `Send Options` policy
/// counts as off, so a later save of Send controls does not switch its stored option back on.
fn legacy_send_options(policies: &[OrgPolicy]) -> (bool, bool) {
let disable_send = policies.iter().any(|p| p.has_type(OrgPolicyType::DisableSend) && p.enabled);
let disable_hide_email = policies.iter().any(|p| p.has_type(OrgPolicyType::SendOptions) && hide_email_disabled(p));
(disable_send, disable_hide_email)
}
/// Mirrors a saved Send policy into the others, like upstream's `SendControlsSyncPolicyEvent`,
/// `DisableSendSyncPolicyEvent` and `SendOptionsSyncPolicyEvent`, and returns the policies to save.
/// `policies` are the organization's policies, including the saved one.
fn mirror_send_policies(saved: &OrgPolicy, policies: &[OrgPolicy]) -> ApiResult<Vec<OrgPolicy>> {
let find_or_new = |policy_type| {
policies
.iter()
.find(|p| p.has_type(policy_type))
.cloned()
.unwrap_or_else(|| OrgPolicy::new(saved.org_uuid.clone(), policy_type, false, String::from("null")))
};
if saved.has_type(OrgPolicyType::SendControls) {
let data = saved.send_controls_data();
let mut disable_send = find_or_new(OrgPolicyType::DisableSend);
disable_send.enabled = saved.enabled && data.disable_send;
let mut send_options = find_or_new(OrgPolicyType::SendOptions);
send_options.enabled = saved.enabled && data.disable_hide_email;
send_options.data = serde_json::to_string(&SendOptionsPolicyData {
disable_hide_email: data.disable_hide_email,
})?;
return Ok(vec![disable_send, send_options]);
}
let mut controls = find_or_new(OrgPolicyType::SendControls);
let mut data = controls.send_controls_data();
(data.disable_send, data.disable_hide_email) = legacy_send_options(policies);
// Turning a legacy policy off must not switch off the restrictions only Send controls has.
controls.enabled |= data.disable_send || data.disable_hide_email;
controls.data = serde_json::to_string(&data)?;
Ok(vec![controls])
}
impl OrgPolicy {
/// Stored data was validated when saved; data that can not be read blocks Sends instead of allowing them.
fn send_controls_data(&self) -> SendControlsPolicyData {
let data = serde_json::from_str::<Value>(&self.data).ok();
data.and_then(|data| SendControlsPolicyData::parse(Some(&data)).ok()).unwrap_or_else(|| {
error!("Invalid Send controls policy data, blocking Sends: {}", self.data);
SendControlsPolicyData {
disable_send: true,
..Default::default()
}
})
}
/// Validates the data of a Send policy before it is saved and returns the data to store.
pub fn send_policy_data(policy_type: OrgPolicyType, data: Option<Value>) -> ApiResult<Option<Value>> {
match policy_type {
OrgPolicyType::SendControls => {
Ok(Some(serde_json::to_value(SendControlsPolicyData::parse(data.as_ref())?)?))
}
OrgPolicyType::SendOptions => {
if let Some(value) = &data
&& let Err(e) = Option::<SendOptionsPolicyData>::deserialize(value)
{
err!(format!("Invalid Send options policy data: {e}"))
}
Ok(data)
}
_ => Ok(data),
}
}
/// Keeps Send controls and the legacy Send policies in sync after one of them was saved.
pub async fn sync_send_policies(saved: &Self, conn: &DbConn) -> EmptyResult {
if !SEND_POLICIES.contains(&saved.atype) {
return Ok(());
}
for mut policy in mirror_send_policies(saved, &Self::find_by_org(&saved.org_uuid, conn).await)? {
policy.save(conn).await?;
}
Ok(())
}
/// Upstream's `PolicyQuery`: until Send controls is saved, it is derived from the legacy policies, so the
/// current policy editor shows what is enforced instead of turning it off on the next save.
pub fn synthesize_send_controls(org_id: &OrganizationId, policies: &[Self]) -> Self {
let (disable_send, disable_hide_email) = legacy_send_options(policies);
let data = SendControlsPolicyData {
disable_send,
disable_hide_email,
..Default::default()
};
Self::new(
org_id.clone(),
OrgPolicyType::SendControls,
disable_send || disable_hide_email,
json!(data).to_string(),
)
}
/// Enforces the Send policies of every organization in which the user is an accepted or confirmed
/// member below Admin on the Send as it would be saved. Deleting a Send stays possible.
pub async fn check_send(user_id: &UserId, send: &Send, conn: &DbConn) -> EmptyResult {
let policies = conn
.run(move |conn| {
org_policies::table
.inner_join(users_organizations::table.on(users_organizations::org_uuid.eq(org_policies::org_uuid)))
.filter(users_organizations::user_uuid.eq(user_id))
.filter(
users_organizations::status
.eq_any([MembershipStatus::Accepted as i32, MembershipStatus::Confirmed as i32]),
)
// Owners and admins are exempt. The numbers of MembershipType are not its order.
.filter(users_organizations::atype.ne(MembershipType::Owner as i32))
.filter(users_organizations::atype.ne(MembershipType::Admin as i32))
.filter(org_policies::enabled.eq(true))
.filter(org_policies::atype.eq_any(SEND_POLICIES))
.select(org_policies::all_columns)
.load::<Self>(conn)
})
.await?;
let mut controls = SendControlsPolicyData::default();
for policy in policies {
match OrgPolicyType::from_i32(policy.atype) {
Some(OrgPolicyType::DisableSend) => controls.disable_send = true,
Some(OrgPolicyType::SendOptions) => controls.disable_hide_email |= hide_email_disabled(&policy),
_ => controls.merge(policy.send_controls_data()),
}
}
controls.check_send(send)
}
}
Loading…
Cancel
Save