From 4e60df7a080872487945039860bc9ca3cb8d3225 Mon Sep 17 00:00:00 2001 From: Jeremy Lin Date: Thu, 10 Dec 2020 00:17:34 -0800 Subject: [PATCH] Fix stale data check failure when cloning a cipher --- src/api/core/ciphers.rs | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index 1948e98d..0136ca79 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -49,7 +49,7 @@ pub fn routes() -> Vec { post_cipher_admin, post_cipher_share, put_cipher_share, - put_cipher_share_seleted, + put_cipher_share_selected, post_cipher, put_cipher, delete_cipher_post, @@ -212,22 +212,35 @@ pub struct Attachments2Data { Key: String, } +/// Called when an org admin clones an org cipher. #[post("/ciphers/admin", data = "")] fn post_ciphers_admin(data: JsonUpcase, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult { - let data: ShareCipherData = data.into_inner().data; + post_ciphers_create(data, headers, conn, nt) +} + +/// Called when creating a new org-owned cipher, or cloning a cipher (whether +/// user- or org-owned). When cloning a cipher to a user-owned cipher, +/// `organizationId` is null. +#[post("/ciphers/create", data = "")] +fn post_ciphers_create(data: JsonUpcase, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult { + let mut 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) -} + // When cloning a cipher, the Bitwarden clients seem to set this field + // based on the cipher being cloned (when creating a new cipher, it's set + // to null as expected). However, `cipher.created_at` is initialized to + // the current time, so the stale data check will end up failing down the + // line. Since this function only creates new ciphers (whether by cloning + // or otherwise), we can just ignore this field entirely. + data.Cipher.LastKnownRevisionDate = None; -#[post("/ciphers/create", data = "")] -fn post_ciphers_create(data: JsonUpcase, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult { - post_ciphers_admin(data, headers, conn, nt) + share_cipher_by_uuid(&cipher.uuid, data, &headers, &conn, &nt) } +/// Called when creating a new user-owned cipher. #[post("/ciphers", data = "")] fn post_ciphers(data: JsonUpcase, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult { let data: CipherData = data.into_inner().data; @@ -407,6 +420,7 @@ fn post_ciphers_import(data: JsonUpcase, headers: Headers, conn: DbC Ok(()) } +/// Called when an org admin modifies an existing org cipher. #[put("/ciphers//admin", data = "")] fn put_cipher_admin( uuid: String, @@ -581,7 +595,7 @@ struct ShareSelectedCipherData { } #[put("/ciphers/share", data = "")] -fn put_cipher_share_seleted( +fn put_cipher_share_selected( data: JsonUpcase, headers: Headers, conn: DbConn,