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";
|
|
|
|
|
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,
|
|
|
|
download_send
|
|
|
|
]
|
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");
|
2021-11-07 18:53:39 +01:00
|
|
|
if let Ok(conn) = pool.get().await {
|
2021-11-16 17:07:55 +01:00
|
|
|
Send::purge(&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>,
|
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.
|
2021-11-16 17:07:55 +01:00
|
|
|
async fn enforce_disable_send_policy(headers: &Headers, conn: &DbConn) -> EmptyResult {
|
2021-03-16 10:07:45 +01:00
|
|
|
let user_uuid = &headers.user.uuid;
|
|
|
|
let policy_type = OrgPolicyType::DisableSend;
|
2021-11-16 17:07:55 +01:00
|
|
|
if !CONFIG.sends_allowed() || OrgPolicy::is_applicable_to_user(user_uuid, policy_type, 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
|
2021-11-16 17:07:55 +01:00
|
|
|
async fn enforce_disable_hide_email_policy(data: &SendData, headers: &Headers, conn: &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(())
|
|
|
|
}
|
|
|
|
|
2021-11-16 17:07:55 +01:00
|
|
|
async 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."
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-11-16 17:07:55 +01:00
|
|
|
let mut send = Send::new(data.Type, data.Name, data_str, data.Key, data.DeletionDate.naive_utc()).await;
|
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")]
|
2021-11-16 17:07:55 +01:00
|
|
|
async fn get_sends(headers: Headers, conn: DbConn) -> Json<Value> {
|
2021-08-03 17:33:59 +02:00
|
|
|
let sends = Send::find_by_user(&headers.user.uuid, &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>")]
|
2021-11-16 17:07:55 +01:00
|
|
|
async fn get_send(uuid: String, headers: Headers, conn: DbConn) -> JsonResult {
|
|
|
|
let send = match Send::find_by_uuid(&uuid, &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>")]
|
2021-11-16 17:07:55 +01:00
|
|
|
async fn post_send(data: JsonUpcase<SendData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
|
|
|
enforce_disable_send_policy(&headers, &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;
|
2021-11-16 17:07:55 +01:00
|
|
|
enforce_disable_hide_email_policy(&data, &headers, &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")
|
|
|
|
}
|
|
|
|
|
2021-11-16 17:07:55 +01:00
|
|
|
let mut send = create_send(data, headers.user.uuid).await?;
|
|
|
|
send.save(&conn).await?;
|
2022-05-20 20:37:32 +02:00
|
|
|
nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&conn).await).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>,
|
|
|
|
}
|
|
|
|
|
2021-03-14 23:35:55 +01:00
|
|
|
#[post("/sends/file", format = "multipart/form-data", data = "<data>")]
|
2021-11-07 18:53:39 +01:00
|
|
|
async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
2021-11-16 17:07:55 +01:00
|
|
|
enforce_disable_send_policy(&headers, &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
|
|
|
|
2021-11-16 17:07:55 +01:00
|
|
|
enforce_disable_hide_email_policy(&model, &headers, &conn).await?;
|
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
|
|
|
// Get the file length and add an extra 5% to avoid issues
|
|
|
|
const SIZE_525_MB: u64 = 550_502_400;
|
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) => {
|
2021-11-16 17:07:55 +01:00
|
|
|
let left = (limit_kb * 1024) - Attachment::size_by_user(&headers.user.uuid, &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
|
|
|
};
|
|
|
|
|
2021-11-16 17:07:55 +01:00
|
|
|
let mut send = create_send(model, headers.user.uuid).await?;
|
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 04:33:27 +02:00
|
|
|
match data.persist_to(&file_path).await {
|
|
|
|
Ok(_result) => {}
|
2022-07-09 04:49:51 +02:00
|
|
|
Err(_error) => 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
|
2021-11-16 17:07:55 +01:00
|
|
|
send.save(&conn).await?;
|
2022-05-20 20:37:32 +02:00
|
|
|
nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&conn).await).await;
|
2021-03-14 23:35:55 +01:00
|
|
|
|
|
|
|
Ok(Json(send.to_json()))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Deserialize)]
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
pub struct SendAccessData {
|
|
|
|
pub Password: Option<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[post("/sends/access/<access_id>", data = "<data>")]
|
2021-11-16 17:07:55 +01:00
|
|
|
async fn post_access(access_id: String, data: JsonUpcase<SendAccessData>, conn: DbConn, ip: ClientIp) -> JsonResult {
|
|
|
|
let mut send = match Send::find_by_access_id(&access_id, &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;
|
|
|
|
}
|
|
|
|
|
2021-11-16 17:07:55 +01:00
|
|
|
send.save(&conn).await?;
|
2021-03-14 23:35:55 +01:00
|
|
|
|
2021-11-16 17:07:55 +01:00
|
|
|
Ok(Json(send.to_json_access(&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(
|
2021-03-14 23:35:55 +01:00
|
|
|
send_id: String,
|
|
|
|
file_id: String,
|
|
|
|
data: JsonUpcase<SendAccessData>,
|
|
|
|
host: Host,
|
|
|
|
conn: DbConn,
|
|
|
|
) -> JsonResult {
|
2021-11-16 17:07:55 +01:00
|
|
|
let mut send = match Send::find_by_uuid(&send_id, &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;
|
|
|
|
|
2021-11-16 17:07:55 +01:00
|
|
|
send.save(&conn).await?;
|
2021-03-14 23:35:55 +01:00
|
|
|
|
2021-06-25 20:33:51 +02:00
|
|
|
let token_claims = crate::auth::generate_send_claims(&send_id, &file_id);
|
|
|
|
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>")]
|
2021-11-07 18:53:39 +01:00
|
|
|
async fn download_send(send_id: SafeString, file_id: SafeString, t: String) -> Option<NamedFile> {
|
2021-06-25 20:33:51 +02:00
|
|
|
if let Ok(claims) = crate::auth::decode_send(&t) {
|
|
|
|
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(
|
|
|
|
id: String,
|
|
|
|
data: JsonUpcase<SendData>,
|
|
|
|
headers: Headers,
|
|
|
|
conn: DbConn,
|
|
|
|
nt: Notify<'_>,
|
|
|
|
) -> JsonResult {
|
|
|
|
enforce_disable_send_policy(&headers, &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;
|
2021-11-16 17:07:55 +01:00
|
|
|
enforce_disable_hide_email_policy(&data, &headers, &conn).await?;
|
2021-03-14 23:35:55 +01:00
|
|
|
|
2021-11-16 17:07:55 +01:00
|
|
|
let mut send = match Send::find_by_uuid(&id, &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));
|
|
|
|
}
|
|
|
|
|
2021-11-16 17:07:55 +01:00
|
|
|
send.save(&conn).await?;
|
2022-05-20 20:37:32 +02:00
|
|
|
nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&conn).await).await;
|
2021-03-14 23:35:55 +01:00
|
|
|
|
|
|
|
Ok(Json(send.to_json()))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[delete("/sends/<id>")]
|
2021-11-16 17:07:55 +01:00
|
|
|
async fn delete_send(id: String, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
|
|
|
let send = match Send::find_by_uuid(&id, &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")
|
|
|
|
}
|
|
|
|
|
2021-11-16 17:07:55 +01:00
|
|
|
send.delete(&conn).await?;
|
2022-05-20 20:37:32 +02:00
|
|
|
nt.send_send_update(UpdateType::SyncSendDelete, &send, &send.update_users_revision(&conn).await).await;
|
2021-03-14 23:35:55 +01:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[put("/sends/<id>/remove-password")]
|
2021-11-16 17:07:55 +01:00
|
|
|
async fn put_remove_password(id: String, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
|
|
|
enforce_disable_send_policy(&headers, &conn).await?;
|
2021-03-16 10:07:45 +01:00
|
|
|
|
2021-11-16 17:07:55 +01:00
|
|
|
let mut send = match Send::find_by_uuid(&id, &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);
|
2021-11-16 17:07:55 +01:00
|
|
|
send.save(&conn).await?;
|
2022-05-20 20:37:32 +02:00
|
|
|
nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&conn).await).await;
|
2021-03-14 23:35:55 +01:00
|
|
|
|
|
|
|
Ok(Json(send.to_json()))
|
|
|
|
}
|