Skip to content

Commit 2a5489a

Browse files
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 <[email protected]> * Update src/api/core/ciphers.rs Co-authored-by: Daniel García <[email protected]> --------- Signed-off-by: BlackDex <[email protected]> Co-authored-by: Daniel García <[email protected]>
1 parent 8fd0ee4 commit 2a5489a

File tree

3 files changed

+112
-39
lines changed

3 files changed

+112
-39
lines changed

src/api/core/ciphers.rs

Lines changed: 68 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ pub fn routes() -> Vec<Route> {
7878
restore_cipher_put,
7979
restore_cipher_put_admin,
8080
restore_cipher_selected,
81+
restore_cipher_selected_admin,
8182
delete_all,
8283
move_cipher_selected,
8384
move_cipher_selected_put,
@@ -318,7 +319,7 @@ async fn post_ciphers_create(
318319
// or otherwise), we can just ignore this field entirely.
319320
data.cipher.last_known_revision_date = None;
320321

321-
share_cipher_by_uuid(&cipher.uuid, data, &headers, &mut conn, &nt).await
322+
share_cipher_by_uuid(&cipher.uuid, data, &headers, &mut conn, &nt, None).await
322323
}
323324

324325
/// Called when creating a new user-owned cipher.
@@ -920,7 +921,7 @@ async fn post_cipher_share(
920921
) -> JsonResult {
921922
let data: ShareCipherData = data.into_inner();
922923

923-
share_cipher_by_uuid(&cipher_id, data, &headers, &mut conn, &nt).await
924+
share_cipher_by_uuid(&cipher_id, data, &headers, &mut conn, &nt, None).await
924925
}
925926

926927
#[put("/ciphers/<cipher_id>/share", data = "<data>")]
@@ -933,7 +934,7 @@ async fn put_cipher_share(
933934
) -> JsonResult {
934935
let data: ShareCipherData = data.into_inner();
935936

936-
share_cipher_by_uuid(&cipher_id, data, &headers, &mut conn, &nt).await
937+
share_cipher_by_uuid(&cipher_id, data, &headers, &mut conn, &nt, None).await
937938
}
938939

939940
#[derive(Deserialize)]
@@ -973,11 +974,16 @@ async fn put_cipher_share_selected(
973974
};
974975

975976
match shared_cipher_data.cipher.id.take() {
976-
Some(id) => share_cipher_by_uuid(&id, shared_cipher_data, &headers, &mut conn, &nt).await?,
977+
Some(id) => {
978+
share_cipher_by_uuid(&id, shared_cipher_data, &headers, &mut conn, &nt, Some(UpdateType::None)).await?
979+
}
977980
None => err!("Request missing ids field"),
978981
};
979982
}
980983

984+
// Multi share actions do not send out a push for each cipher, we need to send a general sync here
985+
nt.send_user_update(UpdateType::SyncCiphers, &headers.user, &headers.device.push_uuid, &mut conn).await;
986+
981987
Ok(())
982988
}
983989

