Spiegel von
https://github.com/dani-garcia/vaultwarden.git
synchronisiert 2025-01-07 11:45:40 +01:00
introduce attachment id
Dieser Commit ist enthalten in:
Ursprung
5dc05d6ba4
Commit
53b1c36085
6 geänderte Dateien mit 84 neuen und 29 gelöschten Zeilen
|
@ -256,7 +256,7 @@ pub struct CipherData {
|
|||
// 'Attachments' is unused, contains map of {id: filename}
|
||||
#[allow(dead_code)]
|
||||
attachments: Option<Value>,
|
||||
attachments2: Option<HashMap<CipherId, Attachments2Data>>,
|
||||
attachments2: Option<HashMap<AttachmentId, Attachments2Data>>,
|
||||
|
||||
// The revision datetime (in ISO 8601 format) of the client's local copy
|
||||
// of the cipher. This is used to prevent a client from updating a cipher
|
||||
|
@ -1040,7 +1040,7 @@ async fn share_cipher_by_uuid(
|
|||
/// their object storage service. For self-hosted instances, it basically just
|
||||
/// redirects to the same location as before the v2 API.
|
||||
#[get("/ciphers/<uuid>/attachment/<attachment_id>")]
|
||||
async fn get_attachment(uuid: CipherId, attachment_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
async fn get_attachment(uuid: CipherId, attachment_id: AttachmentId, headers: Headers, mut conn: DbConn) -> JsonResult {
|
||||
let Some(cipher) = Cipher::find_by_uuid(&uuid, &mut conn).await else {
|
||||
err!("Cipher doesn't exist")
|
||||
};
|
||||
|
@ -1049,7 +1049,7 @@ async fn get_attachment(uuid: CipherId, attachment_id: &str, headers: Headers, m
|
|||
err!("Cipher is not accessible")
|
||||
}
|
||||
|
||||
match Attachment::find_by_id(attachment_id, &mut conn).await {
|
||||
match Attachment::find_by_id(&attachment_id, &mut conn).await {
|
||||
Some(attachment) if uuid == attachment.cipher_uuid => Ok(Json(attachment.to_json(&headers.host))),
|
||||
Some(_) => err!("Attachment doesn't belong to cipher"),
|
||||
None => err!("Attachment doesn't exist"),
|
||||
|
@ -1265,7 +1265,7 @@ async fn save_attachment(
|
|||
}
|
||||
|
||||
let folder_path = tokio::fs::canonicalize(&CONFIG.attachments_folder()).await?.join(cipher_uuid.as_ref());
|
||||
let file_path = folder_path.join(&file_id);
|
||||
let file_path = folder_path.join(file_id.as_ref());
|
||||
tokio::fs::create_dir_all(&folder_path).await?;
|
||||
|
||||
if let Err(_err) = data.data.persist_to(&file_path).await {
|
||||
|
@ -1305,13 +1305,13 @@ async fn save_attachment(
|
|||
#[post("/ciphers/<uuid>/attachment/<attachment_id>", format = "multipart/form-data", data = "<data>", rank = 1)]
|
||||
async fn post_attachment_v2_data(
|
||||
uuid: CipherId,
|
||||
attachment_id: &str,
|
||||
attachment_id: AttachmentId,
|
||||
data: Form<UploadData<'_>>,
|
||||
headers: Headers,
|
||||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> EmptyResult {
|
||||
let attachment = match Attachment::find_by_id(attachment_id, &mut conn).await {
|
||||
let attachment = match Attachment::find_by_id(&attachment_id, &mut conn).await {
|
||||
Some(attachment) if uuid == attachment.cipher_uuid => Some(attachment),
|
||||
Some(_) => err!("Attachment doesn't belong to cipher"),
|
||||
None => err!("Attachment doesn't exist"),
|
||||
|
@ -1354,20 +1354,20 @@ async fn post_attachment_admin(
|
|||
#[post("/ciphers/<uuid>/attachment/<attachment_id>/share", format = "multipart/form-data", data = "<data>")]
|
||||
async fn post_attachment_share(
|
||||
uuid: CipherId,
|
||||
attachment_id: &str,
|
||||
attachment_id: AttachmentId,
|
||||
data: Form<UploadData<'_>>,
|
||||
headers: Headers,
|
||||
mut conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
) -> JsonResult {
|
||||
_delete_cipher_attachment_by_id(&uuid, attachment_id, &headers, &mut conn, &nt).await?;
|
||||
_delete_cipher_attachment_by_id(&uuid, &attachment_id, &headers, &mut conn, &nt).await?;
|
||||
post_attachment(uuid, data, headers, conn, nt).await
|
||||
}
|
||||
|
||||
#[post("/ciphers/<uuid>/attachment/<attachment_id>/delete-admin")]
|
||||
async fn delete_attachment_post_admin(
|
||||
uuid: CipherId,
|
||||
attachment_id: &str,
|
||||
attachment_id: AttachmentId,
|
||||
headers: Headers,
|
||||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
|
@ -1378,7 +1378,7 @@ async fn delete_attachment_post_admin(
|
|||
#[post("/ciphers/<uuid>/attachment/<attachment_id>/delete")]
|
||||
async fn delete_attachment_post(
|
||||
uuid: CipherId,
|
||||
attachment_id: &str,
|
||||
attachment_id: AttachmentId,
|
||||
headers: Headers,
|
||||
conn: DbConn,
|
||||
nt: Notify<'_>,
|
||||
|
@ -1389,23 +1389,23 @@ async fn delete_attachment_post(
|
|||
#[delete("/ciphers/<uuid>/attachment/<attachment_id>")]
|
||||
async fn delete_attachment(
|
||||
uuid: CipherId,
|
||||
attachment_id: &str,
|
||||
attachment_id: AttachmentId,
|
||||
headers: Headers,
|
||||
mut conn: DbConn,
|
||||
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, &nt).await
|
||||
}
|
||||
|
||||
#[delete("/ciphers/<uuid>/attachment/<attachment_id>/admin")]
|
||||
async fn delete_attachment_admin(
|
||||
uuid: CipherId,
|
||||
attachment_id: &str,
|
||||
attachment_id: AttachmentId,
|
||||
headers: Headers,
|
||||
mut conn: DbConn,
|
||||
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, &nt).await
|
||||
}
|
||||
|
||||
#[post("/ciphers/<uuid>/delete")]
|
||||
|
@ -1789,7 +1789,7 @@ async fn _restore_multiple_ciphers(
|
|||
|
||||
async fn _delete_cipher_attachment_by_id(
|
||||
uuid: &CipherId,
|
||||
attachment_id: &str,
|
||||
attachment_id: &AttachmentId,
|
||||
headers: &Headers,
|
||||
conn: &mut DbConn,
|
||||
nt: &Notify<'_>,
|
||||
|
|
|
@ -13,9 +13,9 @@ use serde_json::Value;
|
|||
use crate::{
|
||||
api::{core::now, ApiResult, EmptyResult},
|
||||
auth::decode_file_download,
|
||||
db::models::CipherId,
|
||||
db::models::{AttachmentId, CipherId},
|
||||
error::Error,
|
||||
util::{get_web_vault_version, Cached, SafeString},
|
||||
util::{get_web_vault_version, Cached},
|
||||
CONFIG,
|
||||
};
|
||||
|
||||
|
@ -197,15 +197,15 @@ async fn web_files(p: PathBuf) -> Cached<Option<NamedFile>> {
|
|||
}
|
||||
|
||||
#[get("/attachments/<uuid>/<file_id>?<token>")]
|
||||
async fn attachments(uuid: CipherId, file_id: SafeString, token: String) -> Option<NamedFile> {
|
||||
async fn attachments(uuid: CipherId, file_id: AttachmentId, token: String) -> Option<NamedFile> {
|
||||
let Ok(claims) = decode_file_download(&token) else {
|
||||
return None;
|
||||
};
|
||||
if claims.sub != uuid || claims.file_id != *file_id {
|
||||
if claims.sub != uuid || claims.file_id != file_id {
|
||||
return None;
|
||||
}
|
||||
|
||||
NamedFile::open(Path::new(&CONFIG.attachments_folder()).join(uuid.as_ref()).join(file_id)).await.ok()
|
||||
NamedFile::open(Path::new(&CONFIG.attachments_folder()).join(uuid.as_ref()).join(file_id.as_ref())).await.ok()
|
||||
}
|
||||
|
||||
// We use DbConn here to let the alive healthcheck also verify the database connection.
|
||||
|
|
|
@ -14,7 +14,7 @@ use std::{
|
|||
net::IpAddr,
|
||||
};
|
||||
|
||||
use crate::db::models::{CipherId, CollectionId, MembershipId, OrganizationId, UserId};
|
||||
use crate::db::models::{AttachmentId, CipherId, CollectionId, MembershipId, OrganizationId, UserId};
|
||||
use crate::{error::Error, CONFIG};
|
||||
|
||||
const JWT_ALGORITHM: Algorithm = Algorithm::RS256;
|
||||
|
@ -295,10 +295,10 @@ pub struct FileDownloadClaims {
|
|||
// Subject
|
||||
pub sub: CipherId,
|
||||
|
||||
pub file_id: String,
|
||||
pub file_id: AttachmentId,
|
||||
}
|
||||
|
||||
pub fn generate_file_download_claims(uuid: CipherId, file_id: String) -> FileDownloadClaims {
|
||||
pub fn generate_file_download_claims(uuid: CipherId, file_id: AttachmentId) -> FileDownloadClaims {
|
||||
let time_now = Utc::now();
|
||||
FileDownloadClaims {
|
||||
nbf: time_now.timestamp(),
|
||||
|
|
|
@ -89,9 +89,10 @@ pub fn generate_send_id() -> String {
|
|||
generate_id::<32>() // 256 bits
|
||||
}
|
||||
|
||||
pub fn generate_attachment_id() -> String {
|
||||
use crate::db::models::AttachmentId;
|
||||
pub fn generate_attachment_id() -> AttachmentId {
|
||||
// Attachment IDs are scoped to a cipher, so they can be smaller.
|
||||
generate_id::<10>() // 80 bits
|
||||
AttachmentId(generate_id::<10>()) // 80 bits
|
||||
}
|
||||
|
||||
/// Generates a numeric token for email-based verifications.
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
use std::io::ErrorKind;
|
||||
|
||||
use bigdecimal::{BigDecimal, ToPrimitive};
|
||||
use rocket::request::FromParam;
|
||||
use serde_json::Value;
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
fmt::{Display, Formatter},
|
||||
ops::Deref,
|
||||
};
|
||||
|
||||
use super::{CipherId, OrganizationId, UserId};
|
||||
use crate::CONFIG;
|
||||
|
@ -12,7 +18,7 @@ db_object! {
|
|||
#[diesel(treat_none_as_null = true)]
|
||||
#[diesel(primary_key(id))]
|
||||
pub struct Attachment {
|
||||
pub id: String,
|
||||
pub id: AttachmentId,
|
||||
pub cipher_uuid: CipherId,
|
||||
pub file_name: String, // encrypted
|
||||
pub file_size: i64,
|
||||
|
@ -23,7 +29,7 @@ db_object! {
|
|||
/// Local methods
|
||||
impl Attachment {
|
||||
pub const fn new(
|
||||
id: String,
|
||||
id: AttachmentId,
|
||||
cipher_uuid: CipherId,
|
||||
file_name: String,
|
||||
file_size: i64,
|
||||
|
@ -131,7 +137,7 @@ impl Attachment {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn find_by_id(id: &str, conn: &mut DbConn) -> Option<Self> {
|
||||
pub async fn find_by_id(id: &AttachmentId, conn: &mut DbConn) -> Option<Self> {
|
||||
db_run! { conn: {
|
||||
attachments::table
|
||||
.filter(attachments::id.eq(id.to_lowercase()))
|
||||
|
@ -227,3 +233,51 @@ impl Attachment {
|
|||
}}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DieselNewType, FromForm, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct AttachmentId(pub String);
|
||||
|
||||
impl AsRef<str> for AttachmentId {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for AttachmentId {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<str> for AttachmentId {
|
||||
fn borrow(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for AttachmentId {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for AttachmentId {
|
||||
fn from(raw: String) -> Self {
|
||||
Self(raw)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> FromParam<'r> for AttachmentId {
|
||||
type Error = ();
|
||||
|
||||
#[inline(always)]
|
||||
fn from_param(param: &'r str) -> Result<Self, Self::Error> {
|
||||
if param.chars().all(|c| matches!(c, 'a'..='z' | 'A'..='Z' |'0'..='9' | '-')) {
|
||||
Ok(Self(param.to_string()))
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ mod two_factor_duo_context;
|
|||
mod two_factor_incomplete;
|
||||
mod user;
|
||||
|
||||
pub use self::attachment::Attachment;
|
||||
pub use self::attachment::{Attachment, AttachmentId};
|
||||
pub use self::auth_request::AuthRequest;
|
||||
pub use self::cipher::{Cipher, CipherId, RepromptType};
|
||||
pub use self::collection::{Collection, CollectionCipher, CollectionId, CollectionUser};
|
||||
|
|
Laden …
In neuem Issue referenzieren