Merge branch 'BlackDex-update-notifications'

This commit is contained in:
Daniel García 2023-01-09 18:24:00 +01:00
commit 05a552910c
No known key found for this signature in database
GPG Key ID: FC8A7D14C3CD543A
8 changed files with 187 additions and 63 deletions

View File

@ -13,7 +13,7 @@ use rocket::{
}; };
use crate::{ use crate::{
api::{core::log_event, ApiResult, EmptyResult, JsonResult, NumberOrString}, api::{core::log_event, ApiResult, EmptyResult, JsonResult, Notify, NumberOrString, UpdateType},
auth::{decode_admin, encode_jwt, generate_admin_claims, ClientIp}, auth::{decode_admin, encode_jwt, generate_admin_claims, ClientIp},
config::ConfigBuilder, config::ConfigBuilder,
db::{backup_database, get_sql_server_version, models::*, DbConn, DbConnType}, db::{backup_database, get_sql_server_version, models::*, DbConn, DbConnType},
@ -365,22 +365,30 @@ async fn delete_user(uuid: String, _token: AdminToken, mut conn: DbConn, ip: Cli
} }
#[post("/users/<uuid>/deauth")] #[post("/users/<uuid>/deauth")]
async fn deauth_user(uuid: String, _token: AdminToken, mut conn: DbConn) -> EmptyResult { async fn deauth_user(uuid: String, _token: AdminToken, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
let mut user = get_user_or_404(&uuid, &mut conn).await?; let mut user = get_user_or_404(&uuid, &mut conn).await?;
Device::delete_all_by_user(&user.uuid, &mut conn).await?; Device::delete_all_by_user(&user.uuid, &mut conn).await?;
user.reset_security_stamp(); user.reset_security_stamp();
user.save(&mut conn).await let save_result = user.save(&mut conn).await;
nt.send_user_update(UpdateType::LogOut, &user).await;
save_result
} }
#[post("/users/<uuid>/disable")] #[post("/users/<uuid>/disable")]
async fn disable_user(uuid: String, _token: AdminToken, mut conn: DbConn) -> EmptyResult { async fn disable_user(uuid: String, _token: AdminToken, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
let mut user = get_user_or_404(&uuid, &mut conn).await?; let mut user = get_user_or_404(&uuid, &mut conn).await?;
Device::delete_all_by_user(&user.uuid, &mut conn).await?; Device::delete_all_by_user(&user.uuid, &mut conn).await?;
user.reset_security_stamp(); user.reset_security_stamp();
user.enabled = false; user.enabled = false;
user.save(&mut conn).await let save_result = user.save(&mut conn).await;
nt.send_user_update(UpdateType::LogOut, &user).await;
save_result
} }
#[post("/users/<uuid>/enable")] #[post("/users/<uuid>/enable")]

View File

@ -275,6 +275,7 @@ async fn post_password(
headers: Headers, headers: Headers,
mut conn: DbConn, mut conn: DbConn,
ip: ClientIp, ip: ClientIp,
nt: Notify<'_>,
) -> EmptyResult { ) -> EmptyResult {
let data: ChangePassData = data.into_inner().data; let data: ChangePassData = data.into_inner().data;
let mut user = headers.user; let mut user = headers.user;
@ -293,7 +294,11 @@ async fn post_password(
Some(vec![String::from("post_rotatekey"), String::from("get_contacts"), String::from("get_public_keys")]), Some(vec![String::from("post_rotatekey"), String::from("get_contacts"), String::from("get_public_keys")]),
); );
user.akey = data.Key; user.akey = data.Key;
user.save(&mut conn).await let save_result = user.save(&mut conn).await;
nt.send_user_update(UpdateType::LogOut, &user).await;
save_result
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -308,7 +313,7 @@ struct ChangeKdfData {
} }
#[post("/accounts/kdf", data = "<data>")] #[post("/accounts/kdf", data = "<data>")]
async fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, mut conn: DbConn) -> EmptyResult { async fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
let data: ChangeKdfData = data.into_inner().data; let data: ChangeKdfData = data.into_inner().data;
let mut user = headers.user; let mut user = headers.user;
@ -320,7 +325,11 @@ async fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, mut conn: D
user.client_kdf_type = data.Kdf; user.client_kdf_type = data.Kdf;
user.set_password(&data.NewMasterPasswordHash, None); user.set_password(&data.NewMasterPasswordHash, None);
user.akey = data.Key; user.akey = data.Key;
user.save(&mut conn).await let save_result = user.save(&mut conn).await;
nt.send_user_update(UpdateType::LogOut, &user).await;
save_result
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -388,6 +397,7 @@ async fn post_rotatekey(
// Prevent triggering cipher updates via WebSockets by settings UpdateType::None // Prevent triggering cipher updates via WebSockets by settings UpdateType::None
// The user sessions are invalidated because all the ciphers were re-encrypted and thus triggering an update could cause issues. // The user sessions are invalidated because all the ciphers were re-encrypted and thus triggering an update could cause issues.
// We force the users to logout after the user has been saved to try and prevent these issues.
update_cipher_from_data(&mut saved_cipher, cipher_data, &headers, false, &mut conn, &ip, &nt, UpdateType::None) update_cipher_from_data(&mut saved_cipher, cipher_data, &headers, false, &mut conn, &ip, &nt, UpdateType::None)
.await? .await?
} }
@ -399,11 +409,20 @@ async fn post_rotatekey(
user.private_key = Some(data.PrivateKey); user.private_key = Some(data.PrivateKey);
user.reset_security_stamp(); user.reset_security_stamp();
user.save(&mut conn).await let save_result = user.save(&mut conn).await;
nt.send_user_update(UpdateType::LogOut, &user).await;
save_result
} }
#[post("/accounts/security-stamp", data = "<data>")] #[post("/accounts/security-stamp", data = "<data>")]
async fn post_sstamp(data: JsonUpcase<PasswordData>, headers: Headers, mut conn: DbConn) -> EmptyResult { async fn post_sstamp(
data: JsonUpcase<PasswordData>,
headers: Headers,
mut conn: DbConn,
nt: Notify<'_>,
) -> EmptyResult {
let data: PasswordData = data.into_inner().data; let data: PasswordData = data.into_inner().data;
let mut user = headers.user; let mut user = headers.user;
@ -413,7 +432,11 @@ async fn post_sstamp(data: JsonUpcase<PasswordData>, headers: Headers, mut conn:
Device::delete_all_by_user(&user.uuid, &mut conn).await?; Device::delete_all_by_user(&user.uuid, &mut conn).await?;
user.reset_security_stamp(); user.reset_security_stamp();
user.save(&mut conn).await let save_result = user.save(&mut conn).await;
nt.send_user_update(UpdateType::LogOut, &user).await;
save_result
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -465,7 +488,12 @@ struct ChangeEmailData {
} }
#[post("/accounts/email", data = "<data>")] #[post("/accounts/email", data = "<data>")]
async fn post_email(data: JsonUpcase<ChangeEmailData>, headers: Headers, mut conn: DbConn) -> EmptyResult { async fn post_email(
data: JsonUpcase<ChangeEmailData>,
headers: Headers,
mut conn: DbConn,
nt: Notify<'_>,
) -> EmptyResult {
let data: ChangeEmailData = data.into_inner().data; let data: ChangeEmailData = data.into_inner().data;
let mut user = headers.user; let mut user = headers.user;
@ -507,8 +535,11 @@ async fn post_email(data: JsonUpcase<ChangeEmailData>, headers: Headers, mut con
user.set_password(&data.NewMasterPasswordHash, None); user.set_password(&data.NewMasterPasswordHash, None);
user.akey = data.Key; user.akey = data.Key;
let save_result = user.save(&mut conn).await;
user.save(&mut conn).await nt.send_user_update(UpdateType::LogOut, &user).await;
save_result
} }
#[post("/accounts/verify-email")] #[post("/accounts/verify-email")]

View File

@ -310,7 +310,8 @@ async fn post_ciphers(
data.LastKnownRevisionDate = None; data.LastKnownRevisionDate = None;
let mut cipher = Cipher::new(data.Type, data.Name.clone()); let mut cipher = Cipher::new(data.Type, data.Name.clone());
update_cipher_from_data(&mut cipher, data, &headers, false, &mut conn, &ip, &nt, UpdateType::CipherCreate).await?; update_cipher_from_data(&mut cipher, data, &headers, false, &mut conn, &ip, &nt, UpdateType::SyncCipherCreate)
.await?;
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, &mut conn).await)) Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, &mut conn).await))
} }
@ -415,7 +416,14 @@ pub async fn update_cipher_from_data(
for (id, attachment) in attachments { for (id, attachment) in attachments {
let mut saved_att = match Attachment::find_by_id(&id, conn).await { let mut saved_att = match Attachment::find_by_id(&id, conn).await {
Some(att) => att, Some(att) => att,
None => err!("Attachment doesn't exist"), None => {
// Warn and continue here.
// A missing attachment means it was removed via an other client.
// Also the Desktop Client supports removing attachments and save an update afterwards.
// Bitwarden it self ignores these mismatches server side.
warn!("Attachment {id} doesn't exist");
continue;
}
}; };
if saved_att.cipher_uuid != cipher.uuid { if saved_att.cipher_uuid != cipher.uuid {
@ -482,8 +490,8 @@ pub async fn update_cipher_from_data(
// Only log events for organizational ciphers // Only log events for organizational ciphers
if let Some(org_uuid) = &cipher.organization_uuid { if let Some(org_uuid) = &cipher.organization_uuid {
let event_type = match (&ut, transfer_cipher) { let event_type = match (&ut, transfer_cipher) {
(UpdateType::CipherCreate, true) => EventType::CipherCreated, (UpdateType::SyncCipherCreate, true) => EventType::CipherCreated,
(UpdateType::CipherUpdate, true) => EventType::CipherShared, (UpdateType::SyncCipherUpdate, true) => EventType::CipherShared,
(_, _) => EventType::CipherUpdated, (_, _) => EventType::CipherUpdated,
}; };
@ -499,7 +507,7 @@ pub async fn update_cipher_from_data(
.await; .await;
} }
nt.send_cipher_update(ut, cipher, &cipher.update_users_revision(conn).await).await; nt.send_cipher_update(ut, cipher, &cipher.update_users_revision(conn).await, &headers.device.uuid).await;
} }
Ok(()) Ok(())
@ -562,7 +570,7 @@ async fn post_ciphers_import(
let mut user = headers.user; let mut user = headers.user;
user.update_revision(&mut conn).await?; user.update_revision(&mut conn).await?;
nt.send_user_update(UpdateType::Vault, &user).await; nt.send_user_update(UpdateType::SyncVault, &user).await;
Ok(()) Ok(())
} }
@ -628,7 +636,8 @@ async fn put_cipher(
err!("Cipher is not write accessible") err!("Cipher is not write accessible")
} }
update_cipher_from_data(&mut cipher, data, &headers, false, &mut conn, &ip, &nt, UpdateType::CipherUpdate).await?; update_cipher_from_data(&mut cipher, data, &headers, false, &mut conn, &ip, &nt, UpdateType::SyncCipherUpdate)
.await?;
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, &mut conn).await)) Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, &mut conn).await))
} }
@ -850,9 +859,9 @@ 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.LastKnownRevisionDate.is_some() { let ut = if data.Cipher.LastKnownRevisionDate.is_some() {
UpdateType::CipherUpdate UpdateType::SyncCipherUpdate
} else { } else {
UpdateType::CipherCreate UpdateType::SyncCipherCreate
}; };
update_cipher_from_data(&mut cipher, data.Cipher, headers, shared_to_collection, conn, ip, nt, ut).await?; update_cipher_from_data(&mut cipher, data.Cipher, headers, shared_to_collection, conn, ip, nt, ut).await?;
@ -1054,7 +1063,13 @@ async fn save_attachment(
data.data.move_copy_to(file_path).await? data.data.move_copy_to(file_path).await?
} }
nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(&mut conn).await).await; nt.send_cipher_update(
UpdateType::SyncCipherUpdate,
&cipher,
&cipher.update_users_revision(&mut conn).await,
&headers.device.uuid,
)
.await;
if let Some(org_uuid) = &cipher.organization_uuid { if let Some(org_uuid) = &cipher.organization_uuid {
log_event( log_event(
@ -1390,7 +1405,7 @@ async fn move_cipher_selected(
// Move cipher // Move cipher
cipher.move_to_folder(data.FolderId.clone(), &user_uuid, &mut conn).await?; cipher.move_to_folder(data.FolderId.clone(), &user_uuid, &mut conn).await?;
nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &[user_uuid.clone()]).await; nt.send_cipher_update(UpdateType::SyncCipherUpdate, &cipher, &[user_uuid.clone()], &headers.device.uuid).await;
} }
Ok(()) Ok(())
@ -1438,7 +1453,7 @@ async fn delete_all(
Some(user_org) => { Some(user_org) => {
if user_org.atype == UserOrgType::Owner { if user_org.atype == UserOrgType::Owner {
Cipher::delete_all_by_organization(&org_data.org_id, &mut conn).await?; Cipher::delete_all_by_organization(&org_data.org_id, &mut conn).await?;
nt.send_user_update(UpdateType::Vault, &user).await; nt.send_user_update(UpdateType::SyncVault, &user).await;
log_event( log_event(
EventType::OrganizationPurgedVault as i32, EventType::OrganizationPurgedVault as i32,
@ -1471,7 +1486,7 @@ async fn delete_all(
} }
user.update_revision(&mut conn).await?; user.update_revision(&mut conn).await?;
nt.send_user_update(UpdateType::Vault, &user).await; nt.send_user_update(UpdateType::SyncVault, &user).await;
Ok(()) Ok(())
} }
} }
@ -1497,10 +1512,22 @@ async fn _delete_cipher_by_uuid(
if soft_delete { if soft_delete {
cipher.deleted_at = Some(Utc::now().naive_utc()); cipher.deleted_at = Some(Utc::now().naive_utc());
cipher.save(conn).await?; cipher.save(conn).await?;
nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(conn).await).await; nt.send_cipher_update(
UpdateType::SyncCipherUpdate,
&cipher,
&cipher.update_users_revision(conn).await,
&headers.device.uuid,
)
.await;
} else { } else {
cipher.delete(conn).await?; cipher.delete(conn).await?;
nt.send_cipher_update(UpdateType::CipherDelete, &cipher, &cipher.update_users_revision(conn).await).await; nt.send_cipher_update(
UpdateType::SyncCipherDelete,
&cipher,
&cipher.update_users_revision(conn).await,
&headers.device.uuid,
)
.await;
} }
if let Some(org_uuid) = cipher.organization_uuid { if let Some(org_uuid) = cipher.organization_uuid {
@ -1562,7 +1589,13 @@ async fn _restore_cipher_by_uuid(
cipher.deleted_at = None; cipher.deleted_at = None;
cipher.save(conn).await?; cipher.save(conn).await?;
nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(conn).await).await; nt.send_cipher_update(
UpdateType::SyncCipherUpdate,
&cipher,
&cipher.update_users_revision(conn).await,
&headers.device.uuid,
)
.await;
if let Some(org_uuid) = &cipher.organization_uuid { if let Some(org_uuid) = &cipher.organization_uuid {
log_event( log_event(
EventType::CipherRestored as i32, EventType::CipherRestored as i32,
@ -1639,7 +1672,13 @@ async fn _delete_cipher_attachment_by_id(
// Delete attachment // Delete attachment
attachment.delete(conn).await?; attachment.delete(conn).await?;
nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(conn).await).await; nt.send_cipher_update(
UpdateType::SyncCipherUpdate,
&cipher,
&cipher.update_users_revision(conn).await,
&headers.device.uuid,
)
.await;
if let Some(org_uuid) = cipher.organization_uuid { if let Some(org_uuid) = cipher.organization_uuid {
log_event( log_event(
EventType::CipherAttachmentDeleted as i32, EventType::CipherAttachmentDeleted as i32,

View File

@ -50,7 +50,7 @@ async fn post_folders(data: JsonUpcase<FolderData>, headers: Headers, mut conn:
let mut folder = Folder::new(headers.user.uuid, data.Name); let mut folder = Folder::new(headers.user.uuid, data.Name);
folder.save(&mut conn).await?; folder.save(&mut conn).await?;
nt.send_folder_update(UpdateType::FolderCreate, &folder).await; nt.send_folder_update(UpdateType::SyncFolderCreate, &folder, &headers.device.uuid).await;
Ok(Json(folder.to_json())) Ok(Json(folder.to_json()))
} }
@ -88,7 +88,7 @@ async fn put_folder(
folder.name = data.Name; folder.name = data.Name;
folder.save(&mut conn).await?; folder.save(&mut conn).await?;
nt.send_folder_update(UpdateType::FolderUpdate, &folder).await; nt.send_folder_update(UpdateType::SyncFolderUpdate, &folder, &headers.device.uuid).await;
Ok(Json(folder.to_json())) Ok(Json(folder.to_json()))
} }
@ -112,6 +112,6 @@ async fn delete_folder(uuid: String, headers: Headers, mut conn: DbConn, nt: Not
// Delete the actual folder entry // Delete the actual folder entry
folder.delete(&mut conn).await?; folder.delete(&mut conn).await?;
nt.send_folder_update(UpdateType::FolderDelete, &folder).await; nt.send_folder_update(UpdateType::SyncFolderDelete, &folder, &headers.device.uuid).await;
Ok(()) Ok(())
} }

View File

@ -7,8 +7,7 @@ mod organizations;
mod sends; mod sends;
pub mod two_factor; pub mod two_factor;
pub use ciphers::purge_trashed_ciphers; pub use ciphers::{purge_trashed_ciphers, CipherSyncData, CipherSyncType};
pub use ciphers::{CipherSyncData, CipherSyncType};
pub use emergency_access::{emergency_notification_reminder_job, emergency_request_timeout_job}; pub use emergency_access::{emergency_notification_reminder_job, emergency_request_timeout_job};
pub use events::{event_cleanup_job, log_event, log_user_event}; pub use events::{event_cleanup_job, log_event, log_user_event};
pub use sends::purge_sends; pub use sends::purge_sends;
@ -47,13 +46,11 @@ pub fn events_routes() -> Vec<Route> {
// //
// Move this somewhere else // Move this somewhere else
// //
use rocket::serde::json::Json; use rocket::{serde::json::Json, Catcher, Route};
use rocket::Catcher;
use rocket::Route;
use serde_json::Value; use serde_json::Value;
use crate::{ use crate::{
api::{JsonResult, JsonUpcase}, api::{JsonResult, JsonUpcase, Notify, UpdateType},
auth::Headers, auth::Headers,
db::DbConn, db::DbConn,
error::Error, error::Error,
@ -138,7 +135,12 @@ struct EquivDomainData {
} }
#[post("/settings/domains", data = "<data>")] #[post("/settings/domains", data = "<data>")]
async fn post_eq_domains(data: JsonUpcase<EquivDomainData>, headers: Headers, mut conn: DbConn) -> JsonResult { async fn post_eq_domains(
data: JsonUpcase<EquivDomainData>,
headers: Headers,
mut conn: DbConn,
nt: Notify<'_>,
) -> JsonResult {
let data: EquivDomainData = data.into_inner().data; let data: EquivDomainData = data.into_inner().data;
let excluded_globals = data.ExcludedGlobalEquivalentDomains.unwrap_or_default(); let excluded_globals = data.ExcludedGlobalEquivalentDomains.unwrap_or_default();
@ -152,12 +154,19 @@ async fn post_eq_domains(data: JsonUpcase<EquivDomainData>, headers: Headers, mu
user.save(&mut conn).await?; user.save(&mut conn).await?;
nt.send_user_update(UpdateType::SyncSettings, &user).await;
Ok(Json(json!({}))) Ok(Json(json!({})))
} }
#[put("/settings/domains", data = "<data>")] #[put("/settings/domains", data = "<data>")]
async fn put_eq_domains(data: JsonUpcase<EquivDomainData>, headers: Headers, conn: DbConn) -> JsonResult { async fn put_eq_domains(
post_eq_domains(data, headers, conn).await data: JsonUpcase<EquivDomainData>,
headers: Headers,
conn: DbConn,
nt: Notify<'_>,
) -> JsonResult {
post_eq_domains(data, headers, conn, nt).await
} }
#[get("/hibp/breach?<username>")] #[get("/hibp/breach?<username>")]

View File

@ -957,6 +957,7 @@ async fn bulk_confirm_invite(
headers: AdminHeaders, headers: AdminHeaders,
mut conn: DbConn, mut conn: DbConn,
ip: ClientIp, ip: ClientIp,
nt: Notify<'_>,
) -> Json<Value> { ) -> Json<Value> {
let data = data.into_inner().data; let data = data.into_inner().data;
@ -966,7 +967,8 @@ async fn bulk_confirm_invite(
for invite in keys { for invite in keys {
let org_user_id = invite["Id"].as_str().unwrap_or_default(); let org_user_id = invite["Id"].as_str().unwrap_or_default();
let user_key = invite["Key"].as_str().unwrap_or_default(); let user_key = invite["Key"].as_str().unwrap_or_default();
let err_msg = match _confirm_invite(&org_id, org_user_id, user_key, &headers, &mut conn, &ip).await { let err_msg = match _confirm_invite(&org_id, org_user_id, user_key, &headers, &mut conn, &ip, &nt).await
{
Ok(_) => String::new(), Ok(_) => String::new(),
Err(e) => format!("{:?}", e), Err(e) => format!("{:?}", e),
}; };
@ -998,10 +1000,11 @@ async fn confirm_invite(
headers: AdminHeaders, headers: AdminHeaders,
mut conn: DbConn, mut conn: DbConn,
ip: ClientIp, ip: ClientIp,
nt: Notify<'_>,
) -> EmptyResult { ) -> EmptyResult {
let data = data.into_inner().data; let data = data.into_inner().data;
let user_key = data["Key"].as_str().unwrap_or_default(); let user_key = data["Key"].as_str().unwrap_or_default();
_confirm_invite(&org_id, &org_user_id, user_key, &headers, &mut conn, &ip).await _confirm_invite(&org_id, &org_user_id, user_key, &headers, &mut conn, &ip, &nt).await
} }
async fn _confirm_invite( async fn _confirm_invite(
@ -1011,6 +1014,7 @@ async fn _confirm_invite(
headers: &AdminHeaders, headers: &AdminHeaders,
conn: &mut DbConn, conn: &mut DbConn,
ip: &ClientIp, ip: &ClientIp,
nt: &Notify<'_>,
) -> EmptyResult { ) -> EmptyResult {
if key.is_empty() || org_user_id.is_empty() { if key.is_empty() || org_user_id.is_empty() {
err!("Key or UserId is not set, unable to process request"); err!("Key or UserId is not set, unable to process request");
@ -1069,7 +1073,13 @@ async fn _confirm_invite(
mail::send_invite_confirmed(&address, &org_name).await?; mail::send_invite_confirmed(&address, &org_name).await?;
} }
user_to_confirm.save(conn).await let save_result = user_to_confirm.save(conn).await;
if let Some(user) = User::find_by_uuid(&user_to_confirm.user_uuid, conn).await {
nt.send_user_update(UpdateType::SyncOrgKeys, &user).await;
}
save_result
} }
#[get("/organizations/<org_id>/users/<org_user_id>")] #[get("/organizations/<org_id>/users/<org_user_id>")]
@ -1206,12 +1216,13 @@ async fn bulk_delete_user(
headers: AdminHeaders, headers: AdminHeaders,
mut conn: DbConn, mut conn: DbConn,
ip: ClientIp, ip: ClientIp,
nt: Notify<'_>,
) -> Json<Value> { ) -> Json<Value> {
let data: OrgBulkIds = data.into_inner().data; let data: OrgBulkIds = data.into_inner().data;
let mut bulk_response = Vec::new(); let mut bulk_response = Vec::new();
for org_user_id in data.Ids { for org_user_id in data.Ids {
let err_msg = match _delete_user(&org_id, &org_user_id, &headers, &mut conn, &ip).await { let err_msg = match _delete_user(&org_id, &org_user_id, &headers, &mut conn, &ip, &nt).await {
Ok(_) => String::new(), Ok(_) => String::new(),
Err(e) => format!("{:?}", e), Err(e) => format!("{:?}", e),
}; };
@ -1239,8 +1250,9 @@ async fn delete_user(
headers: AdminHeaders, headers: AdminHeaders,
mut conn: DbConn, mut conn: DbConn,
ip: ClientIp, ip: ClientIp,
nt: Notify<'_>,
) -> EmptyResult { ) -> EmptyResult {
_delete_user(&org_id, &org_user_id, &headers, &mut conn, &ip).await _delete_user(&org_id, &org_user_id, &headers, &mut conn, &ip, &nt).await
} }
#[post("/organizations/<org_id>/users/<org_user_id>/delete")] #[post("/organizations/<org_id>/users/<org_user_id>/delete")]
@ -1250,8 +1262,9 @@ async fn post_delete_user(
headers: AdminHeaders, headers: AdminHeaders,
mut conn: DbConn, mut conn: DbConn,
ip: ClientIp, ip: ClientIp,
nt: Notify<'_>,
) -> EmptyResult { ) -> EmptyResult {
_delete_user(&org_id, &org_user_id, &headers, &mut conn, &ip).await _delete_user(&org_id, &org_user_id, &headers, &mut conn, &ip, &nt).await
} }
async fn _delete_user( async fn _delete_user(
@ -1260,6 +1273,7 @@ async fn _delete_user(
headers: &AdminHeaders, headers: &AdminHeaders,
conn: &mut DbConn, conn: &mut DbConn,
ip: &ClientIp, ip: &ClientIp,
nt: &Notify<'_>,
) -> EmptyResult { ) -> EmptyResult {
let user_to_delete = match UserOrganization::find_by_uuid_and_org(org_user_id, org_id, conn).await { let user_to_delete = match UserOrganization::find_by_uuid_and_org(org_user_id, org_id, conn).await {
Some(user) => user, Some(user) => user,
@ -1288,6 +1302,10 @@ async fn _delete_user(
) )
.await; .await;
if let Some(user) = User::find_by_uuid(&user_to_delete.user_uuid, conn).await {
nt.send_user_update(UpdateType::SyncOrgKeys, &user).await;
}
user_to_delete.delete(conn).await user_to_delete.delete(conn).await
} }

View File

@ -355,6 +355,7 @@ async fn post_access(
data: JsonUpcase<SendAccessData>, data: JsonUpcase<SendAccessData>,
mut conn: DbConn, mut conn: DbConn,
ip: ClientIp, ip: ClientIp,
nt: Notify<'_>,
) -> JsonResult { ) -> JsonResult {
let mut send = match Send::find_by_access_id(&access_id, &mut conn).await { let mut send = match Send::find_by_access_id(&access_id, &mut conn).await {
Some(s) => s, Some(s) => s,
@ -396,6 +397,8 @@ async fn post_access(
send.save(&mut conn).await?; send.save(&mut conn).await?;
nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&mut conn).await).await;
Ok(Json(send.to_json_access(&mut conn).await)) Ok(Json(send.to_json_access(&mut conn).await))
} }
@ -406,6 +409,7 @@ async fn post_access_file(
data: JsonUpcase<SendAccessData>, data: JsonUpcase<SendAccessData>,
host: Host, host: Host,
mut conn: DbConn, mut conn: DbConn,
nt: Notify<'_>,
) -> JsonResult { ) -> JsonResult {
let mut send = match Send::find_by_uuid(&send_id, &mut conn).await { let mut send = match Send::find_by_uuid(&send_id, &mut conn).await {
Some(s) => s, Some(s) => s,
@ -444,6 +448,8 @@ async fn post_access_file(
send.save(&mut conn).await?; send.save(&mut conn).await?;
nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&mut conn).await).await;
let token_claims = crate::auth::generate_send_claims(&send_id, &file_id); let token_claims = crate::auth::generate_send_claims(&send_id, &file_id);
let token = crate::auth::encode_jwt(&token_claims); let token = crate::auth::encode_jwt(&token_claims);
Ok(Json(json!({ Ok(Json(json!({

View File

@ -164,12 +164,13 @@ impl WebSocketUsers {
let data = create_update( let data = create_update(
vec![("UserId".into(), user.uuid.clone().into()), ("Date".into(), serialize_date(user.updated_at))], vec![("UserId".into(), user.uuid.clone().into()), ("Date".into(), serialize_date(user.updated_at))],
ut, ut,
None,
); );
self.send_update(&user.uuid, &data).await; self.send_update(&user.uuid, &data).await;
} }
pub async fn send_folder_update(&self, ut: UpdateType, folder: &Folder) { pub async fn send_folder_update(&self, ut: UpdateType, folder: &Folder, acting_device_uuid: &String) {
let data = create_update( let data = create_update(
vec![ vec![
("Id".into(), folder.uuid.clone().into()), ("Id".into(), folder.uuid.clone().into()),
@ -177,12 +178,19 @@ impl WebSocketUsers {
("RevisionDate".into(), serialize_date(folder.updated_at)), ("RevisionDate".into(), serialize_date(folder.updated_at)),
], ],
ut, ut,
Some(acting_device_uuid.into()),
); );
self.send_update(&folder.user_uuid, &data).await; self.send_update(&folder.user_uuid, &data).await;
} }
pub async fn send_cipher_update(&self, ut: UpdateType, cipher: &Cipher, user_uuids: &[String]) { pub async fn send_cipher_update(
&self,
ut: UpdateType,
cipher: &Cipher,
user_uuids: &[String],
acting_device_uuid: &String,
) {
let user_uuid = convert_option(cipher.user_uuid.clone()); let user_uuid = convert_option(cipher.user_uuid.clone());
let org_uuid = convert_option(cipher.organization_uuid.clone()); let org_uuid = convert_option(cipher.organization_uuid.clone());
@ -195,6 +203,7 @@ impl WebSocketUsers {
("RevisionDate".into(), serialize_date(cipher.updated_at)), ("RevisionDate".into(), serialize_date(cipher.updated_at)),
], ],
ut, ut,
Some(acting_device_uuid.into()),
); );
for uuid in user_uuids { for uuid in user_uuids {
@ -212,6 +221,7 @@ impl WebSocketUsers {
("RevisionDate".into(), serialize_date(send.revision_date)), ("RevisionDate".into(), serialize_date(send.revision_date)),
], ],
ut, ut,
None,
); );
for uuid in user_uuids { for uuid in user_uuids {
@ -228,14 +238,14 @@ impl WebSocketUsers {
"ReceiveMessage", // Target "ReceiveMessage", // Target
[ // Arguments [ // Arguments
{ {
"ContextId": "app_id", "ContextId": acting_device_uuid || Nil,
"Type": ut as i32, "Type": ut as i32,
"Payload": {} "Payload": {}
} }
] ]
] ]
*/ */
fn create_update(payload: Vec<(Value, Value)>, ut: UpdateType) -> Vec<u8> { fn create_update(payload: Vec<(Value, Value)>, ut: UpdateType, acting_device_uuid: Option<String>) -> Vec<u8> {
use rmpv::Value as V; use rmpv::Value as V;
let value = V::Array(vec![ let value = V::Array(vec![
@ -244,7 +254,7 @@ fn create_update(payload: Vec<(Value, Value)>, ut: UpdateType) -> Vec<u8> {
V::Nil, V::Nil,
"ReceiveMessage".into(), "ReceiveMessage".into(),
V::Array(vec![V::Map(vec![ V::Array(vec![V::Map(vec![
("ContextId".into(), "app_id".into()), ("ContextId".into(), acting_device_uuid.map(|v| v.into()).unwrap_or_else(|| V::Nil)),
("Type".into(), (ut as i32).into()), ("Type".into(), (ut as i32).into()),
("Payload".into(), payload.into()), ("Payload".into(), payload.into()),
])]), ])]),
@ -260,17 +270,17 @@ fn create_ping() -> Vec<u8> {
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Eq, PartialEq)] #[derive(Eq, PartialEq)]
pub enum UpdateType { pub enum UpdateType {
CipherUpdate = 0, SyncCipherUpdate = 0,
CipherCreate = 1, SyncCipherCreate = 1,
LoginDelete = 2, SyncLoginDelete = 2,
FolderDelete = 3, SyncFolderDelete = 3,
Ciphers = 4, SyncCiphers = 4,
Vault = 5, SyncVault = 5,
OrgKeys = 6, SyncOrgKeys = 6,
FolderCreate = 7, SyncFolderCreate = 7,
FolderUpdate = 8, SyncFolderUpdate = 8,
CipherDelete = 9, SyncCipherDelete = 9,
SyncSettings = 10, SyncSettings = 10,
LogOut = 11, LogOut = 11,
@ -279,6 +289,9 @@ pub enum UpdateType {
SyncSendUpdate = 13, SyncSendUpdate = 13,
SyncSendDelete = 14, SyncSendDelete = 14,
AuthRequest = 15,
AuthRequestResponse = 16,
None = 100, None = 100,
} }