|
|
@ -1018,14 +1018,53 @@ struct ShareSelectedCipherData { |
|
|
collection_ids: Vec<CollectionId>, |
|
|
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>")] |
|
|
#[put("/ciphers/share", data = "<data>")] |
|
|
async fn put_cipher_share_selected( |
|
|
async fn put_cipher_share_selected( |
|
|
data: Json<ShareSelectedCipherData>, |
|
|
data: Json<ShareSelectedCipherData>, |
|
|
headers: Headers, |
|
|
headers: Headers, |
|
|
conn: DbConn, |
|
|
conn: DbConn, |
|
|
nt: Notify<'_>, |
|
|
nt: Notify<'_>, |
|
|
) -> EmptyResult { |
|
|
) -> JsonResult { |
|
|
let mut data: ShareSelectedCipherData = data.into_inner(); |
|
|
let data: ShareSelectedCipherData = data.into_inner(); |
|
|
|
|
|
|
|
|
if data.ciphers.is_empty() { |
|
|
if data.ciphers.is_empty() { |
|
|
err!("You must select at least one cipher.") |
|
|
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 { |
|
|
let mut shared_cipher_data = ShareCipherData { |
|
|
cipher, |
|
|
cipher, |
|
|
collection_ids: data.collection_ids.clone(), |
|
|
collection_ids: data.collection_ids.clone(), |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
if let Some(id) = shared_cipher_data.cipher.id.take() { |
|
|
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 { |
|
|
} else { |
|
|
err!("Request missing ids field") |
|
|
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
|
|
|
// 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; |
|
|
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( |
|
|
async fn share_cipher_by_uuid( |
|
|
|