diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index 93e06781..02aedc30 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -91,7 +91,7 @@ fn sync(data: Form, headers: Headers, conn: DbConn) -> JsonResult { let ciphers = Cipher::find_by_user(&headers.user.uuid, &conn); let ciphers_json: Vec = ciphers .iter() - .map(|c| c.to_json(&headers.host, &headers.user.uuid, &conn)) + .map(|c| c.to_json(&headers.host, &headers.user.uuid, &conn, "cipherDetails")) .collect(); let domains_json = if data.exclude_domains { @@ -117,7 +117,7 @@ fn get_ciphers(headers: Headers, conn: DbConn) -> JsonResult { let ciphers_json: Vec = ciphers .iter() - .map(|c| c.to_json(&headers.host, &headers.user.uuid, &conn)) + .map(|c| c.to_json(&headers.host, &headers.user.uuid, &conn, "cipherDetails")) .collect(); Ok(Json(json!({ @@ -129,6 +129,10 @@ fn get_ciphers(headers: Headers, conn: DbConn) -> JsonResult { #[get("/ciphers/")] fn get_cipher(uuid: String, headers: Headers, conn: DbConn) -> JsonResult { + _get_cipher(uuid, headers, conn, "cipher") +} + +fn _get_cipher(uuid: String, headers: Headers, conn: DbConn, resp_model: &str) -> JsonResult { let cipher = match Cipher::find_by_uuid(&uuid, &conn) { Some(cipher) => cipher, None => err!("Cipher doesn't exist"), @@ -138,18 +142,18 @@ fn get_cipher(uuid: String, headers: Headers, conn: DbConn) -> JsonResult { err!("Cipher is not owned by user") } - Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn))) + Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn, resp_model))) } #[get("/ciphers//admin")] fn get_cipher_admin(uuid: String, headers: Headers, conn: DbConn) -> JsonResult { // TODO: Implement this correctly - get_cipher(uuid, headers, conn) + _get_cipher(uuid, headers, conn, "cipherMini") } #[get("/ciphers//details")] fn get_cipher_details(uuid: String, headers: Headers, conn: DbConn) -> JsonResult { - get_cipher(uuid, headers, conn) + _get_cipher(uuid, headers, conn, "cipherDetails") } #[derive(Deserialize, Debug)] @@ -198,18 +202,22 @@ pub struct Attachments2Data { #[post("/ciphers/admin", data = "")] fn post_ciphers_admin(data: JsonUpcase, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult { + _post_ciphers_admin(data, headers, conn, nt, "cipherMini") +} + +fn _post_ciphers_admin(data: JsonUpcase, headers: Headers, conn: DbConn, nt: Notify, resp_model: &str) -> JsonResult { let data: ShareCipherData = data.into_inner().data; let mut cipher = Cipher::new(data.Cipher.Type, data.Cipher.Name.clone()); cipher.user_uuid = Some(headers.user.uuid.clone()); cipher.save(&conn)?; - share_cipher_by_uuid(&cipher.uuid, data, &headers, &conn, &nt) + share_cipher_by_uuid(&cipher.uuid, data, &headers, &conn, &nt, &resp_model) } #[post("/ciphers/create", data = "")] fn post_ciphers_create(data: JsonUpcase, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult { - post_ciphers_admin(data, headers, conn, nt) + _post_ciphers_admin(data, headers, conn, nt, "cipher") } #[post("/ciphers", data = "")] @@ -219,7 +227,7 @@ fn post_ciphers(data: JsonUpcase, headers: Headers, conn: DbConn, nt let mut cipher = Cipher::new(data.Type, data.Name.clone()); update_cipher_from_data(&mut cipher, data, &headers, false, &conn, &nt, UpdateType::CipherCreate)?; - Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn))) + Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn, "cipher"))) } pub fn update_cipher_from_data( @@ -385,7 +393,7 @@ fn put_cipher_admin( conn: DbConn, nt: Notify, ) -> JsonResult { - put_cipher(uuid, data, headers, conn, nt) + _put_cipher(uuid, data, headers, conn, nt, "cipherMini") } #[post("/ciphers//admin", data = "")] @@ -396,16 +404,20 @@ fn post_cipher_admin( conn: DbConn, nt: Notify, ) -> JsonResult { - post_cipher(uuid, data, headers, conn, nt) + _put_cipher(uuid, data, headers, conn, nt, "cipherMini") } #[post("/ciphers/", data = "")] fn post_cipher(uuid: String, data: JsonUpcase, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult { - put_cipher(uuid, data, headers, conn, nt) + _put_cipher(uuid, data, headers, conn, nt, "cipher") } #[put("/ciphers/", data = "")] fn put_cipher(uuid: String, data: JsonUpcase, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult { + _put_cipher(uuid, data, headers, conn, nt, "cipher") +} + +fn _put_cipher(uuid: String, data: JsonUpcase, headers: Headers, conn: DbConn, nt: Notify, resp_model: &str) -> JsonResult { let data: CipherData = data.into_inner().data; let mut cipher = match Cipher::find_by_uuid(&uuid, &conn) { @@ -419,7 +431,7 @@ fn put_cipher(uuid: String, data: JsonUpcase, headers: Headers, conn update_cipher_from_data(&mut cipher, data, &headers, false, &conn, &nt, UpdateType::CipherUpdate)?; - Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn))) + Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn, resp_model))) } #[derive(Deserialize)] @@ -522,7 +534,7 @@ fn post_cipher_share( ) -> JsonResult { let data: ShareCipherData = data.into_inner().data; - share_cipher_by_uuid(&uuid, data, &headers, &conn, &nt) + share_cipher_by_uuid(&uuid, data, &headers, &conn, &nt, "cipher") } #[put("/ciphers//share", data = "")] @@ -535,7 +547,7 @@ fn put_cipher_share( ) -> JsonResult { let data: ShareCipherData = data.into_inner().data; - share_cipher_by_uuid(&uuid, data, &headers, &conn, &nt) + share_cipher_by_uuid(&uuid, data, &headers, &conn, &nt, "cipher") } #[derive(Deserialize)] @@ -583,7 +595,7 @@ fn put_cipher_share_seleted( }; match shared_cipher_data.Cipher.Id.take() { - Some(id) => share_cipher_by_uuid(&id, shared_cipher_data, &headers, &conn, &nt)?, + Some(id) => share_cipher_by_uuid(&id, shared_cipher_data, &headers, &conn, &nt, "cipher")?, None => err!("Request missing ids field"), }; } @@ -597,6 +609,7 @@ fn share_cipher_by_uuid( headers: &Headers, conn: &DbConn, nt: &Notify, + resp_model: &str, ) -> JsonResult { let mut cipher = match Cipher::find_by_uuid(&uuid, &conn) { Some(cipher) => { @@ -643,7 +656,7 @@ fn share_cipher_by_uuid( UpdateType::CipherUpdate, )?; - Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn))) + Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn, resp_model))) } #[post("/ciphers//attachment", format = "multipart/form-data", data = "")] @@ -654,6 +667,18 @@ fn post_attachment( headers: Headers, conn: DbConn, nt: Notify, +) -> JsonResult { + _post_attachment(uuid, data, content_type, headers, conn, nt, "cipher") +} + +fn _post_attachment( + uuid: String, + data: Data, + content_type: &ContentType, + headers: Headers, + conn: DbConn, + nt: Notify, + resp_model: &str, ) -> JsonResult { let cipher = match Cipher::find_by_uuid(&uuid, &conn) { Some(cipher) => cipher, @@ -752,7 +777,7 @@ fn post_attachment( nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(&conn)); - Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn))) + Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn, &resp_model))) } #[post("/ciphers//attachment-admin", format = "multipart/form-data", data = "")] @@ -764,7 +789,7 @@ fn post_attachment_admin( conn: DbConn, nt: Notify, ) -> JsonResult { - post_attachment(uuid, data, content_type, headers, conn, nt) + _post_attachment(uuid, data, content_type, headers, conn, nt, "cipherMini") } #[post("/ciphers//attachment//share", format = "multipart/form-data", data = "")] @@ -778,7 +803,7 @@ fn post_attachment_share( nt: Notify, ) -> JsonResult { _delete_cipher_attachment_by_id(&uuid, &attachment_id, &headers, &conn, &nt)?; - post_attachment(uuid, data, content_type, headers, conn, nt) + _post_attachment(uuid, data, content_type, headers, conn, nt, "cipher") } #[post("/ciphers//attachment//delete-admin")] diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index cdbaebd0..1557cf88 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -425,7 +425,7 @@ fn get_org_details(data: Form, headers: Headers, conn: DbConn) -> Jso let ciphers = Cipher::find_by_org(&data.organization_id, &conn); let ciphers_json: Vec = ciphers .iter() - .map(|c| c.to_json(&headers.host, &headers.user.uuid, &conn)) + .map(|c| c.to_json(&headers.host, &headers.user.uuid, &conn, "cipherMiniDetails")) .collect(); Ok(Json(json!({ diff --git a/src/db/models/cipher.rs b/src/db/models/cipher.rs index 1e717ca4..0b9b4e78 100644 --- a/src/db/models/cipher.rs +++ b/src/db/models/cipher.rs @@ -73,49 +73,77 @@ use crate::error::MapResult; /// Database methods impl Cipher { - pub fn to_json(&self, host: &str, user_uuid: &str, conn: &DbConn) -> Value { + pub fn to_json(&self, host: &str, user_uuid: &str, conn: &DbConn, resp_model: &str) -> Value { use crate::util::format_date; let attachments = Attachment::find_by_cipher(&self.uuid, conn); - let attachments_json: Vec = attachments.iter().map(|c| c.to_json(host)).collect(); + let attachments_json = if attachments.is_empty() {Value::Null} else {attachments.iter().map(|c| c.to_json(host)).collect()}; - let fields_json = self.fields.as_ref().and_then(|s| serde_json::from_str(s).ok()).unwrap_or(Value::Null); + let mut fields_json = self.fields.as_ref().and_then(|s| serde_json::from_str(s).ok()).unwrap_or(Value::Null); let password_history_json = self.password_history.as_ref().and_then(|s| serde_json::from_str(s).ok()).unwrap_or(Value::Null); - // Get the data or a default empty value to avoid issues with the mobile apps + // Get the data or a default empty value of common items to avoid issues with the mobile apps let mut data_json: Value = serde_json::from_str(&self.data).unwrap_or_else(|_| json!({ - "Fields":null, + "Fields": null, "Name": self.name, - "Notes":null, - "Password":null, - "PasswordHistory":null, - "PasswordRevisionDate":null, - "Response":null, - "Totp":null, - "Uris":null, - "Username":null + "Notes": null, + "PasswordHistory": null })); // TODO: ******* Backwards compat start ********** // To remove backwards compatibility, just remove this entire section // and remove the compat code from ciphers::update_cipher_from_data - if self.atype == 1 && data_json["Uris"].is_array() { - let uri = data_json["Uris"][0]["Uri"].clone(); - data_json["Uri"] = uri; + if self.atype == 1 && (data_json["Uris"].is_array() || data_json["Uris"].is_null()) { + data_json["Uri"] = data_json["Uris"][0]["Uri"].clone(); } // TODO: ******* Backwards compat end ********** + // Make sure a SecureNote doesn't end up null + if self.atype == 2 && !data_json["Type"].is_i64() { + data_json["Type"] = json!(0); + } + + // Do not include "Response" in data_json + data_json.as_object_mut().unwrap().remove("Response"); + + // Do not include "Response" in data_json.Fields + if data_json["Fields"].is_array() { + data_json["Fields"].as_array_mut() + .unwrap() + .iter_mut() + .for_each(|ref mut f| { + f.as_object_mut().unwrap().remove("Response"); + }); + }; + + // Do not include "Response" in fields_json + if fields_json.is_array() { + fields_json.as_array_mut() + .unwrap() + .iter_mut() + .for_each(|ref mut f| { + f.as_object_mut().unwrap().remove("Response"); + }); + }; + + // Do not include "Response" in Uris + if data_json["Uris"].is_array() { + data_json["Uris"].as_array_mut() + .unwrap() + .iter_mut() + .for_each(|ref mut f| { + f.as_object_mut().unwrap().remove("Response"); + }); + }; + let mut json_object = json!({ "Id": self.uuid, "Type": self.atype, "RevisionDate": format_date(&self.updated_at), "DeletedDate": self.deleted_at.map_or(Value::Null, |d| Value::String(format_date(&d))), - "FolderId": self.get_folder_uuid(&user_uuid, &conn), - "Favorite": self.favorite, "OrganizationId": self.organization_uuid, "Attachments": attachments_json, - "OrganizationUseTotp": true, - "CollectionIds": self.get_collections(user_uuid, &conn), + "OrganizationUseTotp": self.organization_uuid.is_some(), "Name": self.name, "Notes": self.notes, @@ -123,12 +151,33 @@ impl Cipher { "Data": data_json, - "Object": "cipher", - "Edit": true, + "Object": resp_model, "PasswordHistory": password_history_json, + + // These are all included, but only gets populated + "Login": null, + "Card": null, + "Identity": null, + "SecureNote": null, }); + // Add additional items based on the requested response model + if resp_model == "cipher" || resp_model == "cipherDetails" { + json_object["FolderId"] = json!(self.get_folder_uuid(&user_uuid, &conn)); + json_object["Favorite"] = json!(self.favorite); + json_object["Edit"] = json!(self.is_write_accessible_to_user(user_uuid, &conn)); + } + if resp_model == "cipherDetails" || resp_model == "cipherMiniDetails" { + json_object["CollectionIds"] = json!(self.get_collections(user_uuid, &conn)); + } + + // data_json is reused for the json_object[key] value, but do not include the backwards compatibility items + data_json.as_object_mut().unwrap().remove("Fields"); + data_json.as_object_mut().unwrap().remove("Name"); + data_json.as_object_mut().unwrap().remove("Notes"); + data_json.as_object_mut().unwrap().remove("PasswordHistory"); + let key = match self.atype { 1 => "Login", 2 => "SecureNote",