Merge pull request #1689 from jjlin/hide-email
Add support for hiding the sender's email address in Bitwarden Sends
This commit is contained in:
commit
8e6c6a1dc4
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE sends
|
||||||
|
ADD COLUMN hide_email BOOLEAN;
|
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE sends
|
||||||
|
ADD COLUMN hide_email BOOLEAN;
|
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE sends
|
||||||
|
ADD COLUMN hide_email BOOLEAN;
|
|
@ -38,6 +38,7 @@ pub struct SendData {
|
||||||
pub ExpirationDate: Option<DateTime<Utc>>,
|
pub ExpirationDate: Option<DateTime<Utc>>,
|
||||||
pub DeletionDate: DateTime<Utc>,
|
pub DeletionDate: DateTime<Utc>,
|
||||||
pub Disabled: bool,
|
pub Disabled: bool,
|
||||||
|
pub HideEmail: Option<bool>,
|
||||||
|
|
||||||
// Data field
|
// Data field
|
||||||
pub Name: String,
|
pub Name: String,
|
||||||
|
@ -63,6 +64,24 @@ fn enforce_disable_send_policy(headers: &Headers, conn: &DbConn) -> EmptyResult
|
||||||
Ok(())
|
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
|
||||||
|
fn enforce_disable_hide_email_policy(data: &SendData, headers: &Headers, conn: &DbConn) -> EmptyResult {
|
||||||
|
let user_uuid = &headers.user.uuid;
|
||||||
|
let hide_email = data.HideEmail.unwrap_or(false);
|
||||||
|
if hide_email && OrgPolicy::is_hide_email_disabled(user_uuid, conn) {
|
||||||
|
err!(
|
||||||
|
"Due to an Enterprise Policy, you are not allowed to hide your email address \
|
||||||
|
from recipients when creating or editing a Send."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn create_send(data: SendData, user_uuid: String) -> ApiResult<Send> {
|
fn create_send(data: SendData, user_uuid: String) -> ApiResult<Send> {
|
||||||
let data_val = if data.Type == SendType::Text as i32 {
|
let data_val = if data.Type == SendType::Text as i32 {
|
||||||
data.Text
|
data.Text
|
||||||
|
@ -91,6 +110,7 @@ fn create_send(data: SendData, user_uuid: String) -> ApiResult<Send> {
|
||||||
send.max_access_count = data.MaxAccessCount;
|
send.max_access_count = data.MaxAccessCount;
|
||||||
send.expiration_date = data.ExpirationDate.map(|d| d.naive_utc());
|
send.expiration_date = data.ExpirationDate.map(|d| d.naive_utc());
|
||||||
send.disabled = data.Disabled;
|
send.disabled = data.Disabled;
|
||||||
|
send.hide_email = data.HideEmail;
|
||||||
send.atype = data.Type;
|
send.atype = data.Type;
|
||||||
|
|
||||||
send.set_password(data.Password.as_deref());
|
send.set_password(data.Password.as_deref());
|
||||||
|
@ -103,6 +123,7 @@ fn post_send(data: JsonUpcase<SendData>, headers: Headers, conn: DbConn, nt: Not
|
||||||
enforce_disable_send_policy(&headers, &conn)?;
|
enforce_disable_send_policy(&headers, &conn)?;
|
||||||
|
|
||||||
let data: SendData = data.into_inner().data;
|
let data: SendData = data.into_inner().data;
|
||||||
|
enforce_disable_hide_email_policy(&data, &headers, &conn)?;
|
||||||
|
|
||||||
if data.Type == SendType::File as i32 {
|
if data.Type == SendType::File as i32 {
|
||||||
err!("File sends should use /api/sends/file")
|
err!("File sends should use /api/sends/file")
|
||||||
|
@ -133,6 +154,7 @@ fn post_send_file(data: Data, content_type: &ContentType, headers: Headers, conn
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
model_entry.data.read_to_string(&mut buf)?;
|
model_entry.data.read_to_string(&mut buf)?;
|
||||||
let data = serde_json::from_str::<crate::util::UpCase<SendData>>(&buf)?;
|
let data = serde_json::from_str::<crate::util::UpCase<SendData>>(&buf)?;
|
||||||
|
enforce_disable_hide_email_policy(&data.data, &headers, &conn)?;
|
||||||
|
|
||||||
// Get the file length and add an extra 10% to avoid issues
|
// Get the file length and add an extra 10% to avoid issues
|
||||||
const SIZE_110_MB: u64 = 115_343_360;
|
const SIZE_110_MB: u64 = 115_343_360;
|
||||||
|
@ -246,7 +268,7 @@ fn post_access(access_id: String, data: JsonUpcase<SendAccessData>, conn: DbConn
|
||||||
|
|
||||||
send.save(&conn)?;
|
send.save(&conn)?;
|
||||||
|
|
||||||
Ok(Json(send.to_json_access()))
|
Ok(Json(send.to_json_access(&conn)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/sends/<send_id>/access/file/<file_id>", data = "<data>")]
|
#[post("/sends/<send_id>/access/file/<file_id>", data = "<data>")]
|
||||||
|
@ -306,6 +328,7 @@ fn put_send(id: String, data: JsonUpcase<SendData>, headers: Headers, conn: DbCo
|
||||||
enforce_disable_send_policy(&headers, &conn)?;
|
enforce_disable_send_policy(&headers, &conn)?;
|
||||||
|
|
||||||
let data: SendData = data.into_inner().data;
|
let data: SendData = data.into_inner().data;
|
||||||
|
enforce_disable_hide_email_policy(&data, &headers, &conn)?;
|
||||||
|
|
||||||
let mut send = match Send::find_by_uuid(&id, &conn) {
|
let mut send = match Send::find_by_uuid(&id, &conn) {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
|
@ -343,6 +366,7 @@ fn put_send(id: String, data: JsonUpcase<SendData>, headers: Headers, conn: DbCo
|
||||||
send.notes = data.Notes;
|
send.notes = data.Notes;
|
||||||
send.max_access_count = data.MaxAccessCount;
|
send.max_access_count = data.MaxAccessCount;
|
||||||
send.expiration_date = data.ExpirationDate.map(|d| d.naive_utc());
|
send.expiration_date = data.ExpirationDate.map(|d| d.naive_utc());
|
||||||
|
send.hide_email = data.HideEmail;
|
||||||
send.disabled = data.Disabled;
|
send.disabled = data.Disabled;
|
||||||
|
|
||||||
// Only change the value if it's present
|
// Only change the value if it's present
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
|
use serde::Deserialize;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use crate::api::EmptyResult;
|
use crate::api::EmptyResult;
|
||||||
use crate::db::DbConn;
|
use crate::db::DbConn;
|
||||||
use crate::error::MapResult;
|
use crate::error::MapResult;
|
||||||
|
use crate::util::UpCase;
|
||||||
|
|
||||||
use super::{Organization, UserOrgStatus, UserOrgType, UserOrganization};
|
use super::{Organization, UserOrgStatus, UserOrgType, UserOrganization};
|
||||||
|
|
||||||
|
@ -29,6 +31,14 @@ pub enum OrgPolicyType {
|
||||||
// RequireSso = 4, // Not currently supported.
|
// RequireSso = 4, // Not currently supported.
|
||||||
PersonalOwnership = 5,
|
PersonalOwnership = 5,
|
||||||
DisableSend = 6,
|
DisableSend = 6,
|
||||||
|
SendOptions = 7,
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/bitwarden/server/blob/master/src/Core/Models/Data/SendOptionsPolicyData.cs
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub struct SendOptionsPolicyData {
|
||||||
|
pub DisableHideEmail: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Local methods
|
/// Local methods
|
||||||
|
@ -188,6 +198,30 @@ impl OrgPolicy {
|
||||||
false
|
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 fn is_hide_email_disabled(user_uuid: &str, conn: &DbConn) -> bool {
|
||||||
|
// Returns confirmed users only.
|
||||||
|
for policy in OrgPolicy::find_by_user(user_uuid, conn) {
|
||||||
|
if policy.enabled && policy.has_type(OrgPolicyType::SendOptions) {
|
||||||
|
let org_uuid = &policy.org_uuid;
|
||||||
|
if let Some(user) = UserOrganization::find_by_user_and_org(user_uuid, org_uuid, conn) {
|
||||||
|
if user.atype < UserOrgType::Admin {
|
||||||
|
match serde_json::from_str::<UpCase<SendOptionsPolicyData>>(&policy.data) {
|
||||||
|
Ok(opts) => {
|
||||||
|
if opts.data.DisableHideEmail {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => error!("Failed to deserialize policy data: {}", policy.data),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
/*pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
|
/*pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
|
||||||
db_run! { conn: {
|
db_run! { conn: {
|
||||||
diesel::delete(twofactor::table.filter(twofactor::user_uuid.eq(user_uuid)))
|
diesel::delete(twofactor::table.filter(twofactor::user_uuid.eq(user_uuid)))
|
||||||
|
|
|
@ -36,6 +36,7 @@ db_object! {
|
||||||
pub deletion_date: NaiveDateTime,
|
pub deletion_date: NaiveDateTime,
|
||||||
|
|
||||||
pub disabled: bool,
|
pub disabled: bool,
|
||||||
|
pub hide_email: Option<bool>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +74,7 @@ impl Send {
|
||||||
deletion_date,
|
deletion_date,
|
||||||
|
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
hide_email: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,6 +103,22 @@ impl Send {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn creator_identifier(&self, conn: &DbConn) -> Option<String> {
|
||||||
|
if let Some(hide_email) = self.hide_email {
|
||||||
|
if hide_email {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(user_uuid) = &self.user_uuid {
|
||||||
|
if let Some(user) = User::find_by_uuid(user_uuid, conn) {
|
||||||
|
return Some(user.email);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
pub fn to_json(&self) -> Value {
|
pub fn to_json(&self) -> Value {
|
||||||
use crate::util::format_date;
|
use crate::util::format_date;
|
||||||
use data_encoding::BASE64URL_NOPAD;
|
use data_encoding::BASE64URL_NOPAD;
|
||||||
|
@ -123,6 +141,7 @@ impl Send {
|
||||||
"AccessCount": self.access_count,
|
"AccessCount": self.access_count,
|
||||||
"Password": self.password_hash.as_deref().map(|h| BASE64URL_NOPAD.encode(h)),
|
"Password": self.password_hash.as_deref().map(|h| BASE64URL_NOPAD.encode(h)),
|
||||||
"Disabled": self.disabled,
|
"Disabled": self.disabled,
|
||||||
|
"HideEmail": self.hide_email,
|
||||||
|
|
||||||
"RevisionDate": format_date(&self.revision_date),
|
"RevisionDate": format_date(&self.revision_date),
|
||||||
"ExpirationDate": self.expiration_date.as_ref().map(format_date),
|
"ExpirationDate": self.expiration_date.as_ref().map(format_date),
|
||||||
|
@ -131,7 +150,7 @@ impl Send {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_json_access(&self) -> Value {
|
pub fn to_json_access(&self, conn: &DbConn) -> Value {
|
||||||
use crate::util::format_date;
|
use crate::util::format_date;
|
||||||
|
|
||||||
let data: Value = serde_json::from_str(&self.data).unwrap_or_default();
|
let data: Value = serde_json::from_str(&self.data).unwrap_or_default();
|
||||||
|
@ -145,6 +164,7 @@ impl Send {
|
||||||
"File": if self.atype == SendType::File as i32 { Some(&data) } else { None },
|
"File": if self.atype == SendType::File as i32 { Some(&data) } else { None },
|
||||||
|
|
||||||
"ExpirationDate": self.expiration_date.as_ref().map(format_date),
|
"ExpirationDate": self.expiration_date.as_ref().map(format_date),
|
||||||
|
"CreatorIdentifier": self.creator_identifier(conn),
|
||||||
"Object": "send-access",
|
"Object": "send-access",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,6 +123,7 @@ table! {
|
||||||
expiration_date -> Nullable<Datetime>,
|
expiration_date -> Nullable<Datetime>,
|
||||||
deletion_date -> Datetime,
|
deletion_date -> Datetime,
|
||||||
disabled -> Bool,
|
disabled -> Bool,
|
||||||
|
hide_email -> Nullable<Bool>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -123,6 +123,7 @@ table! {
|
||||||
expiration_date -> Nullable<Timestamp>,
|
expiration_date -> Nullable<Timestamp>,
|
||||||
deletion_date -> Timestamp,
|
deletion_date -> Timestamp,
|
||||||
disabled -> Bool,
|
disabled -> Bool,
|
||||||
|
hide_email -> Nullable<Bool>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -123,6 +123,7 @@ table! {
|
||||||
expiration_date -> Nullable<Timestamp>,
|
expiration_date -> Nullable<Timestamp>,
|
||||||
deletion_date -> Timestamp,
|
deletion_date -> Timestamp,
|
||||||
disabled -> Bool,
|
disabled -> Bool,
|
||||||
|
hide_email -> Nullable<Bool>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue