mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2025-09-12 03:25:58 +03:00
Fix several more multi select push issues (#6151)
* Fix several more multi select push issues There were some more items which would still overload the push endpoint. This PR fixes the remaining items (I hope). I also encountered a missing endpoint for restoring multiple ciphers from the trash via the admin console. Overall, we could improve a lot of these items in a different way. Like bundle all SQL Queries etc... But that takes more time, and this fixes overloading the Bitwarden push servers, and speeds up these specific actions. Signed-off-by: BlackDex <black.dex@gmail.com> * Update src/api/core/ciphers.rs Co-authored-by: Daniel García <dani-garcia@users.noreply.github.com> --------- Signed-off-by: BlackDex <black.dex@gmail.com> Co-authored-by: Daniel García <dani-garcia@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
8fd0ee4211
commit
2a5489a4b2
@@ -78,6 +78,7 @@ pub fn routes() -> Vec<Route> {
|
|||||||
restore_cipher_put,
|
restore_cipher_put,
|
||||||
restore_cipher_put_admin,
|
restore_cipher_put_admin,
|
||||||
restore_cipher_selected,
|
restore_cipher_selected,
|
||||||
|
restore_cipher_selected_admin,
|
||||||
delete_all,
|
delete_all,
|
||||||
move_cipher_selected,
|
move_cipher_selected,
|
||||||
move_cipher_selected_put,
|
move_cipher_selected_put,
|
||||||
@@ -318,7 +319,7 @@ async fn post_ciphers_create(
|
|||||||
// or otherwise), we can just ignore this field entirely.
|
// or otherwise), we can just ignore this field entirely.
|
||||||
data.cipher.last_known_revision_date = None;
|
data.cipher.last_known_revision_date = None;
|
||||||
|
|
||||||
share_cipher_by_uuid(&cipher.uuid, data, &headers, &mut conn, &nt).await
|
share_cipher_by_uuid(&cipher.uuid, data, &headers, &mut conn, &nt, None).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when creating a new user-owned cipher.
|
/// Called when creating a new user-owned cipher.
|
||||||
@@ -920,7 +921,7 @@ async fn post_cipher_share(
|
|||||||
) -> JsonResult {
|
) -> JsonResult {
|
||||||
let data: ShareCipherData = data.into_inner();
|
let data: ShareCipherData = data.into_inner();
|
||||||
|
|
||||||
share_cipher_by_uuid(&cipher_id, data, &headers, &mut conn, &nt).await
|
share_cipher_by_uuid(&cipher_id, data, &headers, &mut conn, &nt, None).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[put("/ciphers/<cipher_id>/share", data = "<data>")]
|
#[put("/ciphers/<cipher_id>/share", data = "<data>")]
|
||||||
@@ -933,7 +934,7 @@ async fn put_cipher_share(
|
|||||||
) -> JsonResult {
|
) -> JsonResult {
|
||||||
let data: ShareCipherData = data.into_inner();
|
let data: ShareCipherData = data.into_inner();
|
||||||
|
|
||||||
share_cipher_by_uuid(&cipher_id, data, &headers, &mut conn, &nt).await
|
share_cipher_by_uuid(&cipher_id, data, &headers, &mut conn, &nt, None).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@@ -973,11 +974,16 @@ async fn put_cipher_share_selected(
|
|||||||
};
|
};
|
||||||
|
|
||||||
match shared_cipher_data.cipher.id.take() {
|
match shared_cipher_data.cipher.id.take() {
|
||||||
Some(id) => share_cipher_by_uuid(&id, shared_cipher_data, &headers, &mut conn, &nt).await?,
|
Some(id) => {
|
||||||
|
share_cipher_by_uuid(&id, shared_cipher_data, &headers, &mut conn, &nt, Some(UpdateType::None)).await?
|
||||||
|
}
|
||||||
None => err!("Request missing ids field"),
|
None => 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, &mut conn).await;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -987,6 +993,7 @@ async fn share_cipher_by_uuid(
|
|||||||
headers: &Headers,
|
headers: &Headers,
|
||||||
conn: &mut DbConn,
|
conn: &mut DbConn,
|
||||||
nt: &Notify<'_>,
|
nt: &Notify<'_>,
|
||||||
|
override_ut: Option<UpdateType>,
|
||||||
) -> JsonResult {
|
) -> JsonResult {
|
||||||
let mut cipher = match Cipher::find_by_uuid(cipher_id, conn).await {
|
let mut cipher = match Cipher::find_by_uuid(cipher_id, conn).await {
|
||||||
Some(cipher) => {
|
Some(cipher) => {
|
||||||
@@ -1018,7 +1025,10 @@ async fn share_cipher_by_uuid(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// When LastKnownRevisionDate is None, it is a new cipher, so send CipherCreate.
|
// When LastKnownRevisionDate is None, it is a new cipher, so send CipherCreate.
|
||||||
let ut = if data.cipher.last_known_revision_date.is_some() {
|
// If there is an override, like when handling multiple items, we want to prevent a push notification for every single item
|
||||||
|
let ut = if let Some(ut) = override_ut {
|
||||||
|
ut
|
||||||
|
} else if data.cipher.last_known_revision_date.is_some() {
|
||||||
UpdateType::SyncCipherUpdate
|
UpdateType::SyncCipherUpdate
|
||||||
} else {
|
} else {
|
||||||
UpdateType::SyncCipherCreate
|
UpdateType::SyncCipherCreate
|
||||||
@@ -1517,7 +1527,7 @@ async fn delete_cipher_selected_put_admin(
|
|||||||
|
|
||||||
#[put("/ciphers/<cipher_id>/restore")]
|
#[put("/ciphers/<cipher_id>/restore")]
|
||||||
async fn restore_cipher_put(cipher_id: CipherId, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
async fn restore_cipher_put(cipher_id: CipherId, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
||||||
_restore_cipher_by_uuid(&cipher_id, &headers, &mut conn, &nt).await
|
_restore_cipher_by_uuid(&cipher_id, &headers, false, &mut conn, &nt).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[put("/ciphers/<cipher_id>/restore-admin")]
|
#[put("/ciphers/<cipher_id>/restore-admin")]
|
||||||
@@ -1527,7 +1537,17 @@ async fn restore_cipher_put_admin(
|
|||||||
mut conn: DbConn,
|
mut conn: DbConn,
|
||||||
nt: Notify<'_>,
|
nt: Notify<'_>,
|
||||||
) -> JsonResult {
|
) -> JsonResult {
|
||||||
_restore_cipher_by_uuid(&cipher_id, &headers, &mut conn, &nt).await
|
_restore_cipher_by_uuid(&cipher_id, &headers, false, &mut conn, &nt).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[put("/ciphers/restore-admin", data = "<data>")]
|
||||||
|
async fn restore_cipher_selected_admin(
|
||||||
|
data: Json<CipherIdsData>,
|
||||||
|
headers: Headers,
|
||||||
|
mut conn: DbConn,
|
||||||
|
nt: Notify<'_>,
|
||||||
|
) -> JsonResult {
|
||||||
|
_restore_multiple_ciphers(data, &headers, &mut conn, &nt).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[put("/ciphers/restore", data = "<data>")]
|
#[put("/ciphers/restore", data = "<data>")]
|
||||||
@@ -1555,35 +1575,47 @@ async fn move_cipher_selected(
|
|||||||
nt: Notify<'_>,
|
nt: Notify<'_>,
|
||||||
) -> EmptyResult {
|
) -> EmptyResult {
|
||||||
let data = data.into_inner();
|
let data = data.into_inner();
|
||||||
let user_id = headers.user.uuid;
|
let user_id = &headers.user.uuid;
|
||||||
|
|
||||||
if let Some(ref folder_id) = data.folder_id {
|
if let Some(ref folder_id) = data.folder_id {
|
||||||
if Folder::find_by_uuid_and_user(folder_id, &user_id, &mut conn).await.is_none() {
|
if Folder::find_by_uuid_and_user(folder_id, user_id, &mut conn).await.is_none() {
|
||||||
err!("Invalid folder", "Folder does not exist or belongs to another user");
|
err!("Invalid folder", "Folder does not exist or belongs to another user");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for cipher_id in data.ids {
|
let cipher_count = data.ids.len();
|
||||||
let Some(cipher) = Cipher::find_by_uuid(&cipher_id, &mut conn).await else {
|
let mut single_cipher: Option<Cipher> = None;
|
||||||
err!("Cipher doesn't exist")
|
|
||||||
};
|
|
||||||
|
|
||||||
if !cipher.is_accessible_to_user(&user_id, &mut conn).await {
|
// TODO: Convert this to use a single query (or at least less) to update all items
|
||||||
err!("Cipher is not accessible by user")
|
// Find all ciphers a user has access to, all others will be ignored
|
||||||
|
let accessible_ciphers = Cipher::find_by_user_and_ciphers(user_id, &data.ids, &mut conn).await;
|
||||||
|
let accessible_ciphers_count = accessible_ciphers.len();
|
||||||
|
for cipher in accessible_ciphers {
|
||||||
|
cipher.move_to_folder(data.folder_id.clone(), user_id, &mut conn).await?;
|
||||||
|
if cipher_count == 1 {
|
||||||
|
single_cipher = Some(cipher);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move cipher
|
if let Some(cipher) = single_cipher {
|
||||||
cipher.move_to_folder(data.folder_id.clone(), &user_id, &mut conn).await?;
|
|
||||||
|
|
||||||
nt.send_cipher_update(
|
nt.send_cipher_update(
|
||||||
UpdateType::SyncCipherUpdate,
|
UpdateType::SyncCipherUpdate,
|
||||||
&cipher,
|
&cipher,
|
||||||
std::slice::from_ref(&user_id),
|
std::slice::from_ref(user_id),
|
||||||
&headers.device,
|
&headers.device,
|
||||||
None,
|
None,
|
||||||
&mut conn,
|
&mut conn,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
} else {
|
||||||
|
// Multi move 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, &mut conn).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
if cipher_count != accessible_ciphers_count {
|
||||||
|
err!(format!(
|
||||||
|
"Not all ciphers are moved! {accessible_ciphers_count} of the selected {cipher_count} were moved."
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -1764,6 +1796,7 @@ async fn _delete_multiple_ciphers(
|
|||||||
async fn _restore_cipher_by_uuid(
|
async fn _restore_cipher_by_uuid(
|
||||||
cipher_id: &CipherId,
|
cipher_id: &CipherId,
|
||||||
headers: &Headers,
|
headers: &Headers,
|
||||||
|
multi_restore: bool,
|
||||||
conn: &mut DbConn,
|
conn: &mut DbConn,
|
||||||
nt: &Notify<'_>,
|
nt: &Notify<'_>,
|
||||||
) -> JsonResult {
|
) -> JsonResult {
|
||||||
@@ -1778,6 +1811,7 @@ async fn _restore_cipher_by_uuid(
|
|||||||
cipher.deleted_at = None;
|
cipher.deleted_at = None;
|
||||||
cipher.save(conn).await?;
|
cipher.save(conn).await?;
|
||||||
|
|
||||||
|
if !multi_restore {
|
||||||
nt.send_cipher_update(
|
nt.send_cipher_update(
|
||||||
UpdateType::SyncCipherUpdate,
|
UpdateType::SyncCipherUpdate,
|
||||||
&cipher,
|
&cipher,
|
||||||
@@ -1787,6 +1821,7 @@ async fn _restore_cipher_by_uuid(
|
|||||||
conn,
|
conn,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(org_id) = &cipher.organization_uuid {
|
if let Some(org_id) = &cipher.organization_uuid {
|
||||||
log_event(
|
log_event(
|
||||||
@@ -1814,12 +1849,15 @@ async fn _restore_multiple_ciphers(
|
|||||||
|
|
||||||
let mut ciphers: Vec<Value> = Vec::new();
|
let mut ciphers: Vec<Value> = Vec::new();
|
||||||
for cipher_id in data.ids {
|
for cipher_id in data.ids {
|
||||||
match _restore_cipher_by_uuid(&cipher_id, headers, conn, nt).await {
|
match _restore_cipher_by_uuid(&cipher_id, headers, true, conn, nt).await {
|
||||||
Ok(json) => ciphers.push(json.into_inner()),
|
Ok(json) => ciphers.push(json.into_inner()),
|
||||||
err => return err,
|
err => return err,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Multi move 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, conn).await;
|
||||||
|
|
||||||
Ok(Json(json!({
|
Ok(Json(json!({
|
||||||
"data": ciphers,
|
"data": ciphers,
|
||||||
"object": "list",
|
"object": "list",
|
||||||
|
@@ -6,7 +6,7 @@ use macros::UuidFromParam;
|
|||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
db_object! {
|
db_object! {
|
||||||
#[derive(Debug, Identifiable, Queryable, Insertable, AsChangeset, Deserialize, Serialize)]
|
#[derive(Identifiable, Queryable, Insertable, AsChangeset, Deserialize, Serialize)]
|
||||||
#[diesel(table_name = auth_requests)]
|
#[diesel(table_name = auth_requests)]
|
||||||
#[diesel(treat_none_as_null = true)]
|
#[diesel(treat_none_as_null = true)]
|
||||||
#[diesel(primary_key(uuid))]
|
#[diesel(primary_key(uuid))]
|
||||||
|
@@ -783,7 +783,12 @@ impl Cipher {
|
|||||||
// true, then the non-interesting ciphers will not be returned. As a
|
// true, then the non-interesting ciphers will not be returned. As a
|
||||||
// result, those ciphers will not appear in "My Vault" for the org
|
// result, those ciphers will not appear in "My Vault" for the org
|
||||||
// owner/admin, but they can still be accessed via the org vault view.
|
// owner/admin, but they can still be accessed via the org vault view.
|
||||||
pub async fn find_by_user(user_uuid: &UserId, visible_only: bool, conn: &mut DbConn) -> Vec<Self> {
|
pub async fn find_by_user(
|
||||||
|
user_uuid: &UserId,
|
||||||
|
visible_only: bool,
|
||||||
|
cipher_uuids: &Vec<CipherId>,
|
||||||
|
conn: &mut DbConn,
|
||||||
|
) -> Vec<Self> {
|
||||||
if CONFIG.org_groups_enabled() {
|
if CONFIG.org_groups_enabled() {
|
||||||
db_run! {conn: {
|
db_run! {conn: {
|
||||||
let mut query = ciphers::table
|
let mut query = ciphers::table
|
||||||
@@ -824,6 +829,13 @@ impl Cipher {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only filter for one specific cipher
|
||||||
|
if !cipher_uuids.is_empty() {
|
||||||
|
query = query.filter(
|
||||||
|
ciphers::uuid.eq_any(cipher_uuids)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
query
|
query
|
||||||
.select(ciphers::all_columns)
|
.select(ciphers::all_columns)
|
||||||
.distinct()
|
.distinct()
|
||||||
@@ -856,6 +868,13 @@ impl Cipher {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only filter for one specific cipher
|
||||||
|
if !cipher_uuids.is_empty() {
|
||||||
|
query = query.filter(
|
||||||
|
ciphers::uuid.eq_any(cipher_uuids)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
query
|
query
|
||||||
.select(ciphers::all_columns)
|
.select(ciphers::all_columns)
|
||||||
.distinct()
|
.distinct()
|
||||||
@@ -866,7 +885,23 @@ impl Cipher {
|
|||||||
|
|
||||||
// Find all ciphers visible to the specified user.
|
// Find all ciphers visible to the specified user.
|
||||||
pub async fn find_by_user_visible(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
|
pub async fn find_by_user_visible(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
|
||||||
Self::find_by_user(user_uuid, true, conn).await
|
Self::find_by_user(user_uuid, true, &vec![], conn).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn find_by_user_and_ciphers(
|
||||||
|
user_uuid: &UserId,
|
||||||
|
cipher_uuids: &Vec<CipherId>,
|
||||||
|
conn: &mut DbConn,
|
||||||
|
) -> Vec<Self> {
|
||||||
|
Self::find_by_user(user_uuid, true, cipher_uuids, conn).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn find_by_user_and_cipher(
|
||||||
|
user_uuid: &UserId,
|
||||||
|
cipher_uuid: &CipherId,
|
||||||
|
conn: &mut DbConn,
|
||||||
|
) -> Option<Self> {
|
||||||
|
Self::find_by_user(user_uuid, true, &vec![cipher_uuid.clone()], conn).await.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find all ciphers directly owned by the specified user.
|
// Find all ciphers directly owned by the specified user.
|
||||||
|
Reference in New Issue
Block a user