|
|
@ -666,6 +666,9 @@ async fn post_ciphers_import(data: Json<ImportData>, headers: Headers, conn: DbC |
|
|
for (index, mut cipher_data) in data.ciphers.into_iter().enumerate() { |
|
|
for (index, mut cipher_data) in data.ciphers.into_iter().enumerate() { |
|
|
let folder_id = relations_map.get(&index).and_then(|i| folders.get(*i).cloned()); |
|
|
let folder_id = relations_map.get(&index).and_then(|i| folders.get(*i).cloned()); |
|
|
cipher_data.folder_id = folder_id; |
|
|
cipher_data.folder_id = folder_id; |
|
|
|
|
|
// A personal import always creates personal ciphers, the organization ID is ignored instead of rejected
|
|
|
|
|
|
// Upstream: https://github.com/bitwarden/server/blob/b06b81c1fb33ce9c9872b96397ba2b4d879b66af/src/Api/Tools/Controllers/ImportCiphersController.cs#L58
|
|
|
|
|
|
cipher_data.organization_id = None; |
|
|
|
|
|
|
|
|
let mut cipher = Cipher::new(cipher_data.r#type, cipher_data.name.clone()); |
|
|
let mut cipher = Cipher::new(cipher_data.r#type, cipher_data.name.clone()); |
|
|
update_cipher_from_data(&mut cipher, cipher_data, &headers, None, &conn, &nt, UpdateType::None).await?; |
|
|
update_cipher_from_data(&mut cipher, cipher_data, &headers, None, &conn, &nt, UpdateType::None).await?; |
|
|
@ -687,6 +690,11 @@ async fn put_cipher_admin( |
|
|
conn: DbConn, |
|
|
conn: DbConn, |
|
|
nt: Notify<'_>, |
|
|
nt: Notify<'_>, |
|
|
) -> JsonResult { |
|
|
) -> JsonResult { |
|
|
|
|
|
// Only organization ciphers can be edited here, put_cipher() rejects an organization ID that isn't the cipher's
|
|
|
|
|
|
if data.organization_id.is_none() { |
|
|
|
|
|
err_code!("Resource not found.", "Cipher is not an organization cipher", Status::NotFound.code); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
put_cipher(cipher_id, data, headers, conn, nt).await |
|
|
put_cipher(cipher_id, data, headers, conn, nt).await |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -698,7 +706,7 @@ async fn post_cipher_admin( |
|
|
conn: DbConn, |
|
|
conn: DbConn, |
|
|
nt: Notify<'_>, |
|
|
nt: Notify<'_>, |
|
|
) -> JsonResult { |
|
|
) -> JsonResult { |
|
|
post_cipher(cipher_id, data, headers, conn, nt).await |
|
|
put_cipher_admin(cipher_id, data, headers, conn, nt).await |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
#[post("/ciphers/<cipher_id>", data = "<data>")] |
|
|
#[post("/ciphers/<cipher_id>", data = "<data>")] |
|
|
@ -735,6 +743,11 @@ async fn put_cipher( |
|
|
err!("Cipher is not write accessible") |
|
|
err!("Cipher is not write accessible") |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Moving a cipher into an organization is only possible through the share endpoints
|
|
|
|
|
|
if cipher.organization_uuid != data.organization_id { |
|
|
|
|
|
err!("Organization mismatch. Please resync the client before updating the cipher") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
update_cipher_from_data(&mut cipher, data, &headers, None, &conn, &nt, UpdateType::SyncCipherUpdate).await?; |
|
|
update_cipher_from_data(&mut cipher, data, &headers, None, &conn, &nt, UpdateType::SyncCipherUpdate).await?; |
|
|
|
|
|
|
|
|
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::User, &conn).await?)) |
|
|
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::User, &conn).await?)) |
|
|
@ -993,9 +1006,7 @@ async fn post_cipher_share( |
|
|
conn: DbConn, |
|
|
conn: DbConn, |
|
|
nt: Notify<'_>, |
|
|
nt: Notify<'_>, |
|
|
) -> JsonResult { |
|
|
) -> JsonResult { |
|
|
let data: ShareCipherData = data.into_inner(); |
|
|
put_cipher_share(cipher_id, data, headers, conn, nt).await |
|
|
|
|
|
|
|
|
share_cipher_by_uuid(&cipher_id, data, &headers, &conn, &nt, None).await |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
#[put("/ciphers/<cipher_id>/share", data = "<data>")] |
|
|
#[put("/ciphers/<cipher_id>/share", data = "<data>")] |
|
|
@ -1008,6 +1019,14 @@ async fn put_cipher_share( |
|
|
) -> JsonResult { |
|
|
) -> JsonResult { |
|
|
let data: ShareCipherData = data.into_inner(); |
|
|
let data: ShareCipherData = data.into_inner(); |
|
|
|
|
|
|
|
|
|
|
|
if data.cipher.organization_id.is_none() { |
|
|
|
|
|
err!("Cipher OrganizationId is required.") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if data.collection_ids.is_empty() { |
|
|
|
|
|
err!("You must select at least one collection.") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
share_cipher_by_uuid(&cipher_id, data, &headers, &conn, &nt, None).await |
|
|
share_cipher_by_uuid(&cipher_id, data, &headers, &conn, &nt, None).await |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -1078,6 +1097,9 @@ async fn put_cipher_share_selected( |
|
|
if cipher.id.is_none() { |
|
|
if cipher.id.is_none() { |
|
|
err!("Request missing ids field") |
|
|
err!("Request missing ids field") |
|
|
} |
|
|
} |
|
|
|
|
|
if cipher.organization_id.is_none() { |
|
|
|
|
|
err!("Cipher OrganizationId is required.") |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
let mut responses = Vec::with_capacity(data.ciphers.len()); |
|
|
let mut responses = Vec::with_capacity(data.ciphers.len()); |
|
|
@ -1258,6 +1280,7 @@ async fn post_attachment_v2( |
|
|
data: Json<AttachmentRequestData>, |
|
|
data: Json<AttachmentRequestData>, |
|
|
headers: Headers, |
|
|
headers: Headers, |
|
|
conn: DbConn, |
|
|
conn: DbConn, |
|
|
|
|
|
nt: Notify<'_>, |
|
|
) -> JsonResult { |
|
|
) -> JsonResult { |
|
|
let Some(cipher) = Cipher::find_by_uuid(&cipher_id, &conn).await else { |
|
|
let Some(cipher) = Cipher::find_by_uuid(&cipher_id, &conn).await else { |
|
|
err!("Cipher doesn't exist") |
|
|
err!("Cipher doesn't exist") |
|
|
@ -1288,6 +1311,7 @@ async fn post_attachment_v2( |
|
|
let attachment = |
|
|
let attachment = |
|
|
Attachment::new(attachment_id.clone(), cipher.uuid.clone(), data.file_name, file_size, Some(data.key)); |
|
|
Attachment::new(attachment_id.clone(), cipher.uuid.clone(), data.file_name, file_size, Some(data.key)); |
|
|
attachment.save(&conn).await.expect("Error saving attachment"); |
|
|
attachment.save(&conn).await.expect("Error saving attachment"); |
|
|
|
|
|
notify_attachment_created(&cipher, &headers, &conn, &nt).await; |
|
|
|
|
|
|
|
|
let url = format!("/ciphers/{}/attachment/{attachment_id}", cipher.uuid); |
|
|
let url = format!("/ciphers/{}/attachment/{attachment_id}", cipher.uuid); |
|
|
let response_key = match data.admin_request { |
|
|
let response_key = match data.admin_request { |
|
|
@ -1407,13 +1431,23 @@ async fn save_attachment( |
|
|
|
|
|
|
|
|
save_temp_file(&PathType::Attachments, &format!("{cipher_id}/{file_id}"), data.data, true).await?; |
|
|
save_temp_file(&PathType::Attachments, &format!("{cipher_id}/{file_id}"), data.data, true).await?; |
|
|
|
|
|
|
|
|
|
|
|
// With the v2 API, post_attachment_v2() already did this when it created the attachment record
|
|
|
|
|
|
if attachment.is_none() { |
|
|
|
|
|
notify_attachment_created(&cipher, headers, &conn, &nt).await; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Ok((cipher, conn)) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// Sends the cipher update to the clients and logs the attachment event
|
|
|
|
|
|
async fn notify_attachment_created(cipher: &Cipher, headers: &Headers, conn: &DbConn, nt: &Notify<'_>) { |
|
|
nt.send_cipher_update( |
|
|
nt.send_cipher_update( |
|
|
UpdateType::SyncCipherUpdate, |
|
|
UpdateType::SyncCipherUpdate, |
|
|
&cipher, |
|
|
cipher, |
|
|
&cipher.update_users_revision(&conn).await, |
|
|
&cipher.update_users_revision(conn).await, |
|
|
&headers.device, |
|
|
&headers.device, |
|
|
None, |
|
|
None, |
|
|
&conn, |
|
|
conn, |
|
|
) |
|
|
) |
|
|
.await; |
|
|
.await; |
|
|
|
|
|
|
|
|
@ -1425,12 +1459,10 @@ async fn save_attachment( |
|
|
&headers.user.uuid, |
|
|
&headers.user.uuid, |
|
|
headers.device.atype, |
|
|
headers.device.atype, |
|
|
&headers.ip.ip, |
|
|
&headers.ip.ip, |
|
|
&conn, |
|
|
conn, |
|
|
) |
|
|
) |
|
|
.await; |
|
|
.await; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
Ok((cipher, conn)) |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// v2 API for uploading the actual data content of an attachment.
|
|
|
/// v2 API for uploading the actual data content of an attachment.
|
|
|
|