Fix bulk cipher share response for SDK clients (#7831)

* Fix bulk cipher share response for SDK clients

* Simplify cipher sharing code
This commit is contained in:
Tom authored and GitHub committed 2026-10-25 16:49:20 +01:00
1 parent 7ce705be56
commit e5a90968f2
1 file changed
+60 -6
+60 -6
View File
@@ -1018,14 +1018,53 @@ struct ShareSelectedCipherData {
collection_ids: Vec<CollectionId>,
}
// A bulk share returns cipherMini models, not the cipherDetails models used by
// the single-share route. Keep the encrypted legacy fields and a future v2
// `data` blob, but do not expose details-only fields in this response.
fn cipher_mini_from_json(mut cipher: Value) -> Value {
const FIELDS: &[&str] = &[
"id",
"organizationId",
"type",
"data",
"partialData",
"name",
"notes",
"login",
"card",
"identity",
"secureNote",
"sshKey",
"bankAccount",
"driversLicense",
"passport",
"fields",
"passwordHistory",
"attachments",
"organizationUseTotp",
"revisionDate",
"creationDate",
"deletedDate",
"reprompt",
"key",
];
let Some(object) = cipher.as_object_mut() else {
return json!({ "object": "cipherMini" });
};
object.retain(|field, _| FIELDS.contains(&field.as_str()));
object.insert("object".into(), "cipherMini".into());
cipher
}
#[put("/ciphers/share", data = "<data>")]
async fn put_cipher_share_selected(
data: Json<ShareSelectedCipherData>,
headers: Headers,
conn: DbConn,
nt: Notify<'_>,
) -> EmptyResult {
let mut data: ShareSelectedCipherData = data.into_inner();
) -> JsonResult {
let data: ShareSelectedCipherData = data.into_inner();
if data.ciphers.is_empty() {
err!("You must select at least one cipher.")
@@ -1041,23 +1080,38 @@ async fn put_cipher_share_selected(
}
}
while let Some(cipher) = data.ciphers.pop() {
let mut responses = Vec::with_capacity(data.ciphers.len());
for cipher in data.ciphers {
let mut shared_cipher_data = ShareCipherData {
cipher,
collection_ids: data.collection_ids.clone(),
};
if let Some(id) = shared_cipher_data.cipher.id.take() {
share_cipher_by_uuid(&id, shared_cipher_data, &headers, &conn, &nt, Some(UpdateType::None)).await?
let response =
share_cipher_by_uuid(&id, shared_cipher_data, &headers, &conn, &nt, Some(UpdateType::None)).await?;
// Like upstream's VisibleToClient, withhold a cipher if sharing it
// removed the user's access to it. Only then include its encrypted
// mini response; the full single-share response stays internal.
if let Some(shared_cipher) = Cipher::find_by_uuid(&id, &conn).await
&& shared_cipher.is_accessible_to_user(&headers.user.uuid, &conn).await
{
responses.push(cipher_mini_from_json(response.into_inner()));
}
} else {
err!("Request missing ids field")
};
}
}
// Multi share actions do not send out a push for each cipher, we need to send a general sync here
nt.send_user_update(UpdateType::SyncCiphers, &headers.user, headers.device.push_uuid.as_ref(), &conn).await;
Ok(())
Ok(Json(json!({
"data": responses,
"object": "list",
"continuationToken": null,
})))
}
async fn share_cipher_by_uuid(