0
0
Fork 0
mirror of https://github.com/dani-garcia/vaultwarden synced 2024-12-14 09:33:44 +01:00

Fix attachments during key rotation, add individual attachment key

This commit is contained in:
Daniel García 2018-11-27 17:24:12 +01:00
parent f71f10eac6
commit 6364c05789
No known key found for this signature in database
GPG key ID: FC8A7D14C3CD543A
6 changed files with 89 additions and 35 deletions

View file

@ -0,0 +1,3 @@
ALTER TABLE attachments
ADD COLUMN
key TEXT;

View file

@ -1,4 +1,4 @@
use std::collections::HashSet; use std::collections::{HashSet, HashMap};
use std::path::Path; use std::path::Path;
use rocket::http::ContentType; use rocket::http::ContentType;
@ -162,6 +162,18 @@ pub struct CipherData {
Favorite: Option<bool>, Favorite: Option<bool>,
PasswordHistory: Option<Value>, 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>")] #[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 { let type_data_opt = match data.Type {
1 => data.Login, 1 => data.Login,
2 => data.SecureNote, 2 => data.SecureNote,
@ -252,7 +286,7 @@ pub fn update_cipher_from_data(cipher: &mut Cipher, data: CipherData, headers: &
match cipher.save(&conn) { match cipher.save(&conn) {
Ok(()) => (), 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)); 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 // Read the relations between folders and ciphers
use std::collections::HashMap;
let mut relations_map = HashMap::new(); let mut relations_map = HashMap::new();
for relation in data.FolderRelationships { 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 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| { Multipart::with_body(data.open(), boundary).foreach_entry(|mut field| {
// This is provided by the client, don't trust it match field.headers.name.as_str() {
let name = field.headers.filename.expect("No filename provided"); "key" => {
use std::io::Read;
let file_name = HEXLOWER.encode(&crypto::get_random(vec![0; 10])); let mut key_buffer = String::new();
let path = base_path.join(&file_name); if field.data.read_to_string(&mut key_buffer).is_ok() {
attachment_key = Some(key_buffer);
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) => { "data" => {
println!("Partial result: {:?}", reason); // This is provided by the client, don't trust it
return; let name = field.headers.filename.expect("No filename provided");
},
SaveResult::Error(e) => {
println!("Error: {:?}", e);
return;
}
};
let attachment = Attachment::new(file_name, cipher.uuid.clone(), name, size); let file_name = HEXLOWER.encode(&crypto::get_random(vec![0; 10]));
match attachment.save(&conn) { let path = base_path.join(&file_name);
Ok(()) => (),
Err(_) => println!("Error: failed to save attachment") 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"); }).expect("Error processing multipart data");
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn))) 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)); ws.send_cipher_update(UpdateType::SyncCipherDelete, &cipher, &cipher.update_users_revision(&conn));
Ok(()) Ok(())
} }
Err(_) => err!("Deleting attachement failed") Err(_) => err!("Deleting attachment failed")
} }
} }

View file

@ -12,6 +12,7 @@ pub struct Attachment {
pub cipher_uuid: String, pub cipher_uuid: String,
pub file_name: String, pub file_name: String,
pub file_size: i32, pub file_size: i32,
pub key: Option<String>
} }
/// Local methods /// Local methods
@ -22,6 +23,7 @@ impl Attachment {
cipher_uuid, cipher_uuid,
file_name, file_name,
file_size, file_size,
key: None
} }
} }
@ -41,6 +43,7 @@ impl Attachment {
"FileName": self.file_name, "FileName": self.file_name,
"Size": self.file_size.to_string(), "Size": self.file_size.to_string(),
"SizeName": display_size, "SizeName": display_size,
"Key": self.key,
"Object": "attachment" "Object": "attachment"
}) })
} }
@ -92,8 +95,8 @@ impl Attachment {
} }
pub fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> QueryResult<()> { pub fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> QueryResult<()> {
for attachement in Attachment::find_by_cipher(&cipher_uuid, &conn) { for attachment in Attachment::find_by_cipher(&cipher_uuid, &conn) {
attachement.delete(&conn)?; attachment.delete(&conn)?;
} }
Ok(()) Ok(())
} }

View file

@ -71,7 +71,6 @@ impl Device {
let time_now = Utc::now().naive_utc(); let time_now = Utc::now().naive_utc();
self.updated_at = time_now; self.updated_at = time_now;
let orgowner: Vec<_> = orgs.iter().filter(|o| o.type_ == 0).map(|o| o.org_uuid.clone()).collect(); 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 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(); let orguser: Vec<_> = orgs.iter().filter(|o| o.type_ == 2).map(|o| o.org_uuid.clone()).collect();

View file

@ -4,6 +4,7 @@ table! {
cipher_uuid -> Text, cipher_uuid -> Text,
file_name -> Text, file_name -> Text,
file_size -> Integer, file_size -> Integer,
key -> Nullable<Text>,
} }
} }