Add Organizational event logging feature

This PR adds event/audit logging support for organizations.
By default this feature is disabled, since it does log a lot and adds
extra database transactions.

All events are touched except a few, since we do not support those
features (yet), like SSO for example.

This feature is tested with multiple clients and all database types.

Fixes #229
This commit is contained in:
BlackDex
2022-11-20 19:15:45 +01:00
parent f3beaea9e9
commit 2ea9b66943
31 changed files with 1887 additions and 240 deletions

View File

@@ -10,8 +10,8 @@ use rocket::{
use serde_json::Value;
use crate::{
api::{self, EmptyResult, JsonResult, JsonUpcase, Notify, PasswordData, UpdateType},
auth::Headers,
api::{self, core::log_event, EmptyResult, JsonResult, JsonUpcase, Notify, PasswordData, UpdateType},
auth::{ClientIp, Headers},
crypto,
db::{models::*, DbConn, DbPool},
CONFIG,
@@ -247,9 +247,10 @@ async fn post_ciphers_admin(
data: JsonUpcase<ShareCipherData>,
headers: Headers,
conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> JsonResult {
post_ciphers_create(data, headers, conn, nt).await
post_ciphers_create(data, headers, conn, ip, nt).await
}
/// Called when creating a new org-owned cipher, or cloning a cipher (whether
@@ -260,6 +261,7 @@ async fn post_ciphers_create(
data: JsonUpcase<ShareCipherData>,
headers: Headers,
mut conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> JsonResult {
let mut data: ShareCipherData = data.into_inner().data;
@@ -287,12 +289,18 @@ async fn post_ciphers_create(
// or otherwise), we can just ignore this field entirely.
data.Cipher.LastKnownRevisionDate = None;
share_cipher_by_uuid(&cipher.uuid, data, &headers, &mut conn, &nt).await
share_cipher_by_uuid(&cipher.uuid, data, &headers, &mut conn, &ip, &nt).await
}
/// Called when creating a new user-owned cipher.
#[post("/ciphers", data = "<data>")]
async fn post_ciphers(data: JsonUpcase<CipherData>, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
async fn post_ciphers(
data: JsonUpcase<CipherData>,
headers: Headers,
mut conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> JsonResult {
let mut data: CipherData = data.into_inner().data;
// The web/browser clients set this field to null as expected, but the
@@ -302,7 +310,7 @@ async fn post_ciphers(data: JsonUpcase<CipherData>, headers: Headers, mut conn:
data.LastKnownRevisionDate = None;
let mut cipher = Cipher::new(data.Type, data.Name.clone());
update_cipher_from_data(&mut cipher, data, &headers, false, &mut conn, &nt, UpdateType::CipherCreate).await?;
update_cipher_from_data(&mut cipher, data, &headers, false, &mut conn, &ip, &nt, UpdateType::CipherCreate).await?;
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, &mut conn).await))
}
@@ -329,12 +337,14 @@ async fn enforce_personal_ownership_policy(
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub async fn update_cipher_from_data(
cipher: &mut Cipher,
data: CipherData,
headers: &Headers,
shared_to_collection: bool,
conn: &mut DbConn,
ip: &ClientIp,
nt: &Notify<'_>,
ut: UpdateType,
) -> EmptyResult {
@@ -356,6 +366,9 @@ pub async fn update_cipher_from_data(
err!("Organization mismatch. Please resync the client before updating the cipher")
}
// Check if this cipher is being transferred from a personal to an organization vault
let transfer_cipher = cipher.organization_uuid.is_none() && data.OrganizationId.is_some();
if let Some(org_id) = data.OrganizationId {
match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, conn).await {
None => err!("You don't have permission to add item to organization"),
@@ -460,6 +473,26 @@ pub async fn update_cipher_from_data(
cipher.set_favorite(data.Favorite, &headers.user.uuid, conn).await?;
if ut != UpdateType::None {
// 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,
(_, _) => EventType::CipherUpdated,
};
log_event(
event_type as i32,
&cipher.uuid,
String::from(org_uuid),
headers.user.uuid.clone(),
headers.device.atype,
&ip.ip,
conn,
)
.await;
}
nt.send_cipher_update(ut, cipher, &cipher.update_users_revision(conn).await).await;
}
@@ -488,6 +521,7 @@ async fn post_ciphers_import(
data: JsonUpcase<ImportData>,
headers: Headers,
mut conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> EmptyResult {
enforce_personal_ownership_policy(None, &headers, &mut conn).await?;
@@ -516,7 +550,8 @@ async fn post_ciphers_import(
cipher_data.FolderId = folder_uuid;
let mut cipher = Cipher::new(cipher_data.Type, cipher_data.Name.clone());
update_cipher_from_data(&mut cipher, cipher_data, &headers, false, &mut conn, &nt, UpdateType::None).await?;
update_cipher_from_data(&mut cipher, cipher_data, &headers, false, &mut conn, &ip, &nt, UpdateType::None)
.await?;
}
let mut user = headers.user;
@@ -532,9 +567,10 @@ async fn put_cipher_admin(
data: JsonUpcase<CipherData>,
headers: Headers,
conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> JsonResult {
put_cipher(uuid, data, headers, conn, nt).await
put_cipher(uuid, data, headers, conn, ip, nt).await
}
#[post("/ciphers/<uuid>/admin", data = "<data>")]
@@ -543,9 +579,10 @@ async fn post_cipher_admin(
data: JsonUpcase<CipherData>,
headers: Headers,
conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> JsonResult {
post_cipher(uuid, data, headers, conn, nt).await
post_cipher(uuid, data, headers, conn, ip, nt).await
}
#[post("/ciphers/<uuid>", data = "<data>")]
@@ -554,9 +591,10 @@ async fn post_cipher(
data: JsonUpcase<CipherData>,
headers: Headers,
conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> JsonResult {
put_cipher(uuid, data, headers, conn, nt).await
put_cipher(uuid, data, headers, conn, ip, nt).await
}
#[put("/ciphers/<uuid>", data = "<data>")]
@@ -565,6 +603,7 @@ async fn put_cipher(
data: JsonUpcase<CipherData>,
headers: Headers,
mut conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> JsonResult {
let data: CipherData = data.into_inner().data;
@@ -583,7 +622,7 @@ async fn put_cipher(
err!("Cipher is not write accessible")
}
update_cipher_from_data(&mut cipher, data, &headers, false, &mut conn, &nt, UpdateType::CipherUpdate).await?;
update_cipher_from_data(&mut cipher, data, &headers, false, &mut conn, &ip, &nt, UpdateType::CipherUpdate).await?;
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, &mut conn).await))
}
@@ -600,8 +639,9 @@ async fn put_collections_update(
data: JsonUpcase<CollectionsAdminData>,
headers: Headers,
conn: DbConn,
ip: ClientIp,
) -> EmptyResult {
post_collections_admin(uuid, data, headers, conn).await
post_collections_admin(uuid, data, headers, conn, ip).await
}
#[post("/ciphers/<uuid>/collections", data = "<data>")]
@@ -610,8 +650,9 @@ async fn post_collections_update(
data: JsonUpcase<CollectionsAdminData>,
headers: Headers,
conn: DbConn,
ip: ClientIp,
) -> EmptyResult {
post_collections_admin(uuid, data, headers, conn).await
post_collections_admin(uuid, data, headers, conn, ip).await
}
#[put("/ciphers/<uuid>/collections-admin", data = "<data>")]
@@ -620,8 +661,9 @@ async fn put_collections_admin(
data: JsonUpcase<CollectionsAdminData>,
headers: Headers,
conn: DbConn,
ip: ClientIp,
) -> EmptyResult {
post_collections_admin(uuid, data, headers, conn).await
post_collections_admin(uuid, data, headers, conn, ip).await
}
#[post("/ciphers/<uuid>/collections-admin", data = "<data>")]
@@ -630,6 +672,7 @@ async fn post_collections_admin(
data: JsonUpcase<CollectionsAdminData>,
headers: Headers,
mut conn: DbConn,
ip: ClientIp,
) -> EmptyResult {
let data: CollectionsAdminData = data.into_inner().data;
@@ -665,6 +708,17 @@ async fn post_collections_admin(
}
}
log_event(
EventType::CipherUpdatedCollections as i32,
&cipher.uuid,
cipher.organization_uuid.unwrap(),
headers.user.uuid.clone(),
headers.device.atype,
&ip.ip,
&mut conn,
)
.await;
Ok(())
}
@@ -681,11 +735,12 @@ async fn post_cipher_share(
data: JsonUpcase<ShareCipherData>,
headers: Headers,
mut conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> JsonResult {
let data: ShareCipherData = data.into_inner().data;
share_cipher_by_uuid(&uuid, data, &headers, &mut conn, &nt).await
share_cipher_by_uuid(&uuid, data, &headers, &mut conn, &ip, &nt).await
}
#[put("/ciphers/<uuid>/share", data = "<data>")]
@@ -694,11 +749,12 @@ async fn put_cipher_share(
data: JsonUpcase<ShareCipherData>,
headers: Headers,
mut conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> JsonResult {
let data: ShareCipherData = data.into_inner().data;
share_cipher_by_uuid(&uuid, data, &headers, &mut conn, &nt).await
share_cipher_by_uuid(&uuid, data, &headers, &mut conn, &ip, &nt).await
}
#[derive(Deserialize)]
@@ -713,6 +769,7 @@ async fn put_cipher_share_selected(
data: JsonUpcase<ShareSelectedCipherData>,
headers: Headers,
mut conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> EmptyResult {
let mut data: ShareSelectedCipherData = data.into_inner().data;
@@ -740,7 +797,7 @@ async fn put_cipher_share_selected(
};
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, &ip, &nt).await?,
None => err!("Request missing ids field"),
};
}
@@ -753,6 +810,7 @@ async fn share_cipher_by_uuid(
data: ShareCipherData,
headers: &Headers,
conn: &mut DbConn,
ip: &ClientIp,
nt: &Notify<'_>,
) -> JsonResult {
let mut cipher = match Cipher::find_by_uuid(uuid, conn).await {
@@ -768,37 +826,30 @@ async fn share_cipher_by_uuid(
let mut shared_to_collection = false;
match data.Cipher.OrganizationId.clone() {
// If we don't get an organization ID, we don't do anything
// No error because this is used when using the Clone functionality
None => {}
Some(organization_uuid) => {
for uuid in &data.CollectionIds {
match Collection::find_by_uuid_and_org(uuid, &organization_uuid, conn).await {
None => err!("Invalid collection ID provided"),
Some(collection) => {
if collection.is_writable_by_user(&headers.user.uuid, conn).await {
CollectionCipher::save(&cipher.uuid, &collection.uuid, conn).await?;
shared_to_collection = true;
} else {
err!("No rights to modify the collection")
}
if let Some(organization_uuid) = &data.Cipher.OrganizationId {
for uuid in &data.CollectionIds {
match Collection::find_by_uuid_and_org(uuid, organization_uuid, conn).await {
None => err!("Invalid collection ID provided"),
Some(collection) => {
if collection.is_writable_by_user(&headers.user.uuid, conn).await {
CollectionCipher::save(&cipher.uuid, &collection.uuid, conn).await?;
shared_to_collection = true;
} else {
err!("No rights to modify the collection")
}
}
}
}
};
update_cipher_from_data(
&mut cipher,
data.Cipher,
headers,
shared_to_collection,
conn,
nt,
UpdateType::CipherUpdate,
)
.await?;
// When LastKnownRevisionDate is None, it is a new cipher, so send CipherCreate.
let ut = if data.Cipher.LastKnownRevisionDate.is_some() {
UpdateType::CipherUpdate
} else {
UpdateType::CipherCreate
};
update_cipher_from_data(&mut cipher, data.Cipher, headers, shared_to_collection, conn, ip, nt, ut).await?;
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, conn).await))
}
@@ -893,6 +944,7 @@ async fn save_attachment(
data: Form<UploadData<'_>>,
headers: &Headers,
mut conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> Result<(Cipher, DbConn), crate::error::Error> {
let cipher = match Cipher::find_by_uuid(&cipher_uuid, &mut conn).await {
@@ -1011,6 +1063,19 @@ async fn save_attachment(
nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(&mut conn).await).await;
if let Some(org_uuid) = &cipher.organization_uuid {
log_event(
EventType::CipherAttachmentCreated as i32,
&cipher.uuid,
String::from(org_uuid),
headers.user.uuid.clone(),
headers.device.atype,
&ip.ip,
&mut conn,
)
.await;
}
Ok((cipher, conn))
}
@@ -1025,6 +1090,7 @@ async fn post_attachment_v2_data(
data: Form<UploadData<'_>>,
headers: Headers,
mut conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> EmptyResult {
let attachment = match Attachment::find_by_id(&attachment_id, &mut conn).await {
@@ -1033,7 +1099,7 @@ async fn post_attachment_v2_data(
None => err!("Attachment doesn't exist"),
};
save_attachment(attachment, uuid, data, &headers, conn, nt).await?;
save_attachment(attachment, uuid, data, &headers, conn, ip, nt).await?;
Ok(())
}
@@ -1045,13 +1111,14 @@ async fn post_attachment(
data: Form<UploadData<'_>>,
headers: Headers,
conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> JsonResult {
// Setting this as None signifies to save_attachment() that it should create
// the attachment database record as well as saving the data to disk.
let attachment = None;
let (cipher, mut conn) = save_attachment(attachment, uuid, data, &headers, conn, nt).await?;
let (cipher, mut conn) = save_attachment(attachment, uuid, data, &headers, conn, ip, nt).await?;
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, &mut conn).await))
}
@@ -1062,9 +1129,10 @@ async fn post_attachment_admin(
data: Form<UploadData<'_>>,
headers: Headers,
conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> JsonResult {
post_attachment(uuid, data, headers, conn, nt).await
post_attachment(uuid, data, headers, conn, ip, nt).await
}
#[post("/ciphers/<uuid>/attachment/<attachment_id>/share", format = "multipart/form-data", data = "<data>")]
@@ -1074,10 +1142,11 @@ async fn post_attachment_share(
data: Form<UploadData<'_>>,
headers: Headers,
mut conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> JsonResult {
_delete_cipher_attachment_by_id(&uuid, &attachment_id, &headers, &mut conn, &nt).await?;
post_attachment(uuid, data, headers, conn, nt).await
_delete_cipher_attachment_by_id(&uuid, &attachment_id, &headers, &mut conn, &ip, &nt).await?;
post_attachment(uuid, data, headers, conn, ip, nt).await
}
#[post("/ciphers/<uuid>/attachment/<attachment_id>/delete-admin")]
@@ -1086,9 +1155,10 @@ async fn delete_attachment_post_admin(
attachment_id: String,
headers: Headers,
conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> EmptyResult {
delete_attachment(uuid, attachment_id, headers, conn, nt).await
delete_attachment(uuid, attachment_id, headers, conn, ip, nt).await
}
#[post("/ciphers/<uuid>/attachment/<attachment_id>/delete")]
@@ -1097,9 +1167,10 @@ async fn delete_attachment_post(
attachment_id: String,
headers: Headers,
conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> EmptyResult {
delete_attachment(uuid, attachment_id, headers, conn, nt).await
delete_attachment(uuid, attachment_id, headers, conn, ip, nt).await
}
#[delete("/ciphers/<uuid>/attachment/<attachment_id>")]
@@ -1108,9 +1179,10 @@ async fn delete_attachment(
attachment_id: String,
headers: Headers,
mut conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> EmptyResult {
_delete_cipher_attachment_by_id(&uuid, &attachment_id, &headers, &mut conn, &nt).await
_delete_cipher_attachment_by_id(&uuid, &attachment_id, &headers, &mut conn, &ip, &nt).await
}
#[delete("/ciphers/<uuid>/attachment/<attachment_id>/admin")]
@@ -1119,39 +1191,70 @@ async fn delete_attachment_admin(
attachment_id: String,
headers: Headers,
mut conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> EmptyResult {
_delete_cipher_attachment_by_id(&uuid, &attachment_id, &headers, &mut conn, &nt).await
_delete_cipher_attachment_by_id(&uuid, &attachment_id, &headers, &mut conn, &ip, &nt).await
}
#[post("/ciphers/<uuid>/delete")]
async fn delete_cipher_post(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
_delete_cipher_by_uuid(&uuid, &headers, &mut conn, false, &nt).await
async fn delete_cipher_post(
uuid: String,
headers: Headers,
mut conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> EmptyResult {
_delete_cipher_by_uuid(&uuid, &headers, &mut conn, false, &ip, &nt).await // permanent delete
}
#[post("/ciphers/<uuid>/delete-admin")]
async fn delete_cipher_post_admin(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
_delete_cipher_by_uuid(&uuid, &headers, &mut conn, false, &nt).await
async fn delete_cipher_post_admin(
uuid: String,
headers: Headers,
mut conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> EmptyResult {
_delete_cipher_by_uuid(&uuid, &headers, &mut conn, false, &ip, &nt).await // permanent delete
}
#[put("/ciphers/<uuid>/delete")]
async fn delete_cipher_put(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
_delete_cipher_by_uuid(&uuid, &headers, &mut conn, true, &nt).await
async fn delete_cipher_put(
uuid: String,
headers: Headers,
mut conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> EmptyResult {
_delete_cipher_by_uuid(&uuid, &headers, &mut conn, true, &ip, &nt).await // soft delete
}
#[put("/ciphers/<uuid>/delete-admin")]
async fn delete_cipher_put_admin(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
_delete_cipher_by_uuid(&uuid, &headers, &mut conn, true, &nt).await
async fn delete_cipher_put_admin(
uuid: String,
headers: Headers,
mut conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> EmptyResult {
_delete_cipher_by_uuid(&uuid, &headers, &mut conn, true, &ip, &nt).await
}
#[delete("/ciphers/<uuid>")]
async fn delete_cipher(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
_delete_cipher_by_uuid(&uuid, &headers, &mut conn, false, &nt).await
async fn delete_cipher(uuid: String, headers: Headers, mut conn: DbConn, ip: ClientIp, nt: Notify<'_>) -> EmptyResult {
_delete_cipher_by_uuid(&uuid, &headers, &mut conn, false, &ip, &nt).await // permanent delete
}
#[delete("/ciphers/<uuid>/admin")]
async fn delete_cipher_admin(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
_delete_cipher_by_uuid(&uuid, &headers, &mut conn, false, &nt).await
async fn delete_cipher_admin(
uuid: String,
headers: Headers,
mut conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> EmptyResult {
_delete_cipher_by_uuid(&uuid, &headers, &mut conn, false, &ip, &nt).await // permanent delete
}
#[delete("/ciphers", data = "<data>")]
@@ -1159,9 +1262,10 @@ async fn delete_cipher_selected(
data: JsonUpcase<Value>,
headers: Headers,
conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> EmptyResult {
_delete_multiple_ciphers(data, headers, conn, false, nt).await
_delete_multiple_ciphers(data, headers, conn, false, ip, nt).await // permanent delete
}
#[post("/ciphers/delete", data = "<data>")]
@@ -1169,9 +1273,10 @@ async fn delete_cipher_selected_post(
data: JsonUpcase<Value>,
headers: Headers,
conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> EmptyResult {
_delete_multiple_ciphers(data, headers, conn, false, nt).await
_delete_multiple_ciphers(data, headers, conn, false, ip, nt).await // permanent delete
}
#[put("/ciphers/delete", data = "<data>")]
@@ -1179,9 +1284,10 @@ async fn delete_cipher_selected_put(
data: JsonUpcase<Value>,
headers: Headers,
conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> EmptyResult {
_delete_multiple_ciphers(data, headers, conn, true, nt).await // soft delete
_delete_multiple_ciphers(data, headers, conn, true, ip, nt).await // soft delete
}
#[delete("/ciphers/admin", data = "<data>")]
@@ -1189,9 +1295,10 @@ async fn delete_cipher_selected_admin(
data: JsonUpcase<Value>,
headers: Headers,
conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> EmptyResult {
delete_cipher_selected(data, headers, conn, nt).await
_delete_multiple_ciphers(data, headers, conn, false, ip, nt).await // permanent delete
}
#[post("/ciphers/delete-admin", data = "<data>")]
@@ -1199,9 +1306,10 @@ async fn delete_cipher_selected_post_admin(
data: JsonUpcase<Value>,
headers: Headers,
conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> EmptyResult {
delete_cipher_selected_post(data, headers, conn, nt).await
_delete_multiple_ciphers(data, headers, conn, false, ip, nt).await // permanent delete
}
#[put("/ciphers/delete-admin", data = "<data>")]
@@ -1209,19 +1317,32 @@ async fn delete_cipher_selected_put_admin(
data: JsonUpcase<Value>,
headers: Headers,
conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> EmptyResult {
delete_cipher_selected_put(data, headers, conn, nt).await
_delete_multiple_ciphers(data, headers, conn, true, ip, nt).await // soft delete
}
#[put("/ciphers/<uuid>/restore")]
async fn restore_cipher_put(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
_restore_cipher_by_uuid(&uuid, &headers, &mut conn, &nt).await
async fn restore_cipher_put(
uuid: String,
headers: Headers,
mut conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> JsonResult {
_restore_cipher_by_uuid(&uuid, &headers, &mut conn, &ip, &nt).await
}
#[put("/ciphers/<uuid>/restore-admin")]
async fn restore_cipher_put_admin(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
_restore_cipher_by_uuid(&uuid, &headers, &mut conn, &nt).await
async fn restore_cipher_put_admin(
uuid: String,
headers: Headers,
mut conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> JsonResult {
_restore_cipher_by_uuid(&uuid, &headers, &mut conn, &ip, &nt).await
}
#[put("/ciphers/restore", data = "<data>")]
@@ -1229,9 +1350,10 @@ async fn restore_cipher_selected(
data: JsonUpcase<Value>,
headers: Headers,
mut conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> JsonResult {
_restore_multiple_ciphers(data, &headers, &mut conn, &nt).await
_restore_multiple_ciphers(data, &headers, &mut conn, ip, &nt).await
}
#[derive(Deserialize)]
@@ -1303,6 +1425,7 @@ async fn delete_all(
data: JsonUpcase<PasswordData>,
headers: Headers,
mut conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> EmptyResult {
let data: PasswordData = data.into_inner().data;
@@ -1323,6 +1446,18 @@ async fn delete_all(
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;
log_event(
EventType::OrganizationPurgedVault as i32,
&org_data.org_id,
org_data.org_id.clone(),
user.uuid,
headers.device.atype,
&ip.ip,
&mut conn,
)
.await;
Ok(())
} else {
err!("You don't have permission to purge the organization vault");
@@ -1354,6 +1489,7 @@ async fn _delete_cipher_by_uuid(
headers: &Headers,
conn: &mut DbConn,
soft_delete: bool,
ip: &ClientIp,
nt: &Notify<'_>,
) -> EmptyResult {
let mut cipher = match Cipher::find_by_uuid(uuid, conn).await {
@@ -1374,6 +1510,16 @@ async fn _delete_cipher_by_uuid(
nt.send_cipher_update(UpdateType::CipherDelete, &cipher, &cipher.update_users_revision(conn).await).await;
}
if let Some(org_uuid) = cipher.organization_uuid {
let event_type = match soft_delete {
true => EventType::CipherSoftDeleted as i32,
false => EventType::CipherDeleted as i32,
};
log_event(event_type, &cipher.uuid, org_uuid, headers.user.uuid.clone(), headers.device.atype, &ip.ip, conn)
.await;
}
Ok(())
}
@@ -1382,6 +1528,7 @@ async fn _delete_multiple_ciphers(
headers: Headers,
mut conn: DbConn,
soft_delete: bool,
ip: ClientIp,
nt: Notify<'_>,
) -> EmptyResult {
let data: Value = data.into_inner().data;
@@ -1395,7 +1542,7 @@ async fn _delete_multiple_ciphers(
};
for uuid in uuids {
if let error @ Err(_) = _delete_cipher_by_uuid(uuid, &headers, &mut conn, soft_delete, &nt).await {
if let error @ Err(_) = _delete_cipher_by_uuid(uuid, &headers, &mut conn, soft_delete, &ip, &nt).await {
return error;
};
}
@@ -1403,7 +1550,13 @@ async fn _delete_multiple_ciphers(
Ok(())
}
async fn _restore_cipher_by_uuid(uuid: &str, headers: &Headers, conn: &mut DbConn, nt: &Notify<'_>) -> JsonResult {
async fn _restore_cipher_by_uuid(
uuid: &str,
headers: &Headers,
conn: &mut DbConn,
ip: &ClientIp,
nt: &Notify<'_>,
) -> JsonResult {
let mut cipher = match Cipher::find_by_uuid(uuid, conn).await {
Some(cipher) => cipher,
None => err!("Cipher doesn't exist"),
@@ -1417,6 +1570,19 @@ async fn _restore_cipher_by_uuid(uuid: &str, headers: &Headers, conn: &mut DbCon
cipher.save(conn).await?;
nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(conn).await).await;
if let Some(org_uuid) = &cipher.organization_uuid {
log_event(
EventType::CipherRestored as i32,
&cipher.uuid.clone(),
String::from(org_uuid),
headers.user.uuid.clone(),
headers.device.atype,
&ip.ip,
conn,
)
.await;
}
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, conn).await))
}
@@ -1424,6 +1590,7 @@ async fn _restore_multiple_ciphers(
data: JsonUpcase<Value>,
headers: &Headers,
conn: &mut DbConn,
ip: ClientIp,
nt: &Notify<'_>,
) -> JsonResult {
let data: Value = data.into_inner().data;
@@ -1438,7 +1605,7 @@ async fn _restore_multiple_ciphers(
let mut ciphers: Vec<Value> = Vec::new();
for uuid in uuids {
match _restore_cipher_by_uuid(uuid, headers, conn, nt).await {
match _restore_cipher_by_uuid(uuid, headers, conn, &ip, nt).await {
Ok(json) => ciphers.push(json.into_inner()),
err => return err,
}
@@ -1456,6 +1623,7 @@ async fn _delete_cipher_attachment_by_id(
attachment_id: &str,
headers: &Headers,
conn: &mut DbConn,
ip: &ClientIp,
nt: &Notify<'_>,
) -> EmptyResult {
let attachment = match Attachment::find_by_id(attachment_id, conn).await {
@@ -1479,6 +1647,18 @@ 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;
if let Some(org_uuid) = cipher.organization_uuid {
log_event(
EventType::CipherAttachmentDeleted as i32,
&cipher.uuid,
org_uuid,
headers.user.uuid.clone(),
headers.device.atype,
&ip.ip,
conn,
)
.await;
}
Ok(())
}