Add support for send v2 API endpoints

This PR adds support for the Send v2 API.
It should prevent 404 errors which could cause some issues with some
configurations on some reverse proxies.

In the long run, we can probably remove the old file upload API, but for
now lets leave it there, since Bitwarden also still has this endpoint in
the code.

Might fixes #2753
This commit is contained in:
BlackDex 2022-09-22 19:40:04 +02:00 committed by Daniel García
parent d6e0d4cbbd
commit 9eabcd5cae
No known key found for this signature in database
GPG key ID: FC8A7D14C3CD543A
2 changed files with 127 additions and 13 deletions

View file

@ -947,10 +947,12 @@ async fn save_attachment(
let mut data = data.into_inner(); let mut data = data.into_inner();
// There seems to be a bug somewhere regarding uploading attachments using the Android Client (Maybe iOS too?) // There is a bug regarding uploading attachments/sends using the Mobile clients
// See: https://github.com/dani-garcia/vaultwarden/issues/2644 // See: https://github.com/dani-garcia/vaultwarden/issues/2644 && https://github.com/bitwarden/mobile/issues/2018
// Since all other clients seem to match TempFile::File and not TempFile::Buffered lets catch this and return an error for now. // This has been fixed via a PR: https://github.com/bitwarden/mobile/pull/2031, but hasn't landed in a new release yet.
// We need to figure out how to solve this, but for now it's better to not accept these attachments since they will be broken. // On the vaultwarden side this is temporarily fixed by using a custom multer library
// See: https://github.com/dani-garcia/vaultwarden/pull/2675
// In any case we will match TempFile::File and not TempFile::Buffered, since Buffered will alter the contents.
if let TempFile::Buffered { if let TempFile::Buffered {
content: _, content: _,
} = &data.data } = &data.data

View file

@ -17,6 +17,9 @@ use crate::{
const SEND_INACCESSIBLE_MSG: &str = "Send does not exist or is no longer available"; const SEND_INACCESSIBLE_MSG: &str = "Send does not exist or is no longer available";
// The max file size allowed by Bitwarden clients and add an extra 5% to avoid issues
const SIZE_525_MB: u64 = 550_502_400;
pub fn routes() -> Vec<rocket::Route> { pub fn routes() -> Vec<rocket::Route> {
routes![ routes![
get_sends, get_sends,
@ -28,7 +31,9 @@ pub fn routes() -> Vec<rocket::Route> {
put_send, put_send,
delete_send, delete_send,
put_remove_password, put_remove_password,
download_send download_send,
post_send_file_v2,
post_send_file_v2_data
] ]
} }
@ -58,6 +63,7 @@ struct SendData {
Notes: Option<String>, Notes: Option<String>,
Text: Option<Value>, Text: Option<Value>,
File: Option<Value>, File: Option<Value>,
FileLength: Option<NumberOrString>,
} }
/// Enforces the `Disable Send` policy. A non-owner/admin user belonging to /// Enforces the `Disable Send` policy. A non-owner/admin user belonging to
@ -185,6 +191,14 @@ struct UploadData<'f> {
data: TempFile<'f>, data: TempFile<'f>,
} }
#[derive(FromForm)]
struct UploadDataV2<'f> {
data: TempFile<'f>,
}
// @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads (v2).
// This method still exists to support older clients, probably need to remove it sometime.
// Upstream: https://github.com/bitwarden/server/blob/d0c793c95181dfb1b447eb450f85ba0bfd7ef643/src/Api/Controllers/SendsController.cs#L164-L167
#[post("/sends/file", format = "multipart/form-data", data = "<data>")] #[post("/sends/file", format = "multipart/form-data", data = "<data>")]
async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult { async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult {
enforce_disable_send_policy(&headers, &conn).await?; enforce_disable_send_policy(&headers, &conn).await?;
@ -197,9 +211,6 @@ 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?;
// Get the file length and add an extra 5% to avoid issues
const SIZE_525_MB: u64 = 550_502_400;
let size_limit = match CONFIG.user_attachment_limit() { let size_limit = match CONFIG.user_attachment_limit() {
Some(0) => err!("File uploads are disabled"), Some(0) => err!("File uploads are disabled"),
Some(limit_kb) => { Some(limit_kb) => {
@ -217,10 +228,12 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, conn: DbCo
err!("Send content is not a file"); err!("Send content is not a file");
} }
// There seems to be a bug somewhere regarding uploading attachments using the Android Client (Maybe iOS too?) // There is a bug regarding uploading attachments/sends using the Mobile clients
// See: https://github.com/dani-garcia/vaultwarden/issues/2644 // See: https://github.com/dani-garcia/vaultwarden/issues/2644 && https://github.com/bitwarden/mobile/issues/2018
// Since all other clients seem to match TempFile::File and not TempFile::Buffered lets catch this and return an error for now. // This has been fixed via a PR: https://github.com/bitwarden/mobile/pull/2031, but hasn't landed in a new release yet.
// We need to figure out how to solve this, but for now it's better to not accept these attachments since they will be broken. // On the vaultwarden side this is temporarily fixed by using a custom multer library
// See: https://github.com/dani-garcia/vaultwarden/pull/2675
// In any case we will match TempFile::File and not TempFile::Buffered, since Buffered will alter the contents.
if let TempFile::Buffered { if let TempFile::Buffered {
content: _, content: _,
} = &data } = &data
@ -252,11 +265,110 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, conn: DbCo
// Save the changes in the database // Save the changes in the database
send.save(&conn).await?; send.save(&conn).await?;
nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&conn).await).await; nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&conn).await).await;
Ok(Json(send.to_json())) Ok(Json(send.to_json()))
} }
// Upstream: https://github.com/bitwarden/server/blob/d0c793c95181dfb1b447eb450f85ba0bfd7ef643/src/Api/Controllers/SendsController.cs#L190
#[post("/sends/file/v2", data = "<data>")]
async fn post_send_file_v2(data: JsonUpcase<SendData>, headers: Headers, conn: DbConn) -> JsonResult {
enforce_disable_send_policy(&headers, &conn).await?;
let data = data.into_inner().data;
if data.Type != SendType::File as i32 {
err!("Send content is not a file");
}
enforce_disable_hide_email_policy(&data, &headers, &conn).await?;
let file_length = match &data.FileLength {
Some(m) => Some(m.into_i32()?),
_ => None,
};
let size_limit = match CONFIG.user_attachment_limit() {
Some(0) => err!("File uploads are disabled"),
Some(limit_kb) => {
let left = (limit_kb * 1024) - Attachment::size_by_user(&headers.user.uuid, &conn).await;
if left <= 0 {
err!("Attachment storage limit reached! Delete some attachments to free up space")
}
std::cmp::Ord::max(left as u64, SIZE_525_MB)
}
None => SIZE_525_MB,
};
if file_length.is_some() && file_length.unwrap() as u64 > size_limit {
err!("Attachment storage limit exceeded with this file");
}
let mut send = create_send(data, headers.user.uuid)?;
let file_id = crate::crypto::generate_send_id();
let mut data_value: Value = serde_json::from_str(&send.data)?;
if let Some(o) = data_value.as_object_mut() {
o.insert(String::from("Id"), Value::String(file_id.clone()));
o.insert(String::from("Size"), Value::Number(file_length.unwrap().into()));
o.insert(String::from("SizeName"), Value::String(crate::util::get_display_size(file_length.unwrap())));
}
send.data = serde_json::to_string(&data_value)?;
send.save(&conn).await?;
Ok(Json(json!({
"fileUploadType": 0, // 0 == Direct | 1 == Azure
"object": "send-fileUpload",
"url": format!("/sends/{}/file/{}", send.uuid, file_id),
"sendResponse": send.to_json()
})))
}
// https://github.com/bitwarden/server/blob/d0c793c95181dfb1b447eb450f85ba0bfd7ef643/src/Api/Controllers/SendsController.cs#L243
#[post("/sends/<send_uuid>/file/<file_id>", format = "multipart/form-data", data = "<data>")]
async fn post_send_file_v2_data(
send_uuid: String,
file_id: String,
data: Form<UploadDataV2<'_>>,
headers: Headers,
conn: DbConn,
nt: Notify<'_>,
) -> EmptyResult {
enforce_disable_send_policy(&headers, &conn).await?;
let mut data = data.into_inner();
// There is a bug regarding uploading attachments/sends using the Mobile clients
// See: https://github.com/dani-garcia/vaultwarden/issues/2644 && https://github.com/bitwarden/mobile/issues/2018
// This has been fixed via a PR: https://github.com/bitwarden/mobile/pull/2031, but hasn't landed in a new release yet.
// On the vaultwarden side this is temporarily fixed by using a custom multer library
// See: https://github.com/dani-garcia/vaultwarden/pull/2675
// In any case we will match TempFile::File and not TempFile::Buffered, since Buffered will alter the contents.
if let TempFile::Buffered {
content: _,
} = &data.data
{
err!("Error reading attachment data. Please try an other client.");
}
if let Some(send) = Send::find_by_uuid(&send_uuid, &conn).await {
let folder_path = tokio::fs::canonicalize(&CONFIG.sends_folder()).await?.join(&send_uuid);
let file_path = folder_path.join(&file_id);
tokio::fs::create_dir_all(&folder_path).await?;
if let Err(_err) = data.data.persist_to(&file_path).await {
data.data.move_copy_to(file_path).await?
}
nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&conn).await).await;
} else {
err!("Send not found. Unable to save the file.");
}
Ok(())
}
#[derive(Deserialize)] #[derive(Deserialize)]
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub struct SendAccessData { pub struct SendAccessData {