Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update WebSocket Notifications #3076

Merged
merged 1 commit into from
Jan 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions src/api/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use rocket::{
};

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},
config::ConfigBuilder,
db::{backup_database, get_sql_server_version, models::*, DbConn, DbConnType},
Expand Down Expand Up @@ -380,22 +380,30 @@ async fn delete_user(uuid: String, _token: AdminToken, mut conn: DbConn, ip: Cli
}

#[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?;
Device::delete_all_by_user(&user.uuid, &mut conn).await?;
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")]
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?;
Device::delete_all_by_user(&user.uuid, &mut conn).await?;
user.reset_security_stamp();
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")]
Expand Down
47 changes: 39 additions & 8 deletions src/api/core/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ async fn post_password(
headers: Headers,
mut conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> EmptyResult {
let data: ChangePassData = data.into_inner().data;
let mut user = headers.user;
Expand All @@ -293,7 +294,11 @@ async fn post_password(
Some(vec![String::from("post_rotatekey"), String::from("get_contacts"), String::from("get_public_keys")]),
);
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)]
Expand All @@ -308,7 +313,7 @@ struct ChangeKdfData {
}

#[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 mut user = headers.user;

Expand All @@ -320,7 +325,11 @@ async fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, mut conn: D
user.client_kdf_type = data.Kdf;
user.set_password(&data.NewMasterPasswordHash, None);
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)]
Expand Down Expand Up @@ -388,6 +397,7 @@ async fn post_rotatekey(

// 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.
// 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)
.await?
}
Expand All @@ -399,11 +409,20 @@ async fn post_rotatekey(
user.private_key = Some(data.PrivateKey);
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>")]
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 mut user = headers.user;

Expand All @@ -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?;
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)]
Expand Down Expand Up @@ -465,7 +488,12 @@ struct ChangeEmailData {
}

#[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 mut user = headers.user;

Expand Down Expand Up @@ -507,8 +535,11 @@ async fn post_email(data: JsonUpcase<ChangeEmailData>, headers: Headers, mut con

user.set_password(&data.NewMasterPasswordHash, None);
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")]
Expand Down
73 changes: 56 additions & 17 deletions src/api/core/ciphers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,8 @@ async fn post_ciphers(
data.LastKnownRevisionDate = None;

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))
}
Expand Down Expand Up @@ -415,7 +416,14 @@ pub async fn update_cipher_from_data(
for (id, attachment) in attachments {
let mut saved_att = match Attachment::find_by_id(&id, conn).await {
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 {
Expand Down Expand Up @@ -482,8 +490,8 @@ pub async fn update_cipher_from_data(
// Only log events for organizational ciphers
if let Some(org_uuid) = &cipher.organization_uuid {
let event_type = match (&ut, transfer_cipher) {
(UpdateType::CipherCreate, true) => EventType::CipherCreated,
(UpdateType::CipherUpdate, true) => EventType::CipherShared,
(UpdateType::SyncCipherCreate, true) => EventType::CipherCreated,
(UpdateType::SyncCipherUpdate, true) => EventType::CipherShared,
(_, _) => EventType::CipherUpdated,
};

Expand All @@ -499,7 +507,7 @@ pub async fn update_cipher_from_data(
.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(())
Expand Down Expand Up @@ -562,7 +570,7 @@ async fn post_ciphers_import(

let mut user = headers.user;
user.update_revision(&mut conn).await?;
nt.send_user_update(UpdateType::Vault, &user).await;
nt.send_user_update(UpdateType::SyncVault, &user).await;
Ok(())
}

Expand Down Expand Up @@ -628,7 +636,8 @@ async fn put_cipher(
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))
}
Expand Down Expand Up @@ -850,9 +859,9 @@ async fn share_cipher_by_uuid(

// When LastKnownRevisionDate is None, it is a new cipher, so send CipherCreate.
let ut = if data.Cipher.LastKnownRevisionDate.is_some() {
UpdateType::CipherUpdate
UpdateType::SyncCipherUpdate
} else {
UpdateType::CipherCreate
UpdateType::SyncCipherCreate
};

update_cipher_from_data(&mut cipher, data.Cipher, headers, shared_to_collection, conn, ip, nt, ut).await?;
Expand Down Expand Up @@ -1067,7 +1076,13 @@ async fn save_attachment(
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 {
log_event(
Expand Down Expand Up @@ -1403,7 +1418,7 @@ async fn move_cipher_selected(
// Move cipher
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(())
Expand Down Expand Up @@ -1451,7 +1466,7 @@ async fn delete_all(
Some(user_org) => {
if user_org.atype == UserOrgType::Owner {
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(
EventType::OrganizationPurgedVault as i32,
Expand Down Expand Up @@ -1484,7 +1499,7 @@ async fn delete_all(
}

user.update_revision(&mut conn).await?;
nt.send_user_update(UpdateType::Vault, &user).await;
nt.send_user_update(UpdateType::SyncVault, &user).await;
Ok(())
}
}
Expand All @@ -1510,10 +1525,22 @@ async fn _delete_cipher_by_uuid(
if soft_delete {
cipher.deleted_at = Some(Utc::now().naive_utc());
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 {
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 {
Expand Down Expand Up @@ -1575,7 +1602,13 @@ async fn _restore_cipher_by_uuid(
cipher.deleted_at = None;
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 {
log_event(
EventType::CipherRestored as i32,
Expand Down Expand Up @@ -1652,7 +1685,13 @@ async fn _delete_cipher_attachment_by_id(

// Delete attachment
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 {
log_event(
EventType::CipherAttachmentDeleted as i32,
Expand Down
6 changes: 3 additions & 3 deletions src/api/core/folders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

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()))
}
Expand Down Expand Up @@ -88,7 +88,7 @@ async fn put_folder(
folder.name = data.Name;

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()))
}
Expand All @@ -112,6 +112,6 @@ async fn delete_folder(uuid: String, headers: Headers, mut conn: DbConn, nt: Not
// Delete the actual folder entry
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(())
}
Loading