Browse Source

Add some extra access checks for attachments and groups

Daniel García 2 years ago
parent
commit
60964c07e6
7 changed files with 100 additions and 27 deletions
  1. 9 0
      src/api/core/ciphers.rs
  2. 18 6
      src/api/core/organizations.rs
  3. 20 17
      src/api/core/sends.rs
  4. 8 2
      src/api/web.rs
  5. 30 0
      src/auth.rs
  6. 3 1
      src/db/models/attachment.rs
  7. 12 1
      src/util.rs

+ 9 - 0
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/<uuid>/attachment/<attachment_id>")]
 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"),

+ 18 - 6
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,

+ 20 - 17
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?
-        }
+    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");
+    }
 
-        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 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(())
 }
 

+ 8 - 2
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<Option<NamedFile>> {
     Cached::long(NamedFile::open(Path::new(&CONFIG.web_vault_folder()).join(p)).await.ok(), true)
 }
 
-#[get("/attachments/<uuid>/<file_id>")]
-async fn attachments(uuid: SafeString, file_id: SafeString) -> Option<NamedFile> {
+#[get("/attachments/<uuid>/<file_id>?<token>")]
+async fn attachments(uuid: SafeString, file_id: SafeString, token: String) -> Option<NamedFile> {
+    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()
 }
 

+ 30 - 0
src/auth.rs

@@ -24,6 +24,7 @@ static JWT_VERIFYEMAIL_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|verifyema
 static JWT_ADMIN_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|admin", CONFIG.domain_origin()));
 static JWT_SEND_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|send", CONFIG.domain_origin()));
 static JWT_ORG_API_KEY_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|api.organization", CONFIG.domain_origin()));
+static JWT_FILE_DOWNLOAD_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|file_download", CONFIG.domain_origin()));
 
 static PRIVATE_RSA_KEY: Lazy<EncodingKey> = Lazy::new(|| {
     let key =
@@ -98,6 +99,10 @@ pub fn decode_api_org(token: &str) -> Result<OrgApiKeyLoginJwtClaims, Error> {
     decode_jwt(token, JWT_ORG_API_KEY_ISSUER.to_string())
 }
 
+pub fn decode_file_download(token: &str) -> Result<FileDownloadClaims, Error> {
+    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

+ 3 - 1
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;

+ 12 - 1
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<Path> for SafeString {
     #[inline]
     fn as_ref(&self) -> &Path {