@@ -78,6 +78,7 @@ pub fn routes() -> Vec<Route> {
78
78
restore_cipher_put,
79
79
restore_cipher_put_admin,
80
80
restore_cipher_selected,
81
+ restore_cipher_selected_admin,
81
82
delete_all,
82
83
move_cipher_selected,
83
84
move_cipher_selected_put,
@@ -318,7 +319,7 @@ async fn post_ciphers_create(
318
319
// or otherwise), we can just ignore this field entirely.
319
320
data. cipher . last_known_revision_date = None ;
320
321
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
322
323
}
323
324
324
325
/// Called when creating a new user-owned cipher.
@@ -920,7 +921,7 @@ async fn post_cipher_share(
920
921
) -> JsonResult {
921
922
let data: ShareCipherData = data. into_inner ( ) ;
922
923
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
924
925
}
925
926
926
927
#[ put( "/ciphers/<cipher_id>/share" , data = "<data>" ) ]
@@ -933,7 +934,7 @@ async fn put_cipher_share(
933
934
) -> JsonResult {
934
935
let data: ShareCipherData = data. into_inner ( ) ;
935
936
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
937
938
}
938
939
939
940
#[ derive( Deserialize ) ]
@@ -973,11 +974,16 @@ async fn put_cipher_share_selected(
973
974
} ;
974
975
975
976
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
+ }
977
980
None => err ! ( "Request missing ids field" ) ,
978
981
} ;
979
982
}
980
983
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
+
981
987
Ok ( ( ) )
982
988
}
983
989
@@ -987,6 +993,7 @@ async fn share_cipher_by_uuid(
987
993
headers : & Headers ,
988
994
conn : & mut DbConn ,
989
995
nt : & Notify < ' _ > ,
996
+ override_ut : Option < UpdateType > ,
990
997
) -> JsonResult {
991
998
let mut cipher = match Cipher :: find_by_uuid ( cipher_id, conn) . await {
992
999
Some ( cipher) => {
@@ -1018,7 +1025,10 @@ async fn share_cipher_by_uuid(
1018
1025
} ;
1019
1026
1020
1027
// 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 ( ) {
1022
1032
UpdateType :: SyncCipherUpdate
1023
1033
} else {
1024
1034
UpdateType :: SyncCipherCreate
@@ -1517,7 +1527,7 @@ async fn delete_cipher_selected_put_admin(
1517
1527
1518
1528
#[ put( "/ciphers/<cipher_id>/restore" ) ]
1519
1529
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
1521
1531
}
1522
1532
1523
1533
#[ put( "/ciphers/<cipher_id>/restore-admin" ) ]
@@ -1527,7 +1537,17 @@ async fn restore_cipher_put_admin(
1527
1537
mut conn : DbConn ,
1528
1538
nt : Notify < ' _ > ,
1529
1539
) -> 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
1531
1551
}
1532
1552
1533
1553
#[ put( "/ciphers/restore" , data = "<data>" ) ]
@@ -1555,35 +1575,47 @@ async fn move_cipher_selected(
1555
1575
nt : Notify < ' _ > ,
1556
1576
) -> EmptyResult {
1557
1577
let data = data. into_inner ( ) ;
1558
- let user_id = headers. user . uuid ;
1578
+ let user_id = & headers. user . uuid ;
1559
1579
1560
1580
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 ( ) {
1562
1582
err ! ( "Invalid folder" , "Folder does not exist or belongs to another user" ) ;
1563
1583
}
1564
1584
}
1565
1585
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) ;
1573
1597
}
1598
+ }
1574
1599
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 {
1578
1601
nt. send_cipher_update (
1579
1602
UpdateType :: SyncCipherUpdate ,
1580
1603
& cipher,
1581
- std:: slice:: from_ref ( & user_id) ,
1604
+ std:: slice:: from_ref ( user_id) ,
1582
1605
& headers. device ,
1583
1606
None ,
1584
1607
& mut conn,
1585
1608
)
1586
1609
. 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
+ ) )
1587
1619
}
1588
1620
1589
1621
Ok ( ( ) )
@@ -1764,6 +1796,7 @@ async fn _delete_multiple_ciphers(
1764
1796
async fn _restore_cipher_by_uuid (
1765
1797
cipher_id : & CipherId ,
1766
1798
headers : & Headers ,
1799
+ multi_restore : bool ,
1767
1800
conn : & mut DbConn ,
1768
1801
nt : & Notify < ' _ > ,
1769
1802
) -> JsonResult {
@@ -1778,15 +1811,17 @@ async fn _restore_cipher_by_uuid(
1778
1811
cipher. deleted_at = None ;
1779
1812
cipher. save ( conn) . await ?;
1780
1813
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
+ }
1790
1825
1791
1826
if let Some ( org_id) = & cipher. organization_uuid {
1792
1827
log_event (
@@ -1814,12 +1849,15 @@ async fn _restore_multiple_ciphers(
1814
1849
1815
1850
let mut ciphers: Vec < Value > = Vec :: new ( ) ;
1816
1851
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 {
1818
1853
Ok ( json) => ciphers. push ( json. into_inner ( ) ) ,
1819
1854
err => return err,
1820
1855
}
1821
1856
}
1822
1857
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
+
1823
1861
Ok ( Json ( json ! ( {
1824
1862
"data" : ciphers,
1825
1863
"object" : "list" ,
0 commit comments