diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index e465c597..43702219 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -934,6 +934,15 @@ async fn share_cipher_by_uuid( /// redirects to the same location as before the v2 API. #[get("/ciphers//attachment/")] async fn get_attachment(uuid: &str, attachment_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult { + let cipher = match Cipher::find_by_uuid(uuid, &mut conn).await { + Some(cipher) => cipher, + None => err!("Cipher doesn't exist"), + }; + + if !cipher.is_accessible_to_user(&headers.user.uuid, &mut conn).await { + err!("Cipher is not accessible") + } + match Attachment::find_by_id(attachment_id, &mut conn).await { Some(attachment) if uuid == attachment.cipher_uuid => Ok(Json(attachment.to_json(&headers.host))), Some(_) => err!("Attachment doesn't belong to cipher"), diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index 551656c0..6eaeeb63 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -2578,11 +2578,15 @@ async fn put_user_groups( err!("Group support is disabled"); } - match UserOrganization::find_by_uuid(org_user_id, &mut conn).await { - Some(_) => { /* Do nothing */ } + let user_org = match UserOrganization::find_by_uuid(org_user_id, &mut conn).await { + Some(uo) => uo, _ => err!("User could not be found!"), }; + if user_org.org_uuid != org_id { + err!("Group doesn't belong to organization"); + } + GroupUser::delete_all_by_user(org_user_id, &mut conn).await?; let assigned_group_ids = data.into_inner().data; @@ -2628,16 +2632,24 @@ async fn delete_group_user( err!("Group support is disabled"); } - match UserOrganization::find_by_uuid(org_user_id, &mut conn).await { - Some(_) => { /* Do nothing */ } + let user_org = match UserOrganization::find_by_uuid(org_user_id, &mut conn).await { + Some(uo) => uo, _ => err!("User could not be found!"), }; - match Group::find_by_uuid(group_id, &mut conn).await { - Some(_) => { /* Do nothing */ } + if user_org.org_uuid != org_id { + err!("User doesn't belong to organization"); + } + + let group = match Group::find_by_uuid(group_id, &mut conn).await { + Some(g) => g, _ => err!("Group could not be found!"), }; + if group.organizations_uuid != org_id { + err!("Group doesn't belong to organization"); + } + log_event( EventType::OrganizationUserUpdatedGroups as i32, org_user_id, diff --git a/src/api/core/sends.rs b/src/api/core/sends.rs index 7b91299b..1ea790e6 100644 --- a/src/api/core/sends.rs +++ b/src/api/core/sends.rs @@ -340,27 +340,30 @@ async fn post_send_file_v2_data( let mut data = data.into_inner(); - if let Some(send) = Send::find_by_uuid(send_uuid, &mut 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?; + let Some(send) = Send::find_by_uuid(send_uuid, &mut conn).await else { err!("Send not found. Unable to save the file.") }; - 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(&mut conn).await, - &headers.device.uuid, - &mut conn, - ) - .await; - } else { - err!("Send not found. Unable to save the file."); + let Some(send_user_id) = &send.user_uuid else {err!("Sends are only supported for users at the moment")}; + if send_user_id != &headers.user.uuid { + err!("Send doesn't belong to user"); } + 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(&mut conn).await, + &headers.device.uuid, + &mut conn, + ) + .await; + Ok(()) } diff --git a/src/api/web.rs b/src/api/web.rs index abe2b5b9..13637b77 100644 --- a/src/api/web.rs +++ b/src/api/web.rs @@ -5,6 +5,7 @@ use serde_json::Value; use crate::{ api::{core::now, ApiResult, EmptyResult}, + auth::decode_file_download, error::Error, util::{Cached, SafeString}, CONFIG, @@ -91,8 +92,13 @@ async fn web_files(p: PathBuf) -> Cached> { Cached::long(NamedFile::open(Path::new(&CONFIG.web_vault_folder()).join(p)).await.ok(), true) } -#[get("/attachments//")] -async fn attachments(uuid: SafeString, file_id: SafeString) -> Option { +#[get("/attachments//?")] +async fn attachments(uuid: SafeString, file_id: SafeString, token: String) -> Option { + let Ok(claims) = dbg!(decode_file_download(&token)) else { return None }; + if claims.sub != *uuid || claims.file_id != *file_id { + return None; + } + NamedFile::open(Path::new(&CONFIG.attachments_folder()).join(uuid).join(file_id)).await.ok() } diff --git a/src/auth.rs b/src/auth.rs index 6b01a4d4..6879bb6e 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -24,6 +24,7 @@ static JWT_VERIFYEMAIL_ISSUER: Lazy = Lazy::new(|| format!("{}|verifyema static JWT_ADMIN_ISSUER: Lazy = Lazy::new(|| format!("{}|admin", CONFIG.domain_origin())); static JWT_SEND_ISSUER: Lazy = Lazy::new(|| format!("{}|send", CONFIG.domain_origin())); static JWT_ORG_API_KEY_ISSUER: Lazy = Lazy::new(|| format!("{}|api.organization", CONFIG.domain_origin())); +static JWT_FILE_DOWNLOAD_ISSUER: Lazy = Lazy::new(|| format!("{}|file_download", CONFIG.domain_origin())); static PRIVATE_RSA_KEY: Lazy = Lazy::new(|| { let key = @@ -98,6 +99,10 @@ pub fn decode_api_org(token: &str) -> Result { decode_jwt(token, JWT_ORG_API_KEY_ISSUER.to_string()) } +pub fn decode_file_download(token: &str) -> Result { + decode_jwt(token, JWT_FILE_DOWNLOAD_ISSUER.to_string()) +} + #[derive(Debug, Serialize, Deserialize)] pub struct LoginJwtClaims { // Not before @@ -234,6 +239,31 @@ pub fn generate_organization_api_key_login_claims(uuid: String, org_id: String) } } +#[derive(Debug, Serialize, Deserialize)] +pub struct FileDownloadClaims { + // Not before + pub nbf: i64, + // Expiration time + pub exp: i64, + // Issuer + pub iss: String, + // Subject + pub sub: String, + + pub file_id: String, +} + +pub fn generate_file_download_claims(uuid: String, file_id: String) -> FileDownloadClaims { + let time_now = Utc::now().naive_utc(); + FileDownloadClaims { + nbf: time_now.timestamp(), + exp: (time_now + Duration::minutes(5)).timestamp(), + iss: JWT_FILE_DOWNLOAD_ISSUER.to_string(), + sub: uuid, + file_id, + } +} + #[derive(Debug, Serialize, Deserialize)] pub struct BasicJwtClaims { // Not before diff --git a/src/db/models/attachment.rs b/src/db/models/attachment.rs index e850537d..616aae2f 100644 --- a/src/db/models/attachment.rs +++ b/src/db/models/attachment.rs @@ -35,7 +35,8 @@ impl Attachment { } pub fn get_url(&self, host: &str) -> String { - format!("{}/attachments/{}/{}", host, self.cipher_uuid, self.id) + let token = encode_jwt(&generate_file_download_claims(self.cipher_uuid.clone(), self.id.clone())); + format!("{}/attachments/{}/{}?token={}", host, self.cipher_uuid, self.id, token) } pub fn to_json(&self, host: &str) -> Value { @@ -51,6 +52,7 @@ impl Attachment { } } +use crate::auth::{encode_jwt, generate_file_download_claims}; use crate::db::DbConn; use crate::api::EmptyResult; diff --git a/src/util.rs b/src/util.rs index 6de97ef6..e12e334b 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,7 +1,10 @@ // // Web Headers and caching // -use std::io::{Cursor, ErrorKind}; +use std::{ + io::{Cursor, ErrorKind}, + ops::Deref, +}; use rocket::{ fairing::{Fairing, Info, Kind}, @@ -209,6 +212,14 @@ impl std::fmt::Display for SafeString { } } +impl Deref for SafeString { + type Target = String; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + impl AsRef for SafeString { #[inline] fn as_ref(&self) -> &Path {