mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2026-05-08 12:34:43 +02:00
fix: avoid panic in emergency access jobs and attachment save
This commit is contained in:
parent
7cf0c5d67e
commit
645164382e
2 changed files with 89 additions and 44 deletions
|
|
@ -11,6 +11,7 @@ use rocket::{
|
|||
use serde_json::Value;
|
||||
|
||||
use crate::auth::ClientVersion;
|
||||
use crate::error::MapResult;
|
||||
use crate::util::{deser_opt_nonempty_str, save_temp_file, NumberOrString};
|
||||
use crate::{
|
||||
api::{self, core::log_event, EmptyResult, JsonResult, Notify, PasswordOrOtpData, UpdateType},
|
||||
|
|
@ -1132,7 +1133,7 @@ async fn post_attachment_v2(
|
|||
let attachment_id = crypto::generate_attachment_id();
|
||||
let attachment =
|
||||
Attachment::new(attachment_id.clone(), cipher.uuid.clone(), data.file_name, file_size, Some(data.key));
|
||||
attachment.save(&conn).await.expect("Error saving attachment");
|
||||
attachment.save(&conn).await.map_res("Error saving attachment")?;
|
||||
|
||||
let url = format!("/ciphers/{}/attachment/{attachment_id}", cipher.uuid);
|
||||
let response_key = match data.admin_request {
|
||||
|
|
@ -1272,7 +1273,7 @@ async fn save_attachment(
|
|||
if size != attachment.file_size {
|
||||
// Update the attachment with the actual file size.
|
||||
attachment.file_size = size;
|
||||
attachment.save(&conn).await.expect("Error updating attachment");
|
||||
attachment.save(&conn).await.map_res("Error updating attachment")?;
|
||||
}
|
||||
} else {
|
||||
attachment.delete(&conn).await.ok();
|
||||
|
|
@ -1285,17 +1286,14 @@ async fn save_attachment(
|
|||
// SAFETY: This value is only stored in the database and is not used to access the file system.
|
||||
// As a result, the conditions specified by Rocket [0] are met and this is safe to use.
|
||||
// [0]: https://docs.rs/rocket/latest/rocket/fs/struct.FileName.html#-danger-
|
||||
let encrypted_filename = data.data.raw_name().map(|s| s.dangerous_unsafe_unsanitized_raw().to_string());
|
||||
|
||||
if encrypted_filename.is_none() {
|
||||
let Some(filename) = data.data.raw_name().map(|s| s.dangerous_unsafe_unsanitized_raw().to_string()) else {
|
||||
err!("No filename provided")
|
||||
}
|
||||
};
|
||||
if data.key.is_none() {
|
||||
err!("No attachment key provided")
|
||||
}
|
||||
let attachment =
|
||||
Attachment::new(file_id.clone(), cipher_id.clone(), encrypted_filename.unwrap(), size, data.key);
|
||||
attachment.save(&conn).await.expect("Error saving attachment");
|
||||
let attachment = Attachment::new(file_id.clone(), cipher_id.clone(), filename, size, data.key);
|
||||
attachment.save(&conn).await.map_res("Error saving attachment")?;
|
||||
}
|
||||
|
||||
save_temp_file(&PathType::Attachments, &format!("{cipher_id}/{file_id}"), data.data, true).await?;
|
||||
|
|
|
|||
|
|
@ -90,9 +90,10 @@ async fn get_emergency_access(emer_id: EmergencyAccessId, headers: Headers, conn
|
|||
check_emergency_access_enabled()?;
|
||||
|
||||
match EmergencyAccess::find_by_uuid_and_grantor_uuid(&emer_id, &headers.user.uuid, &conn).await {
|
||||
Some(emergency_access) => Ok(Json(
|
||||
emergency_access.to_json_grantee_details(&conn).await.expect("Grantee user should exist but does not!"),
|
||||
)),
|
||||
Some(emergency_access) => match emergency_access.to_json_grantee_details(&conn).await {
|
||||
Some(details) => Ok(Json(details)),
|
||||
None => err!("Grantee user does not exist for this emergency access."),
|
||||
},
|
||||
None => err!("Emergency access not valid."),
|
||||
}
|
||||
}
|
||||
|
|
@ -257,8 +258,11 @@ async fn send_invite(data: Json<EmergencyAccessInviteData>, headers: Headers, co
|
|||
new_emergency_access.save(&conn).await?;
|
||||
|
||||
if CONFIG.mail_enabled() {
|
||||
let Some(invite_email) = new_emergency_access.email.clone() else {
|
||||
err!("Grantee email does not exist on emergency access record")
|
||||
};
|
||||
mail::send_emergency_access_invite(
|
||||
&new_emergency_access.email.expect("Grantee email does not exists"),
|
||||
&invite_email,
|
||||
grantee_user.uuid,
|
||||
new_emergency_access.uuid,
|
||||
&grantor_user.name,
|
||||
|
|
@ -702,8 +706,7 @@ fn is_valid_request(
|
|||
requesting_user_id: &UserId,
|
||||
requested_access_type: EmergencyAccessType,
|
||||
) -> bool {
|
||||
emergency_access.grantee_uuid.is_some()
|
||||
&& emergency_access.grantee_uuid.as_ref().unwrap() == requesting_user_id
|
||||
emergency_access.grantee_uuid.as_ref() == Some(requesting_user_id)
|
||||
&& emergency_access.status == EmergencyAccessStatus::RecoveryApproved as i32
|
||||
&& emergency_access.atype == requested_access_type as i32
|
||||
}
|
||||
|
|
@ -731,37 +734,58 @@ pub async fn emergency_request_timeout_job(pool: DbPool) {
|
|||
let now = Utc::now().naive_utc();
|
||||
for mut emer in emergency_access_list {
|
||||
// The find_all_recoveries_initiated already checks if the recovery_initiated_at is not null (None)
|
||||
let recovery_allowed_at =
|
||||
emer.recovery_initiated_at.unwrap() + TimeDelta::try_days(i64::from(emer.wait_time_days)).unwrap();
|
||||
let Some(recovery_initiated_at) = emer.recovery_initiated_at else {
|
||||
error!("Emergency access {} has no recovery_initiated_at; skipping", emer.uuid);
|
||||
continue;
|
||||
};
|
||||
let Some(wait_time) = TimeDelta::try_days(i64::from(emer.wait_time_days)) else {
|
||||
error!("Emergency access {} has invalid wait_time_days={}; skipping", emer.uuid, emer.wait_time_days);
|
||||
continue;
|
||||
};
|
||||
let recovery_allowed_at = recovery_initiated_at + wait_time;
|
||||
if recovery_allowed_at.le(&now) {
|
||||
// Only update the access status
|
||||
// Updating the whole record could cause issues when the emergency_notification_reminder_job is also active
|
||||
emer.update_access_status_and_save(EmergencyAccessStatus::RecoveryApproved as i32, &now, &conn)
|
||||
if let Err(e) = emer
|
||||
.update_access_status_and_save(EmergencyAccessStatus::RecoveryApproved as i32, &now, &conn)
|
||||
.await
|
||||
.expect("Unable to update emergency access status");
|
||||
{
|
||||
error!("Unable to update emergency access {} status: {e:?}", emer.uuid);
|
||||
continue;
|
||||
}
|
||||
|
||||
if CONFIG.mail_enabled() {
|
||||
// get grantor user to send Accepted email
|
||||
let grantor_user =
|
||||
User::find_by_uuid(&emer.grantor_uuid, &conn).await.expect("Grantor user not found");
|
||||
let Some(grantor_user) = User::find_by_uuid(&emer.grantor_uuid, &conn).await else {
|
||||
error!("Grantor user {} not found for emergency access {}", emer.grantor_uuid, emer.uuid);
|
||||
continue;
|
||||
};
|
||||
|
||||
// get grantee user to send Accepted email
|
||||
let grantee_user =
|
||||
User::find_by_uuid(&emer.grantee_uuid.clone().expect("Grantee user invalid"), &conn)
|
||||
.await
|
||||
.expect("Grantee user not found");
|
||||
let Some(grantee_uuid) = emer.grantee_uuid.as_ref() else {
|
||||
error!("Grantee uuid missing for emergency access {}", emer.uuid);
|
||||
continue;
|
||||
};
|
||||
let Some(grantee_user) = User::find_by_uuid(grantee_uuid, &conn).await else {
|
||||
error!("Grantee user {grantee_uuid} not found for emergency access {}", emer.uuid);
|
||||
continue;
|
||||
};
|
||||
|
||||
mail::send_emergency_access_recovery_timed_out(
|
||||
if let Err(e) = mail::send_emergency_access_recovery_timed_out(
|
||||
&grantor_user.email,
|
||||
&grantee_user.name,
|
||||
emer.get_type_as_str(),
|
||||
)
|
||||
.await
|
||||
.expect("Error on sending email");
|
||||
{
|
||||
error!("Error sending recovery timed out email for {}: {e:?}", emer.uuid);
|
||||
}
|
||||
|
||||
mail::send_emergency_access_recovery_approved(&grantee_user.email, &grantor_user.name)
|
||||
.await
|
||||
.expect("Error on sending email");
|
||||
if let Err(e) =
|
||||
mail::send_emergency_access_recovery_approved(&grantee_user.email, &grantor_user.name).await
|
||||
{
|
||||
error!("Error sending recovery approved email for {}: {e:?}", emer.uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -784,43 +808,66 @@ pub async fn emergency_notification_reminder_job(pool: DbPool) {
|
|||
}
|
||||
|
||||
let now = Utc::now().naive_utc();
|
||||
let one_day = match TimeDelta::try_days(1) {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
error!("Failed to construct 1-day TimeDelta; aborting reminder job");
|
||||
return;
|
||||
}
|
||||
};
|
||||
for mut emer in emergency_access_list {
|
||||
// The find_all_recoveries_initiated already checks if the recovery_initiated_at is not null (None)
|
||||
// Calculate the day before the recovery will become active
|
||||
let final_recovery_reminder_at =
|
||||
emer.recovery_initiated_at.unwrap() + TimeDelta::try_days(i64::from(emer.wait_time_days - 1)).unwrap();
|
||||
let Some(recovery_initiated_at) = emer.recovery_initiated_at else {
|
||||
error!("Emergency access {} has no recovery_initiated_at; skipping", emer.uuid);
|
||||
continue;
|
||||
};
|
||||
let Some(wait_time) = TimeDelta::try_days(i64::from(emer.wait_time_days - 1)) else {
|
||||
error!("Emergency access {} has invalid wait_time_days={}; skipping", emer.uuid, emer.wait_time_days);
|
||||
continue;
|
||||
};
|
||||
let final_recovery_reminder_at = recovery_initiated_at + wait_time;
|
||||
// Calculate if a day has passed since the previous notification, else no notification has been sent before
|
||||
let next_recovery_reminder_at = if let Some(last_notification_at) = emer.last_notification_at {
|
||||
last_notification_at + TimeDelta::try_days(1).unwrap()
|
||||
last_notification_at + one_day
|
||||
} else {
|
||||
now
|
||||
};
|
||||
if final_recovery_reminder_at.le(&now) && next_recovery_reminder_at.le(&now) {
|
||||
// Only update the last notification date
|
||||
// Updating the whole record could cause issues when the emergency_request_timeout_job is also active
|
||||
emer.update_last_notification_date_and_save(&now, &conn)
|
||||
.await
|
||||
.expect("Unable to update emergency access notification date");
|
||||
if let Err(e) = emer.update_last_notification_date_and_save(&now, &conn).await {
|
||||
error!("Unable to update emergency access {} notification date: {e:?}", emer.uuid);
|
||||
continue;
|
||||
}
|
||||
|
||||
if CONFIG.mail_enabled() {
|
||||
// get grantor user to send Accepted email
|
||||
let grantor_user =
|
||||
User::find_by_uuid(&emer.grantor_uuid, &conn).await.expect("Grantor user not found");
|
||||
let Some(grantor_user) = User::find_by_uuid(&emer.grantor_uuid, &conn).await else {
|
||||
error!("Grantor user {} not found for emergency access {}", emer.grantor_uuid, emer.uuid);
|
||||
continue;
|
||||
};
|
||||
|
||||
// get grantee user to send Accepted email
|
||||
let grantee_user =
|
||||
User::find_by_uuid(&emer.grantee_uuid.clone().expect("Grantee user invalid"), &conn)
|
||||
.await
|
||||
.expect("Grantee user not found");
|
||||
let Some(grantee_uuid) = emer.grantee_uuid.as_ref() else {
|
||||
error!("Grantee uuid missing for emergency access {}", emer.uuid);
|
||||
continue;
|
||||
};
|
||||
let Some(grantee_user) = User::find_by_uuid(grantee_uuid, &conn).await else {
|
||||
error!("Grantee user {grantee_uuid} not found for emergency access {}", emer.uuid);
|
||||
continue;
|
||||
};
|
||||
|
||||
mail::send_emergency_access_recovery_reminder(
|
||||
if let Err(e) = mail::send_emergency_access_recovery_reminder(
|
||||
&grantor_user.email,
|
||||
&grantee_user.name,
|
||||
emer.get_type_as_str(),
|
||||
"1", // This notification is only triggered one day before the activation
|
||||
)
|
||||
.await
|
||||
.expect("Error on sending email");
|
||||
{
|
||||
error!("Error sending recovery reminder email for {}: {e:?}", emer.uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue