committed by
GitHub
6 changed files with 324 additions and 96 deletions
@ -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…
Reference in new issue