diff --git a/migrations/2018-11-27-152651_add_att_key_columns/down.sql b/migrations/2018-11-27-152651_add_att_key_columns/down.sql new file mode 100644 index 0000000..e69de29 diff --git a/migrations/2018-11-27-152651_add_att_key_columns/up.sql b/migrations/2018-11-27-152651_add_att_key_columns/up.sql new file mode 100644 index 0000000..4a73a8c --- /dev/null +++ b/migrations/2018-11-27-152651_add_att_key_columns/up.sql @@ -0,0 +1,3 @@ +ALTER TABLE attachments + ADD COLUMN + key TEXT; \ No newline at end of file diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index f812bab..d148017 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -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, PasswordHistory: Option, + + // These are used during key rotation + #[serde(rename = "Attachments")] + _Attachments: Option, // Unused, contains map of {id: filename} + Attachments2: Option> +} + +#[derive(Deserialize, Debug)] +#[allow(non_snake_case)] +pub struct Attachments2Data { + FileName: String, + Key: String, } #[post("/ciphers/admin", 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, 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") } } diff --git a/src/db/models/attachment.rs b/src/db/models/attachment.rs index 4e2038d..97a4d75 100644 --- a/src/db/models/attachment.rs +++ b/src/db/models/attachment.rs @@ -12,6 +12,7 @@ pub struct Attachment { pub cipher_uuid: String, pub file_name: String, pub file_size: i32, + pub key: Option } /// 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(()) } diff --git a/src/db/models/device.rs b/src/db/models/device.rs index 22b26e5..ca99837 100644 --- a/src/db/models/device.rs +++ b/src/db/models/device.rs @@ -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(); diff --git a/src/db/schema.rs b/src/db/schema.rs index 49880fe..4816853 100644 --- a/src/db/schema.rs +++ b/src/db/schema.rs @@ -4,6 +4,7 @@ table! { cipher_uuid -> Text, file_name -> Text, file_size -> Integer, + key -> Nullable, } }