2021-11-07 18:53:39 +01:00
|
|
|
use std::path::Path;
|
2021-03-14 23:35:55 +01:00
|
|
|
|
|
|
|
use chrono::{DateTime, Duration, Utc};
|
2021-11-07 18:53:39 +01:00
|
|
|
use rocket::form::Form;
|
|
|
|
use rocket::fs::NamedFile;
|
|
|
|
use rocket::fs::TempFile;
|
|
|
|
use rocket::serde::json::Json;
|
2021-03-14 23:35:55 +01:00
|
|
|
use serde_json::Value;
|
|
|
|
|
|
|
|
use crate::{
|
2021-12-31 15:59:58 +01:00
|
|
|
api::{ApiResult, EmptyResult, JsonResult, JsonUpcase, Notify, NumberOrString, UpdateType},
|
2022-02-13 13:06:52 +01:00
|
|
|
auth::{ClientIp, Headers, Host},
|
2022-07-09 04:49:51 +02:00
|
|
|
db::{models::*, DbConn, DbPool},
|
2022-07-09 04:33:27 +02:00
|
|
|
util::SafeString,
|
2022-07-09 04:49:51 +02:00
|
|
|
CONFIG,
|
2021-03-14 23:35:55 +01:00
|
|
|
};
|
|
|
|
|
2021-03-25 12:40:32 +01:00
|
|
|
const SEND_INACCESSIBLE_MSG: &str = "Send does not exist or is no longer available";
|
|
|
|
|
2022-09-22 19:40:04 +02:00
|
|
|
// The max file size allowed by Bitwarden clients and add an extra 5% to avoid issues
|
|
|
|
const SIZE_525_MB: u64 = 550_502_400;
|
|
|
|
|
2021-03-14 23:35:55 +01:00
|
|
|
pub fn routes() -> Vec<rocket::Route> {
|
2021-06-25 20:33:51 +02:00
|
|
|
routes![
|
2021-08-03 17:33:59 +02:00
|
|
|
get_sends,
|
|
|
|
get_send,
|
2021-06-25 20:33:51 +02:00
|
|
|
post_send,
|
|
|
|
post_send_file,
|
|
|
|
post_access,
|
|
|
|
post_access_file,
|
|
|
|
put_send,
|
|
|
|
delete_send,
|
|
|
|
put_remove_password,
|
2022-09-22 19:40:04 +02:00
|
|
|
download_send,
|
|
|
|
post_send_file_v2,
|
|
|
|
post_send_file_v2_data
|
2021-06-25 20:33:51 +02:00
|
|
|
]
|
2021-03-14 23:35:55 +01:00
|
|
|
}
|
|
|
|
|
2021-11-07 18:53:39 +01:00
|
|
|
pub async fn purge_sends(pool: DbPool) {
|
2021-04-03 05:16:49 +02:00
|
|
|
debug!("Purging sends");
|
2022-05-20 23:39:47 +02:00
|
|
|
if let Ok(mut conn) = pool.get().await {
|
|
|
|
Send::purge(&mut conn).await;
|
2021-04-03 05:16:49 +02:00
|
|
|
} else {
|
|
|
|
error!("Failed to get DB connection while purging sends")
|
|
|
|
}
|
2021-03-22 19:57:35 +01:00
|
|
|
}
|
|
|
|
|
2021-03-14 23:35:55 +01:00
|
|
|
#[derive(Deserialize)]
|
|
|
|
#[allow(non_snake_case)]
|
2021-12-31 15:59:58 +01:00
|
|
|
struct SendData {
|
|
|
|
Type: i32,
|
|
|
|
Key: String,
|
|
|
|
Password: Option<String>,
|
|
|
|
MaxAccessCount: Option<NumberOrString>,
|
|
|
|
ExpirationDate: Option<DateTime<Utc>>,
|
|
|
|
DeletionDate: DateTime<Utc>,
|
|
|
|
Disabled: bool,
|
|
|
|
HideEmail: Option<bool>,
|
2021-03-14 23:35:55 +01:00
|
|
|
|
|
|
|
// Data field
|
2021-12-31 15:59:58 +01:00
|
|
|
Name: String,
|
|
|
|
Notes: Option<String>,
|
|
|
|
Text: Option<Value>,
|
|
|
|
File: Option<Value>,
|
2022-09-22 19:40:04 +02:00
|
|
|
FileLength: Option<NumberOrString>,
|
2021-03-14 23:35:55 +01:00
|
|
|
}
|
|
|
|
|
2021-03-16 10:07:45 +01:00
|
|
|
/// 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
|
2021-05-12 05:07:32 +02:00
|
|
|
///
|
|
|
|
/// There is also a Vaultwarden-specific `sends_allowed` config setting that
|
|
|
|
/// controls this policy globally.
|
2022-05-20 23:39:47 +02:00
|
|
|
async fn enforce_disable_send_policy(headers: &Headers, conn: &mut DbConn) -> EmptyResult {
|
2021-03-16 10:07:45 +01:00
|
|
|
let user_uuid = &headers.user.uuid;
|
2022-08-20 16:42:36 +02:00
|
|
|
if !CONFIG.sends_allowed()
|
|
|
|
|| OrgPolicy::is_applicable_to_user(user_uuid, OrgPolicyType::DisableSend, None, conn).await
|
|
|
|
{
|
2021-03-16 10:07:45 +01:00
|
|
|
err!("Due to an Enterprise Policy, you are only able to delete an existing Send.")
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-05-12 09:54:28 +02:00
|
|
|
/// 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
|
2022-05-20 23:39:47 +02:00
|
|
|
async fn enforce_disable_hide_email_policy(data: &SendData, headers: &Headers, conn: &mut DbConn) -> EmptyResult {
|
2021-05-12 09:54:28 +02:00
|
|
|
let user_uuid = &headers.user.uuid;
|
|
|
|
let hide_email = data.HideEmail.unwrap_or(false);
|
2021-11-16 17:07:55 +01:00
|
|
|
if hide_email && OrgPolicy::is_hide_email_disabled(user_uuid, conn).await {
|
2021-05-12 09:54:28 +02:00
|
|
|
err!(
|
|
|
|
"Due to an Enterprise Policy, you are not allowed to hide your email address \
|
|
|
|
from recipients when creating or editing a Send."
|
|
|
|
)
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2022-07-10 16:39:38 +02:00
|
|
|
fn create_send(data: SendData, user_uuid: String) -> ApiResult<Send> {
|
2021-03-14 23:35:55 +01:00
|
|
|
let data_val = if data.Type == SendType::Text as i32 {
|
|
|
|
data.Text
|
|
|
|
} else if data.Type == SendType::File as i32 {
|
|
|
|
data.File
|
|
|
|
} else {
|
|
|
|
err!("Invalid Send type")
|
|
|
|
};
|
|
|
|
|
|
|
|
let data_str = if let Some(mut d) = data_val {
|
|
|
|
d.as_object_mut().and_then(|o| o.remove("Response"));
|
|
|
|
serde_json::to_string(&d)?
|
|
|
|
} else {
|
|
|
|
err!("Send data not provided");
|
|
|
|
};
|
|
|
|
|
|
|
|
if data.DeletionDate > Utc::now() + Duration::days(31) {
|
|
|
|
err!(
|
|
|
|
"You cannot have a Send with a deletion date that far into the future. Adjust the Deletion Date to a value less than 31 days from now and try again."
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-07-10 16:39:38 +02:00
|
|
|
let mut send = Send::new(data.Type, data.Name, data_str, data.Key, data.DeletionDate.naive_utc());
|
2021-03-14 23:35:55 +01:00
|
|
|
send.user_uuid = Some(user_uuid);
|
|
|
|
send.notes = data.Notes;
|
2021-12-31 15:59:58 +01:00
|
|
|
send.max_access_count = match data.MaxAccessCount {
|
|
|
|
Some(m) => Some(m.into_i32()?),
|
|
|
|
_ => None,
|
|
|
|
};
|
2021-03-14 23:35:55 +01:00
|
|
|
send.expiration_date = data.ExpirationDate.map(|d| d.naive_utc());
|
|
|
|
send.disabled = data.Disabled;
|
2021-05-12 07:51:12 +02:00
|
|
|
send.hide_email = data.HideEmail;
|
2021-03-14 23:35:55 +01:00
|
|
|
send.atype = data.Type;
|
|
|
|
|
|
|
|
send.set_password(data.Password.as_deref());
|
|
|
|
|
|
|
|
Ok(send)
|
|
|
|
}
|
|
|
|
|
2021-08-03 17:33:59 +02:00
|
|
|
#[get("/sends")]
|
2022-05-20 23:39:47 +02:00
|
|
|
async fn get_sends(headers: Headers, mut conn: DbConn) -> Json<Value> {
|
|
|
|
let sends = Send::find_by_user(&headers.user.uuid, &mut conn);
|
2021-11-16 17:07:55 +01:00
|
|
|
let sends_json: Vec<Value> = sends.await.iter().map(|s| s.to_json()).collect();
|
2021-08-03 17:33:59 +02:00
|
|
|
|
|
|
|
Json(json!({
|
|
|
|
"Data": sends_json,
|
|
|
|
"Object": "list",
|
|
|
|
"ContinuationToken": null
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[get("/sends/<uuid>")]
|
2023-04-30 17:18:12 +02:00
|
|
|
async fn get_send(uuid: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
|
|
|
|
let send = match Send::find_by_uuid(uuid, &mut conn).await {
|
2021-08-03 17:33:59 +02:00
|
|
|
Some(send) => send,
|
|
|
|
None => err!("Send not found"),
|
|
|
|
};
|
|
|
|
|
|
|
|
if send.user_uuid.as_ref() != Some(&headers.user.uuid) {
|
|
|
|
err!("Send is not owned by user")
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Json(send.to_json()))
|
|
|
|
}
|
|
|
|
|
2021-03-14 23:35:55 +01:00
|
|
|
#[post("/sends", data = "<data>")]
|
2022-05-20 23:39:47 +02:00
|
|
|
async fn post_send(data: JsonUpcase<SendData>, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
|
|
|
enforce_disable_send_policy(&headers, &mut conn).await?;
|
2021-03-16 10:07:45 +01:00
|
|
|
|
2021-03-14 23:35:55 +01:00
|
|
|
let data: SendData = data.into_inner().data;
|
2022-05-20 23:39:47 +02:00
|
|
|
enforce_disable_hide_email_policy(&data, &headers, &mut conn).await?;
|
2021-03-14 23:35:55 +01:00
|
|
|
|
|
|
|
if data.Type == SendType::File as i32 {
|
|
|
|
err!("File sends should use /api/sends/file")
|
|
|
|
}
|
|
|
|
|
2022-07-10 16:39:38 +02:00
|
|
|
let mut send = create_send(data, headers.user.uuid)?;
|
2022-05-20 23:39:47 +02:00
|
|
|
send.save(&mut conn).await?;
|
2023-06-16 23:34:16 +02:00
|
|
|
nt.send_send_update(
|
|
|
|
UpdateType::SyncSendCreate,
|
|
|
|
&send,
|
|
|
|
&send.update_users_revision(&mut conn).await,
|
|
|
|
&headers.device.uuid,
|
|
|
|
&mut conn,
|
|
|
|
)
|
|
|
|
.await;
|
2021-03-14 23:35:55 +01:00
|
|
|
|
|
|
|
Ok(Json(send.to_json()))
|
|
|
|
}
|
|
|
|
|
2021-11-07 18:53:39 +01:00
|
|
|
#[derive(FromForm)]
|
|
|
|
struct UploadData<'f> {
|
|
|
|
model: Json<crate::util::UpCase<SendData>>,
|
|
|
|
data: TempFile<'f>,
|
|
|
|
}
|
|
|
|
|
2022-09-22 19:40:04 +02:00
|
|
|
#[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
|
2021-03-14 23:35:55 +01:00
|
|
|
#[post("/sends/file", format = "multipart/form-data", data = "<data>")]
|
2022-05-20 23:39:47 +02:00
|
|
|
async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
|
|
|
enforce_disable_send_policy(&headers, &mut conn).await?;
|
2021-03-16 10:07:45 +01:00
|
|
|
|
2021-11-07 18:53:39 +01:00
|
|
|
let UploadData {
|
|
|
|
model,
|
|
|
|
mut data,
|
|
|
|
} = data.into_inner();
|
|
|
|
let model = model.into_inner().data;
|
2021-03-14 23:35:55 +01:00
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
enforce_disable_hide_email_policy(&model, &headers, &mut conn).await?;
|
2021-03-14 23:35:55 +01:00
|
|
|
|
|
|
|
let size_limit = match CONFIG.user_attachment_limit() {
|
|
|
|
Some(0) => err!("File uploads are disabled"),
|
|
|
|
Some(limit_kb) => {
|
2022-05-20 23:39:47 +02:00
|
|
|
let left = (limit_kb * 1024) - Attachment::size_by_user(&headers.user.uuid, &mut conn).await;
|
2021-03-14 23:35:55 +01:00
|
|
|
if left <= 0 {
|
2021-07-13 15:17:03 +02:00
|
|
|
err!("Attachment storage limit reached! Delete some attachments to free up space")
|
2021-03-14 23:35:55 +01:00
|
|
|
}
|
Added web-vault v2.21.x support + some misc fixes
- The new web-vault v2.21.0+ has support for Master Password Reset. For
this to work it generates a public/private key-pair which needs to be
stored in the database. Currently the Master Password Reset is not
fixed, but there are endpoints which are needed even if we do not
support this feature (yet). This PR fixes those endpoints, and stores
the keys already in the database.
- There was an issue when you want to do a key-rotate when you change
your password, it also called an Emergency Access endpoint, which we do
not yet support. Because this endpoint failed to reply correctly
produced some errors, and also prevent the user from being forced to
logout. This resolves #1826 by adding at least that endpoint.
Because of that extra endpoint check to Emergency Access is done using
an old user stamp, i also modified the stamp exception to allow multiple
rocket routes to be called, and added an expiration timestamp to it.
During these tests i stumbled upon an issue that after my key-change was
done, it triggered the websockets to try and reload my ciphers, because
they were updated. This shouldn't happen when rotating they keys, since
all access should be invalided. Now there will be no websocket
notification for this, which also prevents error toasts.
- Increased Send Size limit to 500MB (with a litle overhead)
As a side note, i tested these changes on both v2.20.4 and v2.21.1 web-vault versions, all keeps working.
2021-07-04 23:02:56 +02:00
|
|
|
std::cmp::Ord::max(left as u64, SIZE_525_MB)
|
2021-03-14 23:35:55 +01:00
|
|
|
}
|
Added web-vault v2.21.x support + some misc fixes
- The new web-vault v2.21.0+ has support for Master Password Reset. For
this to work it generates a public/private key-pair which needs to be
stored in the database. Currently the Master Password Reset is not
fixed, but there are endpoints which are needed even if we do not
support this feature (yet). This PR fixes those endpoints, and stores
the keys already in the database.
- There was an issue when you want to do a key-rotate when you change
your password, it also called an Emergency Access endpoint, which we do
not yet support. Because this endpoint failed to reply correctly
produced some errors, and also prevent the user from being forced to
logout. This resolves #1826 by adding at least that endpoint.
Because of that extra endpoint check to Emergency Access is done using
an old user stamp, i also modified the stamp exception to allow multiple
rocket routes to be called, and added an expiration timestamp to it.
During these tests i stumbled upon an issue that after my key-change was
done, it triggered the websockets to try and reload my ciphers, because
they were updated. This shouldn't happen when rotating they keys, since
all access should be invalided. Now there will be no websocket
notification for this, which also prevents error toasts.
- Increased Send Size limit to 500MB (with a litle overhead)
As a side note, i tested these changes on both v2.20.4 and v2.21.1 web-vault versions, all keeps working.
2021-07-04 23:02:56 +02:00
|
|
|
None => SIZE_525_MB,
|
2021-03-14 23:35:55 +01:00
|
|
|
};
|
|
|
|
|
2022-07-10 16:39:38 +02:00
|
|
|
let mut send = create_send(model, headers.user.uuid)?;
|
2021-03-14 23:35:55 +01:00
|
|
|
if send.atype != SendType::File as i32 {
|
|
|
|
err!("Send content is not a file");
|
|
|
|
}
|
|
|
|
|
2021-11-07 18:53:39 +01:00
|
|
|
let size = data.len();
|
|
|
|
if size > size_limit {
|
|
|
|
err!("Attachment storage limit exceeded with this file");
|
|
|
|
}
|
2021-03-14 23:35:55 +01:00
|
|
|
|
2021-11-07 18:53:39 +01:00
|
|
|
let file_id = crate::crypto::generate_send_id();
|
|
|
|
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?;
|
2022-07-08 19:19:00 +02:00
|
|
|
|
2022-07-09 05:41:53 +02:00
|
|
|
if let Err(_err) = data.persist_to(&file_path).await {
|
|
|
|
data.move_copy_to(file_path).await?
|
2022-07-09 04:33:27 +02:00
|
|
|
}
|
2021-03-14 23:35:55 +01:00
|
|
|
|
|
|
|
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));
|
|
|
|
o.insert(String::from("Size"), Value::Number(size.into()));
|
2021-11-07 18:53:39 +01:00
|
|
|
o.insert(String::from("SizeName"), Value::String(crate::util::get_display_size(size as i32)));
|
2021-03-14 23:35:55 +01:00
|
|
|
}
|
|
|
|
send.data = serde_json::to_string(&data_value)?;
|
|
|
|
|
|
|
|
// Save the changes in the database
|
2022-05-20 23:39:47 +02:00
|
|
|
send.save(&mut conn).await?;
|
2023-06-16 23:34:16 +02:00
|
|
|
nt.send_send_update(
|
|
|
|
UpdateType::SyncSendCreate,
|
|
|
|
&send,
|
|
|
|
&send.update_users_revision(&mut conn).await,
|
|
|
|
&headers.device.uuid,
|
|
|
|
&mut conn,
|
|
|
|
)
|
|
|
|
.await;
|
2021-03-14 23:35:55 +01:00
|
|
|
|
|
|
|
Ok(Json(send.to_json()))
|
|
|
|
}
|
|
|
|
|
2022-09-22 19:40:04 +02:00
|
|
|
// Upstream: https://github.com/bitwarden/server/blob/d0c793c95181dfb1b447eb450f85ba0bfd7ef643/src/Api/Controllers/SendsController.cs#L190
|
|
|
|
#[post("/sends/file/v2", data = "<data>")]
|
2022-05-20 23:39:47 +02:00
|
|
|
async fn post_send_file_v2(data: JsonUpcase<SendData>, headers: Headers, mut conn: DbConn) -> JsonResult {
|
|
|
|
enforce_disable_send_policy(&headers, &mut conn).await?;
|
2022-09-22 19:40:04 +02:00
|
|
|
|
|
|
|
let data = data.into_inner().data;
|
|
|
|
|
|
|
|
if data.Type != SendType::File as i32 {
|
|
|
|
err!("Send content is not a file");
|
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
enforce_disable_hide_email_policy(&data, &headers, &mut conn).await?;
|
2022-09-22 19:40:04 +02:00
|
|
|
|
|
|
|
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) => {
|
2022-05-20 23:39:47 +02:00
|
|
|
let left = (limit_kb * 1024) - Attachment::size_by_user(&headers.user.uuid, &mut conn).await;
|
2022-09-22 19:40:04 +02:00
|
|
|
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)?;
|
2022-05-20 23:39:47 +02:00
|
|
|
send.save(&mut conn).await?;
|
2022-09-22 19:40:04 +02:00
|
|
|
|
|
|
|
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(
|
2023-04-30 17:18:12 +02:00
|
|
|
send_uuid: &str,
|
|
|
|
file_id: &str,
|
2022-09-22 19:40:04 +02:00
|
|
|
data: Form<UploadDataV2<'_>>,
|
|
|
|
headers: Headers,
|
2022-05-20 23:39:47 +02:00
|
|
|
mut conn: DbConn,
|
2022-09-22 19:40:04 +02:00
|
|
|
nt: Notify<'_>,
|
|
|
|
) -> EmptyResult {
|
2022-05-20 23:39:47 +02:00
|
|
|
enforce_disable_send_policy(&headers, &mut conn).await?;
|
2022-09-22 19:40:04 +02:00
|
|
|
|
|
|
|
let mut data = data.into_inner();
|
|
|
|
|
2023-08-28 16:48:42 +02:00
|
|
|
let Some(send) = Send::find_by_uuid(send_uuid, &mut conn).await else {
|
|
|
|
err!("Send not found. Unable to save the file.")
|
|
|
|
};
|
2022-09-22 19:40:04 +02:00
|
|
|
|
2023-08-28 16:48:42 +02:00
|
|
|
let Some(send_user_id) = &send.user_uuid else {
|
|
|
|
err!("Sends are only supported for users at the moment")
|
|
|
|
};
|
2023-07-03 19:58:14 +02:00
|
|
|
if send_user_id != &headers.user.uuid {
|
|
|
|
err!("Send doesn't belong to user");
|
|
|
|
}
|
2022-09-22 19:40:04 +02:00
|
|
|
|
2023-07-03 19:58:14 +02:00
|
|
|
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?
|
2022-09-22 19:40:04 +02:00
|
|
|
}
|
|
|
|
|
2023-07-03 19:58:14 +02:00
|
|
|
nt.send_send_update(
|
|
|
|
UpdateType::SyncSendCreate,
|
|
|
|
&send,
|
|
|
|
&send.update_users_revision(&mut conn).await,
|
|
|
|
&headers.device.uuid,
|
|
|
|
&mut conn,
|
|
|
|
)
|
|
|
|
.await;
|
|
|
|
|
2022-09-22 19:40:04 +02:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-03-14 23:35:55 +01:00
|
|
|
#[derive(Deserialize)]
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
pub struct SendAccessData {
|
|
|
|
pub Password: Option<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[post("/sends/access/<access_id>", data = "<data>")]
|
2022-05-20 23:39:47 +02:00
|
|
|
async fn post_access(
|
2023-04-30 17:18:12 +02:00
|
|
|
access_id: &str,
|
2022-05-20 23:39:47 +02:00
|
|
|
data: JsonUpcase<SendAccessData>,
|
|
|
|
mut conn: DbConn,
|
|
|
|
ip: ClientIp,
|
2022-12-30 21:23:55 +01:00
|
|
|
nt: Notify<'_>,
|
2022-05-20 23:39:47 +02:00
|
|
|
) -> JsonResult {
|
2023-04-30 17:18:12 +02:00
|
|
|
let mut send = match Send::find_by_access_id(access_id, &mut conn).await {
|
2021-03-14 23:35:55 +01:00
|
|
|
Some(s) => s,
|
2021-03-25 12:40:32 +01:00
|
|
|
None => err_code!(SEND_INACCESSIBLE_MSG, 404),
|
2021-03-14 23:35:55 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(max_access_count) = send.max_access_count {
|
2021-03-15 07:20:49 +01:00
|
|
|
if send.access_count >= max_access_count {
|
2021-03-25 12:40:32 +01:00
|
|
|
err_code!(SEND_INACCESSIBLE_MSG, 404);
|
2021-03-14 23:35:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(expiration) = send.expiration_date {
|
2021-03-15 07:20:49 +01:00
|
|
|
if Utc::now().naive_utc() >= expiration {
|
2021-03-25 12:40:32 +01:00
|
|
|
err_code!(SEND_INACCESSIBLE_MSG, 404)
|
2021-03-14 23:35:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-15 07:20:49 +01:00
|
|
|
if Utc::now().naive_utc() >= send.deletion_date {
|
2021-03-25 12:40:32 +01:00
|
|
|
err_code!(SEND_INACCESSIBLE_MSG, 404)
|
2021-03-14 23:35:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if send.disabled {
|
2021-03-25 12:40:32 +01:00
|
|
|
err_code!(SEND_INACCESSIBLE_MSG, 404)
|
2021-03-14 23:35:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if send.password_hash.is_some() {
|
|
|
|
match data.into_inner().data.Password {
|
|
|
|
Some(ref p) if send.check_password(p) => { /* Nothing to do here */ }
|
2022-02-13 13:06:52 +01:00
|
|
|
Some(_) => err!("Invalid password", format!("IP: {}.", ip.ip)),
|
|
|
|
None => err_code!("Password not provided", format!("IP: {}.", ip.ip), 401),
|
2021-03-14 23:35:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Files are incremented during the download
|
|
|
|
if send.atype == SendType::Text as i32 {
|
|
|
|
send.access_count += 1;
|
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
send.save(&mut conn).await?;
|
2021-03-14 23:35:55 +01:00
|
|
|
|
2023-06-16 23:34:16 +02:00
|
|
|
nt.send_send_update(
|
|
|
|
UpdateType::SyncSendUpdate,
|
|
|
|
&send,
|
|
|
|
&send.update_users_revision(&mut conn).await,
|
2023-06-22 16:40:26 +02:00
|
|
|
&String::from("00000000-0000-0000-0000-000000000000"),
|
2023-06-16 23:34:16 +02:00
|
|
|
&mut conn,
|
|
|
|
)
|
|
|
|
.await;
|
2022-12-30 21:23:55 +01:00
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
Ok(Json(send.to_json_access(&mut conn).await))
|
2021-03-14 23:35:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[post("/sends/<send_id>/access/file/<file_id>", data = "<data>")]
|
2021-11-16 17:07:55 +01:00
|
|
|
async fn post_access_file(
|
2023-04-30 17:18:12 +02:00
|
|
|
send_id: &str,
|
|
|
|
file_id: &str,
|
2021-03-14 23:35:55 +01:00
|
|
|
data: JsonUpcase<SendAccessData>,
|
|
|
|
host: Host,
|
2022-05-20 23:39:47 +02:00
|
|
|
mut conn: DbConn,
|
2022-12-30 21:23:55 +01:00
|
|
|
nt: Notify<'_>,
|
2021-03-14 23:35:55 +01:00
|
|
|
) -> JsonResult {
|
2023-04-30 17:18:12 +02:00
|
|
|
let mut send = match Send::find_by_uuid(send_id, &mut conn).await {
|
2021-03-14 23:35:55 +01:00
|
|
|
Some(s) => s,
|
2021-03-25 12:40:32 +01:00
|
|
|
None => err_code!(SEND_INACCESSIBLE_MSG, 404),
|
2021-03-14 23:35:55 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(max_access_count) = send.max_access_count {
|
2021-03-15 07:20:49 +01:00
|
|
|
if send.access_count >= max_access_count {
|
2021-03-25 12:40:32 +01:00
|
|
|
err_code!(SEND_INACCESSIBLE_MSG, 404)
|
2021-03-14 23:35:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(expiration) = send.expiration_date {
|
2021-03-15 07:20:49 +01:00
|
|
|
if Utc::now().naive_utc() >= expiration {
|
2021-03-25 12:40:32 +01:00
|
|
|
err_code!(SEND_INACCESSIBLE_MSG, 404)
|
2021-03-14 23:35:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-15 07:20:49 +01:00
|
|
|
if Utc::now().naive_utc() >= send.deletion_date {
|
2021-03-25 12:40:32 +01:00
|
|
|
err_code!(SEND_INACCESSIBLE_MSG, 404)
|
2021-03-14 23:35:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if send.disabled {
|
2021-03-25 12:40:32 +01:00
|
|
|
err_code!(SEND_INACCESSIBLE_MSG, 404)
|
2021-03-14 23:35:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if send.password_hash.is_some() {
|
|
|
|
match data.into_inner().data.Password {
|
|
|
|
Some(ref p) if send.check_password(p) => { /* Nothing to do here */ }
|
|
|
|
Some(_) => err!("Invalid password."),
|
|
|
|
None => err_code!("Password not provided", 401),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
send.access_count += 1;
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
send.save(&mut conn).await?;
|
2021-03-14 23:35:55 +01:00
|
|
|
|
2023-06-16 23:34:16 +02:00
|
|
|
nt.send_send_update(
|
|
|
|
UpdateType::SyncSendUpdate,
|
|
|
|
&send,
|
|
|
|
&send.update_users_revision(&mut conn).await,
|
2023-06-22 16:40:26 +02:00
|
|
|
&String::from("00000000-0000-0000-0000-000000000000"),
|
2023-06-16 23:34:16 +02:00
|
|
|
&mut conn,
|
|
|
|
)
|
|
|
|
.await;
|
2022-12-30 21:23:55 +01:00
|
|
|
|
2023-04-30 17:18:12 +02:00
|
|
|
let token_claims = crate::auth::generate_send_claims(send_id, file_id);
|
2021-06-25 20:33:51 +02:00
|
|
|
let token = crate::auth::encode_jwt(&token_claims);
|
2021-03-14 23:35:55 +01:00
|
|
|
Ok(Json(json!({
|
|
|
|
"Object": "send-fileDownload",
|
|
|
|
"Id": file_id,
|
2021-06-25 20:33:51 +02:00
|
|
|
"Url": format!("{}/api/sends/{}/{}?t={}", &host.host, send_id, file_id, token)
|
2021-03-14 23:35:55 +01:00
|
|
|
})))
|
|
|
|
}
|
|
|
|
|
2021-06-25 20:33:51 +02:00
|
|
|
#[get("/sends/<send_id>/<file_id>?<t>")]
|
2023-04-30 17:18:12 +02:00
|
|
|
async fn download_send(send_id: SafeString, file_id: SafeString, t: &str) -> Option<NamedFile> {
|
|
|
|
if let Ok(claims) = crate::auth::decode_send(t) {
|
2022-12-29 14:11:52 +01:00
|
|
|
if claims.sub == format!("{send_id}/{file_id}") {
|
2021-11-07 18:53:39 +01:00
|
|
|
return NamedFile::open(Path::new(&CONFIG.sends_folder()).join(send_id).join(file_id)).await.ok();
|
2021-06-25 20:33:51 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
2021-03-14 23:35:55 +01:00
|
|
|
#[put("/sends/<id>", data = "<data>")]
|
2021-11-16 17:07:55 +01:00
|
|
|
async fn put_send(
|
2023-04-30 17:18:12 +02:00
|
|
|
id: &str,
|
2021-11-16 17:07:55 +01:00
|
|
|
data: JsonUpcase<SendData>,
|
|
|
|
headers: Headers,
|
2022-05-20 23:39:47 +02:00
|
|
|
mut conn: DbConn,
|
2021-11-16 17:07:55 +01:00
|
|
|
nt: Notify<'_>,
|
|
|
|
) -> JsonResult {
|
2022-05-20 23:39:47 +02:00
|
|
|
enforce_disable_send_policy(&headers, &mut conn).await?;
|
2021-03-16 10:07:45 +01:00
|
|
|
|
2021-03-14 23:35:55 +01:00
|
|
|
let data: SendData = data.into_inner().data;
|
2022-05-20 23:39:47 +02:00
|
|
|
enforce_disable_hide_email_policy(&data, &headers, &mut conn).await?;
|
2021-03-14 23:35:55 +01:00
|
|
|
|
2023-04-30 17:18:12 +02:00
|
|
|
let mut send = match Send::find_by_uuid(id, &mut conn).await {
|
2021-03-14 23:35:55 +01:00
|
|
|
Some(s) => s,
|
|
|
|
None => err!("Send not found"),
|
|
|
|
};
|
|
|
|
|
|
|
|
if send.user_uuid.as_ref() != Some(&headers.user.uuid) {
|
|
|
|
err!("Send is not owned by user")
|
|
|
|
}
|
|
|
|
|
|
|
|
if send.atype != data.Type {
|
|
|
|
err!("Sends can't change type")
|
|
|
|
}
|
|
|
|
|
2021-03-17 19:39:48 +01:00
|
|
|
// When updating a file Send, we receive nulls in the File field, as it's immutable,
|
|
|
|
// so we only need to update the data field in the Text case
|
|
|
|
if data.Type == SendType::Text as i32 {
|
|
|
|
let data_str = if let Some(mut d) = data.Text {
|
|
|
|
d.as_object_mut().and_then(|d| d.remove("Response"));
|
|
|
|
serde_json::to_string(&d)?
|
|
|
|
} else {
|
|
|
|
err!("Send data not provided");
|
|
|
|
};
|
|
|
|
send.data = data_str;
|
|
|
|
}
|
2021-03-14 23:35:55 +01:00
|
|
|
|
|
|
|
if data.DeletionDate > Utc::now() + Duration::days(31) {
|
|
|
|
err!(
|
|
|
|
"You cannot have a Send with a deletion date that far into the future. Adjust the Deletion Date to a value less than 31 days from now and try again."
|
|
|
|
);
|
|
|
|
}
|
|
|
|
send.name = data.Name;
|
2021-03-15 16:42:20 +01:00
|
|
|
send.akey = data.Key;
|
2021-03-14 23:35:55 +01:00
|
|
|
send.deletion_date = data.DeletionDate.naive_utc();
|
|
|
|
send.notes = data.Notes;
|
2021-12-31 15:59:58 +01:00
|
|
|
send.max_access_count = match data.MaxAccessCount {
|
|
|
|
Some(m) => Some(m.into_i32()?),
|
|
|
|
_ => None,
|
|
|
|
};
|
2021-03-14 23:35:55 +01:00
|
|
|
send.expiration_date = data.ExpirationDate.map(|d| d.naive_utc());
|
2021-05-12 07:51:12 +02:00
|
|
|
send.hide_email = data.HideEmail;
|
2021-03-14 23:35:55 +01:00
|
|
|
send.disabled = data.Disabled;
|
|
|
|
|
|
|
|
// Only change the value if it's present
|
|
|
|
if let Some(password) = data.Password {
|
|
|
|
send.set_password(Some(&password));
|
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
send.save(&mut conn).await?;
|
2023-06-16 23:34:16 +02:00
|
|
|
nt.send_send_update(
|
|
|
|
UpdateType::SyncSendUpdate,
|
|
|
|
&send,
|
|
|
|
&send.update_users_revision(&mut conn).await,
|
|
|
|
&headers.device.uuid,
|
|
|
|
&mut conn,
|
|
|
|
)
|
|
|
|
.await;
|
2021-03-14 23:35:55 +01:00
|
|
|
|
|
|
|
Ok(Json(send.to_json()))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[delete("/sends/<id>")]
|
2023-04-30 17:18:12 +02:00
|
|
|
async fn delete_send(id: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
|
|
|
let send = match Send::find_by_uuid(id, &mut conn).await {
|
2021-03-14 23:35:55 +01:00
|
|
|
Some(s) => s,
|
|
|
|
None => err!("Send not found"),
|
|
|
|
};
|
|
|
|
|
|
|
|
if send.user_uuid.as_ref() != Some(&headers.user.uuid) {
|
|
|
|
err!("Send is not owned by user")
|
|
|
|
}
|
|
|
|
|
2022-05-20 23:39:47 +02:00
|
|
|
send.delete(&mut conn).await?;
|
2023-06-16 23:34:16 +02:00
|
|
|
nt.send_send_update(
|
|
|
|
UpdateType::SyncSendDelete,
|
|
|
|
&send,
|
|
|
|
&send.update_users_revision(&mut conn).await,
|
|
|
|
&headers.device.uuid,
|
|
|
|
&mut conn,
|
|
|
|
)
|
|
|
|
.await;
|
2021-03-14 23:35:55 +01:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[put("/sends/<id>/remove-password")]
|
2023-04-30 17:18:12 +02:00
|
|
|
async fn put_remove_password(id: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
2022-05-20 23:39:47 +02:00
|
|
|
enforce_disable_send_policy(&headers, &mut conn).await?;
|
2021-03-16 10:07:45 +01:00
|
|
|
|
2023-04-30 17:18:12 +02:00
|
|
|
let mut send = match Send::find_by_uuid(id, &mut conn).await {
|
2021-03-14 23:35:55 +01:00
|
|
|
Some(s) => s,
|
|
|
|
None => err!("Send not found"),
|
|
|
|
};
|
|
|
|
|
|
|
|
if send.user_uuid.as_ref() != Some(&headers.user.uuid) {
|
|
|
|
err!("Send is not owned by user")
|
|
|
|
}
|
|
|
|
|
|
|
|
send.set_password(None);
|
2022-05-20 23:39:47 +02:00
|
|
|
send.save(&mut conn).await?;
|
2023-06-16 23:34:16 +02:00
|
|
|
nt.send_send_update(
|
|
|
|
UpdateType::SyncSendUpdate,
|
|
|
|
&send,
|
|
|
|
&send.update_users_revision(&mut conn).await,
|
|
|
|
&headers.device.uuid,
|
|
|
|
&mut conn,
|
|
|
|
)
|
|
|
|
.await;
|
2021-03-14 23:35:55 +01:00
|
|
|
|
|
|
|
Ok(Json(send.to_json()))
|
|
|
|
}
|