mirror of
				https://github.com/dani-garcia/vaultwarden.git
				synced 2025-10-30 01:38:20 +02:00 
			
		
		
		
	Fix attachments during key rotation, add individual attachment key
This commit is contained in:
		| @@ -1,4 +1,4 @@ | ||||
| use std::collections::HashSet; | ||||
| use std::collections::{HashSet, HashMap}; | ||||
| use std::path::Path; | ||||
|  | ||||
| use rocket::http::ContentType; | ||||
| @@ -162,6 +162,18 @@ pub struct CipherData { | ||||
|     Favorite: Option<bool>, | ||||
|  | ||||
|     PasswordHistory: Option<Value>, | ||||
|  | ||||
|     // These are used during key rotation | ||||
|     #[serde(rename = "Attachments")] | ||||
|     _Attachments: Option<Value>, // Unused, contains map of {id: filename} | ||||
|     Attachments2: Option<HashMap<String, Attachments2Data>> | ||||
| } | ||||
|  | ||||
| #[derive(Deserialize, Debug)] | ||||
| #[allow(non_snake_case)] | ||||
| pub struct Attachments2Data { | ||||
|     FileName: String, | ||||
|     Key: String, | ||||
| } | ||||
|  | ||||
| #[post("/ciphers/admin", data = "<data>")] | ||||
| @@ -221,6 +233,28 @@ pub fn update_cipher_from_data(cipher: &mut Cipher, data: CipherData, headers: & | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Modify attachments name and keys when rotating | ||||
|     if let Some(attachments) = data.Attachments2 { | ||||
|         for (id, attachment) in attachments { | ||||
|             let mut saved_att = match Attachment::find_by_id(&id, &conn) { | ||||
|                 Some(att) => att, | ||||
|                 None => err!("Attachment doesn't exist") | ||||
|             }; | ||||
|  | ||||
|             if saved_att.cipher_uuid != cipher.uuid { | ||||
|                 err!("Attachment is not owned by the cipher") | ||||
|             } | ||||
|  | ||||
|             saved_att.key = Some(attachment.Key); | ||||
|             saved_att.file_name = attachment.FileName; | ||||
|  | ||||
|             match saved_att.save(&conn) { | ||||
|                 Ok(()) => (), | ||||
|                 Err(_) => err!("Failed to save attachment") | ||||
|             }; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     let type_data_opt = match data.Type { | ||||
|         1 => data.Login, | ||||
|         2 => data.SecureNote, | ||||
| @@ -252,7 +286,7 @@ pub fn update_cipher_from_data(cipher: &mut Cipher, data: CipherData, headers: & | ||||
|  | ||||
|     match cipher.save(&conn) { | ||||
|         Ok(()) => (), | ||||
|         Err(_) => println!("Error: Failed to save cipher") | ||||
|         Err(_) => err!("Failed to save cipher") | ||||
|     }; | ||||
|     ws.send_cipher_update(ut, &cipher, &cipher.update_users_revision(&conn)); | ||||
|  | ||||
| @@ -299,7 +333,6 @@ fn post_ciphers_import(data: JsonUpcase<ImportData>, headers: Headers, conn: DbC | ||||
|     } | ||||
|  | ||||
|     // Read the relations between folders and ciphers | ||||
|     use std::collections::HashMap; | ||||
|     let mut relations_map = HashMap::new(); | ||||
|  | ||||
|     for relation in data.FolderRelationships { | ||||
| @@ -542,37 +575,52 @@ fn post_attachment(uuid: String, data: Data, content_type: &ContentType, headers | ||||
|  | ||||
|     let base_path = Path::new(&CONFIG.attachments_folder).join(&cipher.uuid); | ||||
|  | ||||
|     let mut attachment_key = None; | ||||
|  | ||||
|     Multipart::with_body(data.open(), boundary).foreach_entry(|mut field| { | ||||
|          // This is provided by the client, don't trust it | ||||
|          let name = field.headers.filename.expect("No filename provided"); | ||||
|  | ||||
|         let file_name = HEXLOWER.encode(&crypto::get_random(vec![0; 10])); | ||||
|         let path = base_path.join(&file_name); | ||||
|  | ||||
|         let size = match field.data.save() | ||||
|             .memory_threshold(0) | ||||
|             .size_limit(None) | ||||
|             .with_path(path) { | ||||
|             SaveResult::Full(SavedData::File(_, size)) => size as i32, | ||||
|             SaveResult::Full(other) => { | ||||
|                 println!("Attachment is not a file: {:?}", other); | ||||
|                 return; | ||||
|         match field.headers.name.as_str() { | ||||
|             "key" => { | ||||
|                 use std::io::Read; | ||||
|                 let mut key_buffer = String::new(); | ||||
|                 if field.data.read_to_string(&mut key_buffer).is_ok() { | ||||
|                     attachment_key = Some(key_buffer); | ||||
|                 } | ||||
|             }, | ||||
|             SaveResult::Partial(_, reason) => { | ||||
|                 println!("Partial result: {:?}", reason); | ||||
|                 return; | ||||
|             }, | ||||
|             SaveResult::Error(e) => { | ||||
|                 println!("Error: {:?}", e); | ||||
|                 return; | ||||
|             } | ||||
|         }; | ||||
|             "data" => {                 | ||||
|                 // This is provided by the client, don't trust it | ||||
|                 let name = field.headers.filename.expect("No filename provided"); | ||||
|  | ||||
|         let attachment = Attachment::new(file_name, cipher.uuid.clone(), name, size); | ||||
|         match attachment.save(&conn) { | ||||
|             Ok(()) => (), | ||||
|             Err(_) => println!("Error: failed to save attachment") | ||||
|         }; | ||||
|                 let file_name = HEXLOWER.encode(&crypto::get_random(vec![0; 10])); | ||||
|                 let path = base_path.join(&file_name); | ||||
|  | ||||
|                 let size = match field.data.save() | ||||
|                     .memory_threshold(0) | ||||
|                     .size_limit(None) | ||||
|                     .with_path(path) { | ||||
|                     SaveResult::Full(SavedData::File(_, size)) => size as i32, | ||||
|                     SaveResult::Full(other) => { | ||||
|                         println!("Attachment is not a file: {:?}", other); | ||||
|                         return; | ||||
|                     }, | ||||
|                     SaveResult::Partial(_, reason) => { | ||||
|                         println!("Partial result: {:?}", reason); | ||||
|                         return; | ||||
|                     }, | ||||
|                     SaveResult::Error(e) => { | ||||
|                         println!("Error: {:?}", e); | ||||
|                         return; | ||||
|                     } | ||||
|                 }; | ||||
|  | ||||
|                 let mut attachment = Attachment::new(file_name, cipher.uuid.clone(), name, size); | ||||
|                 attachment.key = attachment_key.clone(); | ||||
|                 match attachment.save(&conn) { | ||||
|                     Ok(()) => (), | ||||
|                     Err(_) => println!("Error: failed to save attachment") | ||||
|                 }; | ||||
|             }, | ||||
|             _ => println!("Error: invalid multipart name") | ||||
|         } | ||||
|     }).expect("Error processing multipart data"); | ||||
|  | ||||
|     Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn))) | ||||
| @@ -793,6 +841,6 @@ fn _delete_cipher_attachment_by_id(uuid: &str, attachment_id: &str, headers: &He | ||||
|             ws.send_cipher_update(UpdateType::SyncCipherDelete, &cipher, &cipher.update_users_revision(&conn)); | ||||
|             Ok(()) | ||||
|         } | ||||
|         Err(_) => err!("Deleting attachement failed") | ||||
|         Err(_) => err!("Deleting attachment failed") | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -12,6 +12,7 @@ pub struct Attachment { | ||||
|     pub cipher_uuid: String, | ||||
|     pub file_name: String, | ||||
|     pub file_size: i32, | ||||
|     pub key: Option<String> | ||||
| } | ||||
|  | ||||
| /// Local methods | ||||
| @@ -22,6 +23,7 @@ impl Attachment { | ||||
|             cipher_uuid, | ||||
|             file_name, | ||||
|             file_size, | ||||
|             key: None | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -41,6 +43,7 @@ impl Attachment { | ||||
|             "FileName": self.file_name, | ||||
|             "Size": self.file_size.to_string(), | ||||
|             "SizeName": display_size, | ||||
|             "Key": self.key, | ||||
|             "Object": "attachment" | ||||
|         }) | ||||
|     } | ||||
| @@ -92,8 +95,8 @@ impl Attachment { | ||||
|     } | ||||
|  | ||||
|     pub fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> QueryResult<()> { | ||||
|         for attachement in Attachment::find_by_cipher(&cipher_uuid, &conn) { | ||||
|             attachement.delete(&conn)?; | ||||
|         for attachment in Attachment::find_by_cipher(&cipher_uuid, &conn) { | ||||
|             attachment.delete(&conn)?; | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
|   | ||||
| @@ -71,7 +71,6 @@ impl Device { | ||||
|         let time_now = Utc::now().naive_utc(); | ||||
|         self.updated_at = time_now; | ||||
|  | ||||
|  | ||||
|         let orgowner: Vec<_> = orgs.iter().filter(|o| o.type_ == 0).map(|o| o.org_uuid.clone()).collect(); | ||||
|         let orgadmin: Vec<_> = orgs.iter().filter(|o| o.type_ == 1).map(|o| o.org_uuid.clone()).collect(); | ||||
|         let orguser: Vec<_> = orgs.iter().filter(|o| o.type_ == 2).map(|o| o.org_uuid.clone()).collect(); | ||||
|   | ||||
| @@ -4,6 +4,7 @@ table! { | ||||
|         cipher_uuid -> Text, | ||||
|         file_name -> Text, | ||||
|         file_size -> Integer, | ||||
|         key -> Nullable<Text>, | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user