@@ -987,6 +993,7 @@ async fn share_cipher_by_uuid(
987993
headers: &Headers,
988994
conn: &mut DbConn,
989995
nt: &Notify<'_>,
996+
override_ut: Option<UpdateType>,
990997
) -> JsonResult {
991998
let mut cipher = match Cipher::find_by_uuid(cipher_id, conn).await {
992999
Some(cipher) => {
@@ -1018,7 +1025,10 @@ async fn share_cipher_by_uuid(
10181025
};
10191026

10201027
// When LastKnownRevisionDate is None, it is a new cipher, so send CipherCreate.
1021-
let ut = if data.cipher.last_known_revision_date.is_some() {
1028+
// If there is an override, like when handling multiple items, we want to prevent a push notification for every single item
1029+
let ut = if let Some(ut) = override_ut {
1030+
ut
1031+
} else if data.cipher.last_known_revision_date.is_some() {
10221032
UpdateType::SyncCipherUpdate
10231033
} else {
10241034
UpdateType::SyncCipherCreate
@@ -1517,7 +1527,7 @@ async fn delete_cipher_selected_put_admin(
15171527

15181528
#[put("/ciphers/<cipher_id>/restore")]
15191529
async fn restore_cipher_put(cipher_id: CipherId, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
1520-
_restore_cipher_by_uuid(&cipher_id, &headers, &mut conn, &nt).await
1530+
_restore_cipher_by_uuid(&cipher_id, &headers, false, &mut conn, &nt).await
15211531
}
15221532

15231533
#[put("/ciphers/<cipher_id>/restore-admin")]
@@ -1527,7 +1537,17 @@ async fn restore_cipher_put_admin(
15271537
mut conn: DbConn,
15281538
nt: Notify<'_>,
15291539
) -> JsonResult {
1530-
_restore_cipher_by_uuid(&cipher_id, &headers, &mut conn, &nt).await
1540+
_restore_cipher_by_uuid(&cipher_id, &headers, false, &mut conn, &nt).await
1541+
}
1542+
1543+
#[put("/ciphers/restore-admin", data = "<data>")]
1544+
async fn restore_cipher_selected_admin(
1545+
data: Json<CipherIdsData>,
1546+
headers: Headers,
1547+
mut conn: DbConn,
1548+
nt: Notify<'_>,
1549+
) -> JsonResult {
1550+
_restore_multiple_ciphers(data, &headers, &mut conn, &nt).await
15311551
}
15321552

15331553
#[put("/ciphers/restore", data = "<data>")]
@@ -1555,35 +1575,47 @@ async fn move_cipher_selected(
15551575
nt: Notify<'_>,
15561576
) -> EmptyResult {
15571577
let data = data.into_inner();
1558-
let user_id = headers.user.uuid;
1578+
let user_id = &headers.user.uuid;
15591579

15601580
if let Some(ref folder_id) = data.folder_id {
1561-
if Folder::find_by_uuid_and_user(folder_id, &user_id, &mut conn).await.is_none() {
1581+
if Folder::find_by_uuid_and_user(folder_id, user_id, &mut conn).await.is_none() {
15621582
err!("Invalid folder", "Folder does not exist or belongs to another user");
15631583
}
15641584
}
15651585

1566-
for cipher_id in data.ids {
1567-
let Some(cipher) = Cipher::find_by_uuid(&cipher_id, &mut conn).await else {
1568-
err!("Cipher doesn't exist")
1569-
};
1570-
1571-
if !cipher.is_accessible_to_user(&user_id, &mut conn).await {
1572-
err!("Cipher is not accessible by user")
1586+
let cipher_count = data.ids.len();
1587+
let mut single_cipher: Option<Cipher> = None;
1588+
1589+
// TODO: Convert this to use a single query (or at least less) to update all items
1590+
// Find all ciphers a user has access to, all others will be ignored
1591+
let accessible_ciphers = Cipher::find_by_user_and_ciphers(user_id, &data.ids, &mut conn).await;
1592+
let accessible_ciphers_count = accessible_ciphers.len();
1593+
for cipher in accessible_ciphers {
1594+
cipher.move_to_folder(data.folder_id.clone(), user_id, &mut conn).await?;
1595+
if cipher_count == 1 {
1596+
single_cipher = Some(cipher);
15731597
}
1598+
}
15741599

1575-
// Move cipher
1576-
cipher.move_to_folder(data.folder_id.clone(), &user_id, &mut conn).await?;
1577-
1600+
if let Some(cipher) = single_cipher {
15781601
nt.send_cipher_update(
15791602
UpdateType::SyncCipherUpdate,
15801603
&cipher,
1581-
std::slice::from_ref(&user_id),
1604+
std::slice::from_ref(user_id),
15821605
&headers.device,
15831606
None,
15841607
&mut conn,
15851608
)
15861609
.await;
1610+
} else {
1611+
// Multi move actions do not send out a push for each cipher, we need to send a general sync here
1612+
nt.send_user_update(UpdateType::SyncCiphers, &headers.user, &headers.device.push_uuid, &mut conn).await;
1613+
}
1614+
1615+
if cipher_count != accessible_ciphers_count {
1616+
err!(format!(
1617+
"Not all ciphers are moved! {accessible_ciphers_count} of the selected {cipher_count} were moved."
1618+
))
15871619
}
15881620

15891621
Ok(())
@@ -1764,6 +1796,7 @@ async fn _delete_multiple_ciphers(
17641796
async fn _restore_cipher_by_uuid(
17651797
cipher_id: &CipherId,
17661798
headers: &Headers,
1799+
multi_restore: bool,
17671800
conn: &mut DbConn,
17681801
nt: &Notify<'_>,
17691802
) -> JsonResult {
@@ -1778,15 +1811,17 @@ async fn _restore_cipher_by_uuid(
17781811
cipher.deleted_at = None;
17791812
cipher.save(conn).await?;
17801813

1781-
nt.send_cipher_update(
1782-
UpdateType::SyncCipherUpdate,
1783-
&cipher,
1784-
&cipher.update_users_revision(conn).await,
1785-
&headers.device,
1786-
None,
1787-
conn,
1788-
)
1789-
.await;
1814+
if !multi_restore {
1815+
nt.send_cipher_update(
1816+
UpdateType::SyncCipherUpdate,
1817+
&cipher,
1818+
&cipher.update_users_revision(conn).await,
1819+
&headers.device,
1820+
None,
1821+
conn,
1822+
)
1823+
.await;
1824+
}
17901825

17911826
if let Some(org_id) = &cipher.organization_uuid {
17921827
log_event(
@@ -1814,12 +1849,15 @@ async fn _restore_multiple_ciphers(
18141849

18151850
let mut ciphers: Vec<Value> = Vec::new();
18161851
for cipher_id in data.ids {
1817-
match _restore_cipher_by_uuid(&cipher_id, headers, conn, nt).await {
1852+
match _restore_cipher_by_uuid(&cipher_id, headers, true, conn, nt).await {
18181853
Ok(json) => ciphers.push(json.into_inner()),
18191854
err => return err,
18201855
}
18211856
}
18221857

1858+
// Multi move actions do not send out a push for each cipher, we need to send a general sync here
1859+
nt.send_user_update(UpdateType::SyncCiphers, &headers.user, &headers.device.push_uuid, conn).await;
1860+
18231861
Ok(Json(json!({
18241862
"data": ciphers,
18251863
"object": "list",

src/db/models/auth_request.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use macros::UuidFromParam;
66
use serde_json::Value;
77

88
db_object! {
9-
#[derive(Debug, Identifiable, Queryable, Insertable, AsChangeset, Deserialize, Serialize)]
9+
#[derive(Identifiable, Queryable, Insertable, AsChangeset, Deserialize, Serialize)]
1010
#[diesel(table_name = auth_requests)]
1111
#[diesel(treat_none_as_null = true)]
1212
#[diesel(primary_key(uuid))]

src/db/models/cipher.rs

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -783,7 +783,12 @@ impl Cipher {
783783
// true, then the non-interesting ciphers will not be returned. As a
784784
// result, those ciphers will not appear in "My Vault" for the org
785785
// owner/admin, but they can still be accessed via the org vault view.
786-
pub async fn find_by_user(user_uuid: &UserId, visible_only: bool, conn: &mut DbConn) -> Vec<Self> {
786+
pub async fn find_by_user(
787+
user_uuid: &UserId,
788+
visible_only: bool,
789+
cipher_uuids: &Vec<CipherId>,
790+
conn: &mut DbConn,
791+
) -> Vec<Self> {
787792
if CONFIG.org_groups_enabled() {
788793
db_run! {conn: {
789794
let mut query = ciphers::table
@@ -821,7 +826,14 @@ impl Cipher {
821826
if !visible_only {
822827
query = query.or_filter(
823828
users_organizations::atype.le(MembershipType::Admin as i32) // Org admin/owner
824-
);
829+
);
830+
}
831+
832+
// Only filter for one specific cipher
833+
if !cipher_uuids.is_empty() {
834+
query = query.filter(
835+
ciphers::uuid.eq_any(cipher_uuids)
836+
);
825837
}
826838

827839
query
@@ -850,11 +862,18 @@ impl Cipher {
850862
.or_filter(users_collections::user_uuid.eq(user_uuid)) // Access to collection
851863
.into_boxed();
852864

853-
if !visible_only {
854-
query = query.or_filter(
855-
users_organizations::atype.le(MembershipType::Admin as i32) // Org admin/owner
856-
);
857-
}
865+
if !visible_only {
866+
query = query.or_filter(
867+
users_organizations::atype.le(MembershipType::Admin as i32) // Org admin/owner
868+
);
869+
}
870+
871+
// Only filter for one specific cipher
872+
if !cipher_uuids.is_empty() {
873+
query = query.filter(
874+
ciphers::uuid.eq_any(cipher_uuids)
875+
);
876+
}
858877

859878
query
860879
.select(ciphers::all_columns)
@@ -866,7 +885,23 @@ impl Cipher {
866885

867886
// Find all ciphers visible to the specified user.
868887
pub async fn find_by_user_visible(user_uuid: &UserId, conn: &mut DbConn) -> Vec<Self> {
869-
Self::find_by_user(user_uuid, true, conn).await
888+
Self::find_by_user(user_uuid, true, &vec![], conn).await
889+
}
890+
891+
pub async fn find_by_user_and_ciphers(
892+
user_uuid: &UserId,
893+
cipher_uuids: &Vec<CipherId>,
894+
conn: &mut DbConn,
895+
) -> Vec<Self> {
896+
Self::find_by_user(user_uuid, true, cipher_uuids, conn).await
897+
}
898+
899+
pub async fn find_by_user_and_cipher(
900+
user_uuid: &UserId,
901+
cipher_uuid: &CipherId,
902+
conn: &mut DbConn,
903+
) -> Option<Self> {
904+
Self::find_by_user(user_uuid, true, &vec![cipher_uuid.clone()], conn).await.pop()
870905
}
871906

872907
// Find all ciphers directly owned by the specified user.

0 commit comments

Comments
 (0)