1
0
Fork 1
Spiegel von https://github.com/dani-garcia/vaultwarden.git synchronisiert 2025-01-08 11:55:42 +01:00

introduce cipher_id newtype

Dieser Commit ist enthalten in:
Stefan Melmuk 2024-12-21 15:03:30 +01:00
Ursprung 189fd77806
Commit 8b8507f8cc
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 817020C608FE9C09
15 geänderte Dateien mit 240 neuen und 150 gelöschten Zeilen

Datei anzeigen

@ -488,13 +488,13 @@ fn validate_keydata(
existing_sends: &[Send], existing_sends: &[Send],
) -> EmptyResult { ) -> EmptyResult {
// Check that we're correctly rotating all the user's ciphers // Check that we're correctly rotating all the user's ciphers
let existing_cipher_ids = existing_ciphers.iter().map(|c| c.uuid.as_str()).collect::<HashSet<_>>(); let existing_cipher_ids = existing_ciphers.iter().map(|c| &c.uuid).collect::<HashSet<&CipherId>>();
let provided_cipher_ids = data let provided_cipher_ids = data
.ciphers .ciphers
.iter() .iter()
.filter(|c| c.organization_id.is_none()) .filter(|c| c.organization_id.is_none())
.filter_map(|c| c.id.as_deref()) .filter_map(|c| c.id.as_ref())
.collect::<HashSet<_>>(); .collect::<HashSet<&CipherId>>();
if !provided_cipher_ids.is_superset(&existing_cipher_ids) { if !provided_cipher_ids.is_superset(&existing_cipher_ids) {
err!("All existing ciphers must be included in the rotation") err!("All existing ciphers must be included in the rotation")
} }

Datei anzeigen

@ -192,8 +192,8 @@ async fn get_ciphers(headers: Headers, mut conn: DbConn) -> Json<Value> {
} }
#[get("/ciphers/<uuid>")] #[get("/ciphers/<uuid>")]
async fn get_cipher(uuid: &str, headers: Headers, mut conn: DbConn) -> JsonResult { async fn get_cipher(uuid: CipherId, headers: Headers, mut conn: DbConn) -> JsonResult {
let Some(cipher) = Cipher::find_by_uuid(uuid, &mut conn).await else { let Some(cipher) = Cipher::find_by_uuid(&uuid, &mut conn).await else {
err!("Cipher doesn't exist") err!("Cipher doesn't exist")
}; };
@ -205,13 +205,13 @@ async fn get_cipher(uuid: &str, headers: Headers, mut conn: DbConn) -> JsonResul
} }
#[get("/ciphers/<uuid>/admin")] #[get("/ciphers/<uuid>/admin")]
async fn get_cipher_admin(uuid: &str, headers: Headers, conn: DbConn) -> JsonResult { async fn get_cipher_admin(uuid: CipherId, headers: Headers, conn: DbConn) -> JsonResult {
// TODO: Implement this correctly // TODO: Implement this correctly
get_cipher(uuid, headers, conn).await get_cipher(uuid, headers, conn).await
} }
#[get("/ciphers/<uuid>/details")] #[get("/ciphers/<uuid>/details")]
async fn get_cipher_details(uuid: &str, headers: Headers, conn: DbConn) -> JsonResult { async fn get_cipher_details(uuid: CipherId, headers: Headers, conn: DbConn) -> JsonResult {
get_cipher(uuid, headers, conn).await get_cipher(uuid, headers, conn).await
} }
@ -219,7 +219,7 @@ async fn get_cipher_details(uuid: &str, headers: Headers, conn: DbConn) -> JsonR
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CipherData { pub struct CipherData {
// Id is optional as it is included only in bulk share // Id is optional as it is included only in bulk share
pub id: Option<String>, pub id: Option<CipherId>,
// Folder id is not included in import // Folder id is not included in import
pub folder_id: Option<String>, pub folder_id: Option<String>,
// TODO: Some of these might appear all the time, no need for Option // TODO: Some of these might appear all the time, no need for Option
@ -256,7 +256,7 @@ pub struct CipherData {
// 'Attachments' is unused, contains map of {id: filename} // 'Attachments' is unused, contains map of {id: filename}
#[allow(dead_code)] #[allow(dead_code)]
attachments: Option<Value>, attachments: Option<Value>,
attachments2: Option<HashMap<String, Attachments2Data>>, attachments2: Option<HashMap<CipherId, Attachments2Data>>,
// The revision datetime (in ISO 8601 format) of the client's local copy // 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 // of the cipher. This is used to prevent a client from updating a cipher
@ -620,7 +620,7 @@ async fn post_ciphers_import(
/// Called when an org admin modifies an existing org cipher. /// Called when an org admin modifies an existing org cipher.
#[put("/ciphers/<uuid>/admin", data = "<data>")] #[put("/ciphers/<uuid>/admin", data = "<data>")]
async fn put_cipher_admin( async fn put_cipher_admin(
uuid: &str, uuid: CipherId,
data: Json<CipherData>, data: Json<CipherData>,
headers: Headers, headers: Headers,
conn: DbConn, conn: DbConn,
@ -631,7 +631,7 @@ async fn put_cipher_admin(
#[post("/ciphers/<uuid>/admin", data = "<data>")] #[post("/ciphers/<uuid>/admin", data = "<data>")]
async fn post_cipher_admin( async fn post_cipher_admin(
uuid: &str, uuid: CipherId,
data: Json<CipherData>, data: Json<CipherData>,
headers: Headers, headers: Headers,
conn: DbConn, conn: DbConn,
@ -641,13 +641,19 @@ async fn post_cipher_admin(
} }
#[post("/ciphers/<uuid>", data = "<data>")] #[post("/ciphers/<uuid>", data = "<data>")]
async fn post_cipher(uuid: &str, data: Json<CipherData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult { async fn post_cipher(
uuid: CipherId,
data: Json<CipherData>,
headers: Headers,
conn: DbConn,
nt: Notify<'_>,
) -> JsonResult {
put_cipher(uuid, data, headers, conn, nt).await put_cipher(uuid, data, headers, conn, nt).await
} }
#[put("/ciphers/<uuid>", data = "<data>")] #[put("/ciphers/<uuid>", data = "<data>")]
async fn put_cipher( async fn put_cipher(
uuid: &str, uuid: CipherId,
data: Json<CipherData>, data: Json<CipherData>,
headers: Headers, headers: Headers,
mut conn: DbConn, mut conn: DbConn,
@ -655,7 +661,7 @@ async fn put_cipher(
) -> JsonResult { ) -> JsonResult {
let data: CipherData = data.into_inner(); let data: CipherData = data.into_inner();
let Some(mut cipher) = Cipher::find_by_uuid(uuid, &mut conn).await else { let Some(mut cipher) = Cipher::find_by_uuid(&uuid, &mut conn).await else {
err!("Cipher doesn't exist") err!("Cipher doesn't exist")
}; };
@ -674,21 +680,26 @@ async fn put_cipher(
} }
#[post("/ciphers/<uuid>/partial", data = "<data>")] #[post("/ciphers/<uuid>/partial", data = "<data>")]
async fn post_cipher_partial(uuid: &str, data: Json<PartialCipherData>, headers: Headers, conn: DbConn) -> JsonResult { async fn post_cipher_partial(
uuid: CipherId,
data: Json<PartialCipherData>,
headers: Headers,
conn: DbConn,
) -> JsonResult {
put_cipher_partial(uuid, data, headers, conn).await put_cipher_partial(uuid, data, headers, conn).await
} }
// Only update the folder and favorite for the user, since this cipher is read-only // Only update the folder and favorite for the user, since this cipher is read-only
#[put("/ciphers/<uuid>/partial", data = "<data>")] #[put("/ciphers/<uuid>/partial", data = "<data>")]
async fn put_cipher_partial( async fn put_cipher_partial(
uuid: &str, uuid: CipherId,
data: Json<PartialCipherData>, data: Json<PartialCipherData>,
headers: Headers, headers: Headers,
mut conn: DbConn, mut conn: DbConn,
) -> JsonResult { ) -> JsonResult {
let data: PartialCipherData = data.into_inner(); let data: PartialCipherData = data.into_inner();
let Some(cipher) = Cipher::find_by_uuid(uuid, &mut conn).await else { let Some(cipher) = Cipher::find_by_uuid(&uuid, &mut conn).await else {
err!("Cipher doesn't exist") err!("Cipher doesn't exist")
}; };
@ -715,7 +726,7 @@ struct CollectionsAdminData {
#[put("/ciphers/<uuid>/collections_v2", data = "<data>")] #[put("/ciphers/<uuid>/collections_v2", data = "<data>")]
async fn put_collections2_update( async fn put_collections2_update(
uuid: &str, uuid: CipherId,
data: Json<CollectionsAdminData>, data: Json<CollectionsAdminData>,
headers: Headers, headers: Headers,
conn: DbConn, conn: DbConn,
@ -726,7 +737,7 @@ async fn put_collections2_update(
#[post("/ciphers/<uuid>/collections_v2", data = "<data>")] #[post("/ciphers/<uuid>/collections_v2", data = "<data>")]
async fn post_collections2_update( async fn post_collections2_update(
uuid: &str, uuid: CipherId,
data: Json<CollectionsAdminData>, data: Json<CollectionsAdminData>,
headers: Headers, headers: Headers,
conn: DbConn, conn: DbConn,
@ -742,7 +753,7 @@ async fn post_collections2_update(
#[put("/ciphers/<uuid>/collections", data = "<data>")] #[put("/ciphers/<uuid>/collections", data = "<data>")]
async fn put_collections_update( async fn put_collections_update(
uuid: &str, uuid: CipherId,
data: Json<CollectionsAdminData>, data: Json<CollectionsAdminData>,
headers: Headers, headers: Headers,
conn: DbConn, conn: DbConn,
@ -753,7 +764,7 @@ async fn put_collections_update(
#[post("/ciphers/<uuid>/collections", data = "<data>")] #[post("/ciphers/<uuid>/collections", data = "<data>")]
async fn post_collections_update( async fn post_collections_update(
uuid: &str, uuid: CipherId,
data: Json<CollectionsAdminData>, data: Json<CollectionsAdminData>,
headers: Headers, headers: Headers,
mut conn: DbConn, mut conn: DbConn,
@ -761,7 +772,7 @@ async fn post_collections_update(
) -> JsonResult { ) -> JsonResult {
let data: CollectionsAdminData = data.into_inner(); let data: CollectionsAdminData = data.into_inner();
let Some(cipher) = Cipher::find_by_uuid(uuid, &mut conn).await else { let Some(cipher) = Cipher::find_by_uuid(&uuid, &mut conn).await else {
err!("Cipher doesn't exist") err!("Cipher doesn't exist")
}; };
@ -771,7 +782,7 @@ async fn post_collections_update(
let posted_collections = HashSet::<CollectionId>::from_iter(data.collection_ids); let posted_collections = HashSet::<CollectionId>::from_iter(data.collection_ids);
let current_collections = let current_collections =
HashSet::<CollectionId>::from_iter(cipher.get_collections(headers.user.uuid.to_string(), &mut conn).await); HashSet::<CollectionId>::from_iter(cipher.get_collections(headers.user.uuid.clone(), &mut conn).await);
for collection in posted_collections.symmetric_difference(&current_collections) { for collection in posted_collections.symmetric_difference(&current_collections) {
match Collection::find_by_uuid_and_org(collection, cipher.organization_uuid.as_ref().unwrap(), &mut conn).await match Collection::find_by_uuid_and_org(collection, cipher.organization_uuid.as_ref().unwrap(), &mut conn).await
@ -819,7 +830,7 @@ async fn post_collections_update(
#[put("/ciphers/<uuid>/collections-admin", data = "<data>")] #[put("/ciphers/<uuid>/collections-admin", data = "<data>")]
async fn put_collections_admin( async fn put_collections_admin(
uuid: &str, uuid: CipherId,
data: Json<CollectionsAdminData>, data: Json<CollectionsAdminData>,
headers: Headers, headers: Headers,
conn: DbConn, conn: DbConn,
@ -830,7 +841,7 @@ async fn put_collections_admin(
#[post("/ciphers/<uuid>/collections-admin", data = "<data>")] #[post("/ciphers/<uuid>/collections-admin", data = "<data>")]
async fn post_collections_admin( async fn post_collections_admin(
uuid: &str, uuid: CipherId,
data: Json<CollectionsAdminData>, data: Json<CollectionsAdminData>,
headers: Headers, headers: Headers,
mut conn: DbConn, mut conn: DbConn,
@ -838,7 +849,7 @@ async fn post_collections_admin(
) -> EmptyResult { ) -> EmptyResult {
let data: CollectionsAdminData = data.into_inner(); let data: CollectionsAdminData = data.into_inner();
let Some(cipher) = Cipher::find_by_uuid(uuid, &mut conn).await else { let Some(cipher) = Cipher::find_by_uuid(&uuid, &mut conn).await else {
err!("Cipher doesn't exist") err!("Cipher doesn't exist")
}; };
@ -847,9 +858,8 @@ async fn post_collections_admin(
} }
let posted_collections = HashSet::<CollectionId>::from_iter(data.collection_ids); let posted_collections = HashSet::<CollectionId>::from_iter(data.collection_ids);
let current_collections = HashSet::<CollectionId>::from_iter( let current_collections =
cipher.get_admin_collections(headers.user.uuid.to_string(), &mut conn).await, HashSet::<CollectionId>::from_iter(cipher.get_admin_collections(headers.user.uuid.clone(), &mut conn).await);
);
for collection in posted_collections.symmetric_difference(&current_collections) { for collection in posted_collections.symmetric_difference(&current_collections) {
match Collection::find_by_uuid_and_org(collection, cipher.organization_uuid.as_ref().unwrap(), &mut conn).await match Collection::find_by_uuid_and_org(collection, cipher.organization_uuid.as_ref().unwrap(), &mut conn).await
@ -906,7 +916,7 @@ struct ShareCipherData {
#[post("/ciphers/<uuid>/share", data = "<data>")] #[post("/ciphers/<uuid>/share", data = "<data>")]
async fn post_cipher_share( async fn post_cipher_share(
uuid: &str, uuid: CipherId,
data: Json<ShareCipherData>, data: Json<ShareCipherData>,
headers: Headers, headers: Headers,
mut conn: DbConn, mut conn: DbConn,
@ -914,12 +924,12 @@ async fn post_cipher_share(
) -> JsonResult { ) -> JsonResult {
let data: ShareCipherData = data.into_inner(); let data: ShareCipherData = data.into_inner();
share_cipher_by_uuid(uuid, data, &headers, &mut conn, &nt).await share_cipher_by_uuid(&uuid, data, &headers, &mut conn, &nt).await
} }
#[put("/ciphers/<uuid>/share", data = "<data>")] #[put("/ciphers/<uuid>/share", data = "<data>")]
async fn put_cipher_share( async fn put_cipher_share(
uuid: &str, uuid: CipherId,
data: Json<ShareCipherData>, data: Json<ShareCipherData>,
headers: Headers, headers: Headers,
mut conn: DbConn, mut conn: DbConn,
@ -927,7 +937,7 @@ async fn put_cipher_share(
) -> JsonResult { ) -> JsonResult {
let data: ShareCipherData = data.into_inner(); let data: ShareCipherData = data.into_inner();
share_cipher_by_uuid(uuid, data, &headers, &mut conn, &nt).await share_cipher_by_uuid(&uuid, data, &headers, &mut conn, &nt).await
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -976,7 +986,7 @@ async fn put_cipher_share_selected(
} }
async fn share_cipher_by_uuid( async fn share_cipher_by_uuid(
uuid: &str, uuid: &CipherId,
data: ShareCipherData, data: ShareCipherData,
headers: &Headers, headers: &Headers,
conn: &mut DbConn, conn: &mut DbConn,
@ -1030,8 +1040,8 @@ async fn share_cipher_by_uuid(
/// their object storage service. For self-hosted instances, it basically just /// their object storage service. For self-hosted instances, it basically just
/// redirects to the same location as before the v2 API. /// redirects to the same location as before the v2 API.
#[get("/ciphers/<uuid>/attachment/<attachment_id>")] #[get("/ciphers/<uuid>/attachment/<attachment_id>")]
async fn get_attachment(uuid: &str, attachment_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult { async fn get_attachment(uuid: CipherId, attachment_id: &str, headers: Headers, mut conn: DbConn) -> JsonResult {
let Some(cipher) = Cipher::find_by_uuid(uuid, &mut conn).await else { let Some(cipher) = Cipher::find_by_uuid(&uuid, &mut conn).await else {
err!("Cipher doesn't exist") err!("Cipher doesn't exist")
}; };
@ -1066,12 +1076,12 @@ enum FileUploadType {
/// For self-hosted instances, it's another API on the local instance. /// For self-hosted instances, it's another API on the local instance.
#[post("/ciphers/<uuid>/attachment/v2", data = "<data>")] #[post("/ciphers/<uuid>/attachment/v2", data = "<data>")]
async fn post_attachment_v2( async fn post_attachment_v2(
uuid: &str, uuid: CipherId,
data: Json<AttachmentRequestData>, data: Json<AttachmentRequestData>,
headers: Headers, headers: Headers,
mut conn: DbConn, mut conn: DbConn,
) -> JsonResult { ) -> JsonResult {
let Some(cipher) = Cipher::find_by_uuid(uuid, &mut conn).await else { let Some(cipher) = Cipher::find_by_uuid(&uuid, &mut conn).await else {
err!("Cipher doesn't exist") err!("Cipher doesn't exist")
}; };
@ -1121,7 +1131,7 @@ struct UploadData<'f> {
/// database record, which is passed in as `attachment`. /// database record, which is passed in as `attachment`.
async fn save_attachment( async fn save_attachment(
mut attachment: Option<Attachment>, mut attachment: Option<Attachment>,
cipher_uuid: &str, cipher_uuid: CipherId,
data: Form<UploadData<'_>>, data: Form<UploadData<'_>>,
headers: &Headers, headers: &Headers,
mut conn: DbConn, mut conn: DbConn,
@ -1136,7 +1146,7 @@ async fn save_attachment(
err!("Attachment size can't be negative") err!("Attachment size can't be negative")
} }
let Some(cipher) = Cipher::find_by_uuid(cipher_uuid, &mut conn).await else { let Some(cipher) = Cipher::find_by_uuid(&cipher_uuid, &mut conn).await else {
err!("Cipher doesn't exist") err!("Cipher doesn't exist")
}; };
@ -1250,11 +1260,11 @@ async fn save_attachment(
err!("No attachment key provided") err!("No attachment key provided")
} }
let attachment = let attachment =
Attachment::new(file_id.clone(), String::from(cipher_uuid), encrypted_filename.unwrap(), size, data.key); Attachment::new(file_id.clone(), cipher_uuid.clone(), encrypted_filename.unwrap(), size, data.key);
attachment.save(&mut conn).await.expect("Error saving attachment"); attachment.save(&mut conn).await.expect("Error saving attachment");
} }
let folder_path = tokio::fs::canonicalize(&CONFIG.attachments_folder()).await?.join(cipher_uuid); 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);
tokio::fs::create_dir_all(&folder_path).await?; tokio::fs::create_dir_all(&folder_path).await?;
@ -1294,7 +1304,7 @@ async fn save_attachment(
/// with this one. /// with this one.
#[post("/ciphers/<uuid>/attachment/<attachment_id>", format = "multipart/form-data", data = "<data>", rank = 1)] #[post("/ciphers/<uuid>/attachment/<attachment_id>", format = "multipart/form-data", data = "<data>", rank = 1)]
async fn post_attachment_v2_data( async fn post_attachment_v2_data(
uuid: &str, uuid: CipherId,
attachment_id: &str, attachment_id: &str,
data: Form<UploadData<'_>>, data: Form<UploadData<'_>>,
headers: Headers, headers: Headers,
@ -1315,7 +1325,7 @@ async fn post_attachment_v2_data(
/// Legacy API for creating an attachment associated with a cipher. /// Legacy API for creating an attachment associated with a cipher.
#[post("/ciphers/<uuid>/attachment", format = "multipart/form-data", data = "<data>")] #[post("/ciphers/<uuid>/attachment", format = "multipart/form-data", data = "<data>")]
async fn post_attachment( async fn post_attachment(
uuid: &str, uuid: CipherId,
data: Form<UploadData<'_>>, data: Form<UploadData<'_>>,
headers: Headers, headers: Headers,
conn: DbConn, conn: DbConn,
@ -1332,7 +1342,7 @@ async fn post_attachment(
#[post("/ciphers/<uuid>/attachment-admin", format = "multipart/form-data", data = "<data>")] #[post("/ciphers/<uuid>/attachment-admin", format = "multipart/form-data", data = "<data>")]
async fn post_attachment_admin( async fn post_attachment_admin(
uuid: &str, uuid: CipherId,
data: Form<UploadData<'_>>, data: Form<UploadData<'_>>,
headers: Headers, headers: Headers,
conn: DbConn, conn: DbConn,
@ -1343,20 +1353,20 @@ async fn post_attachment_admin(
#[post("/ciphers/<uuid>/attachment/<attachment_id>/share", format = "multipart/form-data", data = "<data>")] #[post("/ciphers/<uuid>/attachment/<attachment_id>/share", format = "multipart/form-data", data = "<data>")]
async fn post_attachment_share( async fn post_attachment_share(
uuid: &str, uuid: CipherId,
attachment_id: &str, attachment_id: &str,
data: Form<UploadData<'_>>, data: Form<UploadData<'_>>,
headers: Headers, headers: Headers,
mut conn: DbConn, mut conn: DbConn,
nt: Notify<'_>, nt: Notify<'_>,
) -> JsonResult { ) -> 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_attachment(uuid, data, headers, conn, nt).await
} }
#[post("/ciphers/<uuid>/attachment/<attachment_id>/delete-admin")] #[post("/ciphers/<uuid>/attachment/<attachment_id>/delete-admin")]
async fn delete_attachment_post_admin( async fn delete_attachment_post_admin(
uuid: &str, uuid: CipherId,
attachment_id: &str, attachment_id: &str,
headers: Headers, headers: Headers,
conn: DbConn, conn: DbConn,
@ -1367,7 +1377,7 @@ async fn delete_attachment_post_admin(
#[post("/ciphers/<uuid>/attachment/<attachment_id>/delete")] #[post("/ciphers/<uuid>/attachment/<attachment_id>/delete")]
async fn delete_attachment_post( async fn delete_attachment_post(
uuid: &str, uuid: CipherId,
attachment_id: &str, attachment_id: &str,
headers: Headers, headers: Headers,
conn: DbConn, conn: DbConn,
@ -1378,58 +1388,58 @@ async fn delete_attachment_post(
#[delete("/ciphers/<uuid>/attachment/<attachment_id>")] #[delete("/ciphers/<uuid>/attachment/<attachment_id>")]
async fn delete_attachment( async fn delete_attachment(
uuid: &str, uuid: CipherId,
attachment_id: &str, attachment_id: &str,
headers: Headers, headers: Headers,
mut conn: DbConn, mut conn: DbConn,
nt: Notify<'_>, nt: Notify<'_>,
) -> EmptyResult { ) -> 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")] #[delete("/ciphers/<uuid>/attachment/<attachment_id>/admin")]
async fn delete_attachment_admin( async fn delete_attachment_admin(
uuid: &str, uuid: CipherId,
attachment_id: &str, attachment_id: &str,
headers: Headers, headers: Headers,
mut conn: DbConn, mut conn: DbConn,
nt: Notify<'_>, nt: Notify<'_>,
) -> EmptyResult { ) -> 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")] #[post("/ciphers/<uuid>/delete")]
async fn delete_cipher_post(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { async fn delete_cipher_post(uuid: CipherId, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
_delete_cipher_by_uuid(uuid, &headers, &mut conn, false, &nt).await _delete_cipher_by_uuid(&uuid, &headers, &mut conn, false, &nt).await
// permanent delete // permanent delete
} }
#[post("/ciphers/<uuid>/delete-admin")] #[post("/ciphers/<uuid>/delete-admin")]
async fn delete_cipher_post_admin(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { async fn delete_cipher_post_admin(uuid: CipherId, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
_delete_cipher_by_uuid(uuid, &headers, &mut conn, false, &nt).await _delete_cipher_by_uuid(&uuid, &headers, &mut conn, false, &nt).await
// permanent delete // permanent delete
} }
#[put("/ciphers/<uuid>/delete")] #[put("/ciphers/<uuid>/delete")]
async fn delete_cipher_put(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { async fn delete_cipher_put(uuid: CipherId, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
_delete_cipher_by_uuid(uuid, &headers, &mut conn, true, &nt).await _delete_cipher_by_uuid(&uuid, &headers, &mut conn, true, &nt).await
// soft delete // soft delete
} }
#[put("/ciphers/<uuid>/delete-admin")] #[put("/ciphers/<uuid>/delete-admin")]
async fn delete_cipher_put_admin(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { async fn delete_cipher_put_admin(uuid: CipherId, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
_delete_cipher_by_uuid(uuid, &headers, &mut conn, true, &nt).await _delete_cipher_by_uuid(&uuid, &headers, &mut conn, true, &nt).await
} }
#[delete("/ciphers/<uuid>")] #[delete("/ciphers/<uuid>")]
async fn delete_cipher(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { async fn delete_cipher(uuid: CipherId, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
_delete_cipher_by_uuid(uuid, &headers, &mut conn, false, &nt).await _delete_cipher_by_uuid(&uuid, &headers, &mut conn, false, &nt).await
// permanent delete // permanent delete
} }
#[delete("/ciphers/<uuid>/admin")] #[delete("/ciphers/<uuid>/admin")]
async fn delete_cipher_admin(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { async fn delete_cipher_admin(uuid: CipherId, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
_delete_cipher_by_uuid(uuid, &headers, &mut conn, false, &nt).await _delete_cipher_by_uuid(&uuid, &headers, &mut conn, false, &nt).await
// permanent delete // permanent delete
} }
@ -1494,13 +1504,13 @@ async fn delete_cipher_selected_put_admin(
} }
#[put("/ciphers/<uuid>/restore")] #[put("/ciphers/<uuid>/restore")]
async fn restore_cipher_put(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult { async fn restore_cipher_put(uuid: CipherId, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
_restore_cipher_by_uuid(uuid, &headers, &mut conn, &nt).await _restore_cipher_by_uuid(&uuid, &headers, &mut conn, &nt).await
} }
#[put("/ciphers/<uuid>/restore-admin")] #[put("/ciphers/<uuid>/restore-admin")]
async fn restore_cipher_put_admin(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult { async fn restore_cipher_put_admin(uuid: CipherId, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult {
_restore_cipher_by_uuid(uuid, &headers, &mut conn, &nt).await _restore_cipher_by_uuid(&uuid, &headers, &mut conn, &nt).await
} }
#[put("/ciphers/restore", data = "<data>")] #[put("/ciphers/restore", data = "<data>")]
@ -1517,7 +1527,7 @@ async fn restore_cipher_selected(
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct MoveCipherData { struct MoveCipherData {
folder_id: Option<String>, folder_id: Option<String>,
ids: Vec<String>, ids: Vec<CipherId>,
} }
#[post("/ciphers/move", data = "<data>")] #[post("/ciphers/move", data = "<data>")]
@ -1640,7 +1650,7 @@ async fn delete_all(
} }
async fn _delete_cipher_by_uuid( async fn _delete_cipher_by_uuid(
uuid: &str, uuid: &CipherId,
headers: &Headers, headers: &Headers,
conn: &mut DbConn, conn: &mut DbConn,
soft_delete: bool, soft_delete: bool,
@ -1695,7 +1705,7 @@ async fn _delete_cipher_by_uuid(
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct CipherIdsData { struct CipherIdsData {
ids: Vec<String>, ids: Vec<CipherId>,
} }
async fn _delete_multiple_ciphers( async fn _delete_multiple_ciphers(
@ -1716,7 +1726,7 @@ async fn _delete_multiple_ciphers(
Ok(()) Ok(())
} }
async fn _restore_cipher_by_uuid(uuid: &str, headers: &Headers, conn: &mut DbConn, nt: &Notify<'_>) -> JsonResult { async fn _restore_cipher_by_uuid(uuid: &CipherId, headers: &Headers, conn: &mut DbConn, nt: &Notify<'_>) -> JsonResult {
let Some(mut cipher) = Cipher::find_by_uuid(uuid, conn).await else { let Some(mut cipher) = Cipher::find_by_uuid(uuid, conn).await else {
err!("Cipher doesn't exist") err!("Cipher doesn't exist")
}; };
@ -1778,7 +1788,7 @@ async fn _restore_multiple_ciphers(
} }
async fn _delete_cipher_attachment_by_id( async fn _delete_cipher_attachment_by_id(
uuid: &str, uuid: &CipherId,
attachment_id: &str, attachment_id: &str,
headers: &Headers, headers: &Headers,
conn: &mut DbConn, conn: &mut DbConn,
@ -1788,7 +1798,7 @@ async fn _delete_cipher_attachment_by_id(
err!("Attachment doesn't exist") err!("Attachment doesn't exist")
}; };
if attachment.cipher_uuid != uuid { if &attachment.cipher_uuid != uuid {
err!("Attachment from other cipher") err!("Attachment from other cipher")
} }
@ -1832,10 +1842,10 @@ async fn _delete_cipher_attachment_by_id(
/// It will prevent the so called N+1 SQL issue by running just a few queries which will hold all the data needed. /// It will prevent the so called N+1 SQL issue by running just a few queries which will hold all the data needed.
/// This will not improve the speed of a single cipher.to_json() call that much, so better not to use it for those calls. /// This will not improve the speed of a single cipher.to_json() call that much, so better not to use it for those calls.
pub struct CipherSyncData { pub struct CipherSyncData {
pub cipher_attachments: HashMap<String, Vec<Attachment>>, pub cipher_attachments: HashMap<CipherId, Vec<Attachment>>,
pub cipher_folders: HashMap<String, String>, pub cipher_folders: HashMap<CipherId, String>,
pub cipher_favorites: HashSet<String>, pub cipher_favorites: HashSet<CipherId>,
pub cipher_collections: HashMap<String, Vec<CollectionId>>, pub cipher_collections: HashMap<CipherId, Vec<CollectionId>>,
pub members: HashMap<OrganizationId, Membership>, pub members: HashMap<OrganizationId, Membership>,
pub user_collections: HashMap<CollectionId, CollectionUser>, pub user_collections: HashMap<CollectionId, CollectionUser>,
pub user_collections_groups: HashMap<CollectionId, CollectionGroup>, pub user_collections_groups: HashMap<CollectionId, CollectionGroup>,
@ -1850,8 +1860,8 @@ pub enum CipherSyncType {
impl CipherSyncData { impl CipherSyncData {
pub async fn new(user_uuid: &UserId, sync_type: CipherSyncType, conn: &mut DbConn) -> Self { pub async fn new(user_uuid: &UserId, sync_type: CipherSyncType, conn: &mut DbConn) -> Self {
let cipher_folders: HashMap<String, String>; let cipher_folders: HashMap<CipherId, String>;
let cipher_favorites: HashSet<String>; let cipher_favorites: HashSet<CipherId>;
match sync_type { match sync_type {
// User Sync supports Folders and Favorites // User Sync supports Folders and Favorites
CipherSyncType::User => { CipherSyncType::User => {
@ -1872,14 +1882,14 @@ impl CipherSyncData {
// Generate a list of Cipher UUID's containing a Vec with one or more Attachment records // Generate a list of Cipher UUID's containing a Vec with one or more Attachment records
let orgs = Membership::get_orgs_by_user(user_uuid, conn).await; let orgs = Membership::get_orgs_by_user(user_uuid, conn).await;
let attachments = Attachment::find_all_by_user_and_orgs(user_uuid, &orgs, conn).await; let attachments = Attachment::find_all_by_user_and_orgs(user_uuid, &orgs, conn).await;
let mut cipher_attachments: HashMap<String, Vec<Attachment>> = HashMap::with_capacity(attachments.len()); let mut cipher_attachments: HashMap<CipherId, Vec<Attachment>> = HashMap::with_capacity(attachments.len());
for attachment in attachments { for attachment in attachments {
cipher_attachments.entry(attachment.cipher_uuid.clone()).or_default().push(attachment); cipher_attachments.entry(attachment.cipher_uuid.clone()).or_default().push(attachment);
} }
// Generate a HashMap with the Cipher UUID as key and one or more Collection UUID's // Generate a HashMap with the Cipher UUID as key and one or more Collection UUID's
let user_cipher_collections = Cipher::get_collections_with_cipher_by_user(user_uuid.to_string(), conn).await; let user_cipher_collections = Cipher::get_collections_with_cipher_by_user(user_uuid.clone(), conn).await;
let mut cipher_collections: HashMap<String, Vec<CollectionId>> = let mut cipher_collections: HashMap<CipherId, Vec<CollectionId>> =
HashMap::with_capacity(user_cipher_collections.len()); HashMap::with_capacity(user_cipher_collections.len());
for (cipher, collection) in user_cipher_collections { for (cipher, collection) in user_cipher_collections {
cipher_collections.entry(cipher).or_default().push(collection); cipher_collections.entry(cipher).or_default().push(collection);

Datei anzeigen

@ -8,7 +8,7 @@ use crate::{
api::{EmptyResult, JsonResult}, api::{EmptyResult, JsonResult},
auth::{AdminHeaders, Headers}, auth::{AdminHeaders, Headers},
db::{ db::{
models::{Cipher, Event, Membership, MembershipId, OrganizationId, UserId}, models::{Cipher, CipherId, Event, Membership, MembershipId, OrganizationId, UserId},
DbConn, DbPool, DbConn, DbPool,
}, },
util::parse_date, util::parse_date,
@ -59,14 +59,14 @@ async fn get_org_events(org_id: &str, data: EventRange, _headers: AdminHeaders,
} }
#[get("/ciphers/<cipher_id>/events?<data..>")] #[get("/ciphers/<cipher_id>/events?<data..>")]
async fn get_cipher_events(cipher_id: &str, data: EventRange, headers: Headers, mut conn: DbConn) -> JsonResult { async fn get_cipher_events(cipher_id: CipherId, data: EventRange, headers: Headers, mut conn: DbConn) -> JsonResult {
// Return an empty vec when we org events are disabled. // Return an empty vec when we org events are disabled.
// This prevents client errors // This prevents client errors
let events_json: Vec<Value> = if !CONFIG.org_events_enabled() { let events_json: Vec<Value> = if !CONFIG.org_events_enabled() {
Vec::with_capacity(0) Vec::with_capacity(0)
} else { } else {
let mut events_json = Vec::with_capacity(0); let mut events_json = Vec::with_capacity(0);
if Membership::user_has_ge_admin_access_to_cipher(&headers.user.uuid, cipher_id, &mut conn).await { if Membership::user_has_ge_admin_access_to_cipher(&headers.user.uuid, &cipher_id, &mut conn).await {
let start_date = parse_date(&data.start); let start_date = parse_date(&data.start);
let end_date = if let Some(before_date) = &data.continuation_token { let end_date = if let Some(before_date) = &data.continuation_token {
parse_date(before_date) parse_date(before_date)
@ -74,7 +74,7 @@ async fn get_cipher_events(cipher_id: &str, data: EventRange, headers: Headers,
parse_date(&data.end) parse_date(&data.end)
}; };
events_json = Event::find_by_cipher_uuid(cipher_id, &start_date, &end_date, &mut conn) events_json = Event::find_by_cipher_uuid(&cipher_id, &start_date, &end_date, &mut conn)
.await .await
.iter() .iter()
.map(|e| e.to_json()) .map(|e| e.to_json())
@ -152,7 +152,7 @@ struct EventCollection {
date: String, date: String,
// Optional // Optional
cipher_id: Option<String>, cipher_id: Option<CipherId>,
organization_id: Option<OrganizationId>, organization_id: Option<OrganizationId>,
} }
@ -290,19 +290,19 @@ async fn _log_event(
// 1000..=1099 Are user events, they need to be logged via log_user_event() // 1000..=1099 Are user events, they need to be logged via log_user_event()
// Cipher Events // Cipher Events
1100..=1199 => { 1100..=1199 => {
event.cipher_uuid = Some(String::from(source_uuid)); event.cipher_uuid = Some(source_uuid.to_string().into());
} }
// Collection Events // Collection Events
1300..=1399 => { 1300..=1399 => {
event.collection_uuid = Some(String::from(source_uuid)); event.collection_uuid = Some(source_uuid.to_string().into());
} }
// Group Events // Group Events
1400..=1499 => { 1400..=1499 => {
event.group_uuid = Some(String::from(source_uuid)); event.group_uuid = Some(source_uuid.to_string().into());
} }
// Org User Events // Org User Events
1500..=1599 => { 1500..=1599 => {
event.org_user_uuid = Some(String::from(source_uuid)); event.org_user_uuid = Some(source_uuid.to_string().into());
} }
// 1600..=1699 Are organizational events, and they do not need the source_uuid // 1600..=1699 Are organizational events, and they do not need the source_uuid
// Policy Events // Policy Events

Datei anzeigen

@ -1645,7 +1645,7 @@ async fn post_org_import(
let headers: Headers = headers.into(); let headers: Headers = headers.into();
let mut ciphers: Vec<String> = Vec::with_capacity(data.ciphers.len()); let mut ciphers: Vec<CipherId> = Vec::with_capacity(data.ciphers.len());
for mut cipher_data in data.ciphers { for mut cipher_data in data.ciphers {
// Always clear folder_id's via an organization import // Always clear folder_id's via an organization import
cipher_data.folder_id = None; cipher_data.folder_id = None;
@ -1670,7 +1670,7 @@ async fn post_org_import(
#[allow(dead_code)] #[allow(dead_code)]
struct BulkCollectionsData { struct BulkCollectionsData {
organization_id: OrganizationId, organization_id: OrganizationId,
cipher_ids: Vec<String>, cipher_ids: Vec<CipherId>,
collection_ids: HashSet<CollectionId>, collection_ids: HashSet<CollectionId>,
remove_collections: bool, remove_collections: bool,
} }
@ -2659,7 +2659,7 @@ async fn get_group_users(
err!("Group support is disabled"); err!("Group support is disabled");
} }
if Group::find_by_uuid_and_org(&&group_id, &org_id, &mut conn).await.is_none() { if Group::find_by_uuid_and_org(&group_id, &org_id, &mut conn).await.is_none() {
err!("Group could not be found!", "Group uuid is invalid or does not belong to the organization") err!("Group could not be found!", "Group uuid is invalid or does not belong to the organization")
}; };

Datei anzeigen

@ -437,7 +437,7 @@ impl WebSocketUsers {
let data = create_update( let data = create_update(
vec![ vec![
("Id".into(), cipher.uuid.clone().into()), ("Id".into(), cipher.uuid.to_string().into()),
("UserId".into(), user_uuid), ("UserId".into(), user_uuid),
("OrganizationId".into(), org_uuid), ("OrganizationId".into(), org_uuid),
("CollectionIds".into(), collection_uuids), ("CollectionIds".into(), collection_uuids),

Datei anzeigen

@ -13,6 +13,7 @@ use serde_json::Value;
use crate::{ use crate::{
api::{core::now, ApiResult, EmptyResult}, api::{core::now, ApiResult, EmptyResult},
auth::decode_file_download, auth::decode_file_download,
db::models::CipherId,
error::Error, error::Error,
util::{get_web_vault_version, Cached, SafeString}, util::{get_web_vault_version, Cached, SafeString},
CONFIG, CONFIG,
@ -196,15 +197,15 @@ async fn web_files(p: PathBuf) -> Cached<Option<NamedFile>> {
} }
#[get("/attachments/<uuid>/<file_id>?<token>")] #[get("/attachments/<uuid>/<file_id>?<token>")]
async fn attachments(uuid: SafeString, file_id: SafeString, token: String) -> Option<NamedFile> { async fn attachments(uuid: CipherId, file_id: SafeString, token: String) -> Option<NamedFile> {
let Ok(claims) = decode_file_download(&token) else { let Ok(claims) = decode_file_download(&token) else {
return None; return None;
}; };
if claims.sub != *uuid || claims.file_id != *file_id { if claims.sub != uuid || claims.file_id != *file_id {
return None; return None;
} }
NamedFile::open(Path::new(&CONFIG.attachments_folder()).join(uuid).join(file_id)).await.ok() NamedFile::open(Path::new(&CONFIG.attachments_folder()).join(uuid.as_ref()).join(file_id)).await.ok()
} }
// We use DbConn here to let the alive healthcheck also verify the database connection. // We use DbConn here to let the alive healthcheck also verify the database connection.

Datei anzeigen

@ -14,7 +14,7 @@ use std::{
net::IpAddr, net::IpAddr,
}; };
use crate::db::models::{CollectionId, MembershipId, OrganizationId, UserId}; use crate::db::models::{CipherId, CollectionId, MembershipId, OrganizationId, UserId};
use crate::{error::Error, CONFIG}; use crate::{error::Error, CONFIG};
const JWT_ALGORITHM: Algorithm = Algorithm::RS256; const JWT_ALGORITHM: Algorithm = Algorithm::RS256;
@ -293,12 +293,12 @@ pub struct FileDownloadClaims {
// Issuer // Issuer
pub iss: String, pub iss: String,
// Subject // Subject
pub sub: String, pub sub: CipherId,
pub file_id: String, pub file_id: String,
} }
pub fn generate_file_download_claims(uuid: String, file_id: String) -> FileDownloadClaims { pub fn generate_file_download_claims(uuid: CipherId, file_id: String) -> FileDownloadClaims {
let time_now = Utc::now(); let time_now = Utc::now();
FileDownloadClaims { FileDownloadClaims {
nbf: time_now.timestamp(), nbf: time_now.timestamp(),

Datei anzeigen

@ -3,7 +3,7 @@ use std::io::ErrorKind;
use bigdecimal::{BigDecimal, ToPrimitive}; use bigdecimal::{BigDecimal, ToPrimitive};
use serde_json::Value; use serde_json::Value;
use super::{OrganizationId, UserId}; use super::{CipherId, OrganizationId, UserId};
use crate::CONFIG; use crate::CONFIG;
db_object! { db_object! {
@ -13,7 +13,7 @@ db_object! {
#[diesel(primary_key(id))] #[diesel(primary_key(id))]
pub struct Attachment { pub struct Attachment {
pub id: String, pub id: String,
pub cipher_uuid: String, pub cipher_uuid: CipherId,
pub file_name: String, // encrypted pub file_name: String, // encrypted
pub file_size: i64, pub file_size: i64,
pub akey: Option<String>, pub akey: Option<String>,
@ -22,7 +22,13 @@ db_object! {
/// Local methods /// Local methods
impl Attachment { impl Attachment {
pub const fn new(id: String, cipher_uuid: String, file_name: String, file_size: i64, akey: Option<String>) -> Self { pub const fn new(
id: String,
cipher_uuid: CipherId,
file_name: String,
file_size: i64,
akey: Option<String>,
) -> Self {
Self { Self {
id, id,
cipher_uuid, cipher_uuid,
@ -118,7 +124,7 @@ impl Attachment {
}} }}
} }
pub async fn delete_all_by_cipher(cipher_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_cipher(cipher_uuid: &CipherId, conn: &mut DbConn) -> EmptyResult {
for attachment in Attachment::find_by_cipher(cipher_uuid, conn).await { for attachment in Attachment::find_by_cipher(cipher_uuid, conn).await {
attachment.delete(conn).await?; attachment.delete(conn).await?;
} }
@ -135,7 +141,7 @@ impl Attachment {
}} }}
} }
pub async fn find_by_cipher(cipher_uuid: &str, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_cipher(cipher_uuid: &CipherId, conn: &mut DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
attachments::table attachments::table
.filter(attachments::cipher_uuid.eq(cipher_uuid)) .filter(attachments::cipher_uuid.eq(cipher_uuid))

Datei anzeigen

@ -1,7 +1,13 @@
use crate::util::LowerCase; use crate::util::LowerCase;
use crate::CONFIG; use crate::CONFIG;
use chrono::{NaiveDateTime, TimeDelta, Utc}; use chrono::{NaiveDateTime, TimeDelta, Utc};
use rocket::request::FromParam;
use serde_json::Value; use serde_json::Value;
use std::{
borrow::Borrow,
fmt::{Display, Formatter},
ops::Deref,
};
use super::{ use super::{
Attachment, CollectionCipher, CollectionId, Favorite, FolderCipher, Group, Membership, MembershipStatus, Attachment, CollectionCipher, CollectionId, Favorite, FolderCipher, Group, Membership, MembershipStatus,
@ -18,7 +24,7 @@ db_object! {
#[diesel(treat_none_as_null = true)] #[diesel(treat_none_as_null = true)]
#[diesel(primary_key(uuid))] #[diesel(primary_key(uuid))]
pub struct Cipher { pub struct Cipher {
pub uuid: String, pub uuid: CipherId,
pub created_at: NaiveDateTime, pub created_at: NaiveDateTime,
pub updated_at: NaiveDateTime, pub updated_at: NaiveDateTime,
@ -58,7 +64,7 @@ impl Cipher {
let now = Utc::now().naive_utc(); let now = Utc::now().naive_utc();
Self { Self {
uuid: crate::util::get_uuid(), uuid: CipherId(crate::util::get_uuid()),
created_at: now, created_at: now,
updated_at: now, updated_at: now,
@ -279,7 +285,7 @@ impl Cipher {
Cow::from(Vec::with_capacity(0)) Cow::from(Vec::with_capacity(0))
} }
} else { } else {
Cow::from(self.get_admin_collections(user_uuid.to_string(), conn).await) Cow::from(self.get_admin_collections(user_uuid.clone(), conn).await)
}; };
// There are three types of cipher response models in upstream // There are three types of cipher response models in upstream
@ -683,7 +689,7 @@ impl Cipher {
}} }}
} }
pub async fn find_by_uuid(uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid(uuid: &CipherId, conn: &mut DbConn) -> Option<Self> {
db_run! {conn: { db_run! {conn: {
ciphers::table ciphers::table
.filter(ciphers::uuid.eq(uuid)) .filter(ciphers::uuid.eq(uuid))
@ -693,7 +699,7 @@ impl Cipher {
}} }}
} }
pub async fn find_by_uuid_and_org(cipher_uuid: &str, org_uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_uuid_and_org(cipher_uuid: &CipherId, org_uuid: &str, conn: &mut DbConn) -> Option<Self> {
db_run! {conn: { db_run! {conn: {
ciphers::table ciphers::table
.filter(ciphers::uuid.eq(cipher_uuid)) .filter(ciphers::uuid.eq(cipher_uuid))
@ -862,7 +868,7 @@ impl Cipher {
}} }}
} }
pub async fn get_collections(&self, user_id: String, conn: &mut DbConn) -> Vec<CollectionId> { pub async fn get_collections(&self, user_id: UserId, conn: &mut DbConn) -> Vec<CollectionId> {
if CONFIG.org_groups_enabled() { if CONFIG.org_groups_enabled() {
db_run! {conn: { db_run! {conn: {
ciphers_collections::table ciphers_collections::table
@ -921,7 +927,7 @@ impl Cipher {
} }
} }
pub async fn get_admin_collections(&self, user_id: String, conn: &mut DbConn) -> Vec<CollectionId> { pub async fn get_admin_collections(&self, user_id: UserId, conn: &mut DbConn) -> Vec<CollectionId> {
if CONFIG.org_groups_enabled() { if CONFIG.org_groups_enabled() {
db_run! {conn: { db_run! {conn: {
ciphers_collections::table ciphers_collections::table
@ -985,9 +991,9 @@ impl Cipher {
/// Return a Vec with (cipher_uuid, collection_uuid) /// Return a Vec with (cipher_uuid, collection_uuid)
/// This is used during a full sync so we only need one query for all collections accessible. /// This is used during a full sync so we only need one query for all collections accessible.
pub async fn get_collections_with_cipher_by_user( pub async fn get_collections_with_cipher_by_user(
user_id: String, user_id: UserId,
conn: &mut DbConn, conn: &mut DbConn,
) -> Vec<(String, CollectionId)> { ) -> Vec<(CipherId, CollectionId)> {
db_run! {conn: { db_run! {conn: {
ciphers_collections::table ciphers_collections::table
.inner_join(collections::table.on( .inner_join(collections::table.on(
@ -1021,7 +1027,55 @@ impl Cipher {
.or_filter(collections_groups::collections_uuid.is_not_null()) //Access via group .or_filter(collections_groups::collections_uuid.is_not_null()) //Access via group
.select(ciphers_collections::all_columns) .select(ciphers_collections::all_columns)
.distinct() .distinct()
.load::<(String, CollectionId)>(conn).unwrap_or_default() .load::<(CipherId, CollectionId)>(conn).unwrap_or_default()
}} }}
} }
} }
#[derive(DieselNewType, FromForm, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct CipherId(String);
impl AsRef<str> for CipherId {
fn as_ref(&self) -> &str {
&self.0
}
}
impl Deref for CipherId {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Borrow<str> for CipherId {
fn borrow(&self) -> &str {
&self.0
}
}
impl Display for CipherId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<String> for CipherId {
fn from(raw: String) -> Self {
Self(raw)
}
}
impl<'r> FromParam<'r> for CipherId {
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(())
}
}
}

Datei anzeigen

@ -6,7 +6,9 @@ use std::{
ops::Deref, ops::Deref,
}; };
use super::{CollectionGroup, GroupUser, Membership, MembershipStatus, MembershipType, OrganizationId, User, UserId}; use super::{
CipherId, CollectionGroup, GroupUser, Membership, MembershipStatus, MembershipType, OrganizationId, User, UserId,
};
use crate::CONFIG; use crate::CONFIG;
db_object! { db_object! {
@ -34,7 +36,7 @@ db_object! {
#[diesel(table_name = ciphers_collections)] #[diesel(table_name = ciphers_collections)]
#[diesel(primary_key(cipher_uuid, collection_uuid))] #[diesel(primary_key(cipher_uuid, collection_uuid))]
pub struct CollectionCipher { pub struct CollectionCipher {
pub cipher_uuid: String, pub cipher_uuid: CipherId,
pub collection_uuid: CollectionId, pub collection_uuid: CollectionId,
} }
} }
@ -710,7 +712,7 @@ impl CollectionUser {
/// Database methods /// Database methods
impl CollectionCipher { impl CollectionCipher {
pub async fn save(cipher_uuid: &str, collection_uuid: &CollectionId, conn: &mut DbConn) -> EmptyResult { pub async fn save(cipher_uuid: &CipherId, collection_uuid: &CollectionId, conn: &mut DbConn) -> EmptyResult {
Self::update_users_revision(collection_uuid, conn).await; Self::update_users_revision(collection_uuid, conn).await;
db_run! { conn: db_run! { conn:
@ -740,7 +742,7 @@ impl CollectionCipher {
} }
} }
pub async fn delete(cipher_uuid: &str, collection_uuid: &CollectionId, conn: &mut DbConn) -> EmptyResult { pub async fn delete(cipher_uuid: &CipherId, collection_uuid: &CollectionId, conn: &mut DbConn) -> EmptyResult {
Self::update_users_revision(collection_uuid, conn).await; Self::update_users_revision(collection_uuid, conn).await;
db_run! { conn: { db_run! { conn: {
@ -754,7 +756,7 @@ impl CollectionCipher {
}} }}
} }
pub async fn delete_all_by_cipher(cipher_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_cipher(cipher_uuid: &CipherId, conn: &mut DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::delete(ciphers_collections::table.filter(ciphers_collections::cipher_uuid.eq(cipher_uuid))) diesel::delete(ciphers_collections::table.filter(ciphers_collections::cipher_uuid.eq(cipher_uuid)))
.execute(conn) .execute(conn)

Datei anzeigen

@ -1,7 +1,7 @@
use crate::db::DbConn; use crate::db::DbConn;
use serde_json::Value; use serde_json::Value;
use super::{OrganizationId, UserId}; use super::{CipherId, CollectionId, GroupId, MembershipId, OrganizationId, UserId};
use crate::{api::EmptyResult, error::MapResult, CONFIG}; use crate::{api::EmptyResult, error::MapResult, CONFIG};
use chrono::{NaiveDateTime, TimeDelta, Utc}; use chrono::{NaiveDateTime, TimeDelta, Utc};
@ -20,10 +20,10 @@ db_object! {
pub event_type: i32, // EventType pub event_type: i32, // EventType
pub user_uuid: Option<UserId>, pub user_uuid: Option<UserId>,
pub org_uuid: Option<OrganizationId>, pub org_uuid: Option<OrganizationId>,
pub cipher_uuid: Option<String>, pub cipher_uuid: Option<CipherId>,
pub collection_uuid: Option<String>, pub collection_uuid: Option<CollectionId>,
pub group_uuid: Option<String>, pub group_uuid: Option<GroupId>,
pub org_user_uuid: Option<String>, pub org_user_uuid: Option<MembershipId>,
pub act_user_uuid: Option<String>, pub act_user_uuid: Option<String>,
// Upstream enum: https://github.com/bitwarden/server/blob/8a22c0479e987e756ce7412c48a732f9002f0a2d/src/Core/Enums/DeviceType.cs // Upstream enum: https://github.com/bitwarden/server/blob/8a22c0479e987e756ce7412c48a732f9002f0a2d/src/Core/Enums/DeviceType.cs
pub device_type: Option<i32>, pub device_type: Option<i32>,
@ -298,7 +298,7 @@ impl Event {
} }
pub async fn find_by_cipher_uuid( pub async fn find_by_cipher_uuid(
cipher_uuid: &str, cipher_uuid: &CipherId,
start: &NaiveDateTime, start: &NaiveDateTime,
end: &NaiveDateTime, end: &NaiveDateTime,
conn: &mut DbConn, conn: &mut DbConn,

Datei anzeigen

@ -1,4 +1,4 @@
use super::{User, UserId}; use super::{CipherId, User, UserId};
db_object! { db_object! {
#[derive(Identifiable, Queryable, Insertable)] #[derive(Identifiable, Queryable, Insertable)]
@ -6,7 +6,7 @@ db_object! {
#[diesel(primary_key(user_uuid, cipher_uuid))] #[diesel(primary_key(user_uuid, cipher_uuid))]
pub struct Favorite { pub struct Favorite {
pub user_uuid: UserId, pub user_uuid: UserId,
pub cipher_uuid: String, pub cipher_uuid: CipherId,
} }
} }
@ -17,7 +17,7 @@ use crate::error::MapResult;
impl Favorite { impl Favorite {
// Returns whether the specified cipher is a favorite of the specified user. // Returns whether the specified cipher is a favorite of the specified user.
pub async fn is_favorite(cipher_uuid: &str, user_uuid: &UserId, conn: &mut DbConn) -> bool { pub async fn is_favorite(cipher_uuid: &CipherId, user_uuid: &UserId, conn: &mut DbConn) -> bool {
db_run! { conn: { db_run! { conn: {
let query = favorites::table let query = favorites::table
.filter(favorites::cipher_uuid.eq(cipher_uuid)) .filter(favorites::cipher_uuid.eq(cipher_uuid))
@ -29,7 +29,12 @@ impl Favorite {
} }
// Sets whether the specified cipher is a favorite of the specified user. // Sets whether the specified cipher is a favorite of the specified user.
pub async fn set_favorite(favorite: bool, cipher_uuid: &str, user_uuid: &UserId, conn: &mut DbConn) -> EmptyResult { pub async fn set_favorite(
favorite: bool,
cipher_uuid: &CipherId,
user_uuid: &UserId,
conn: &mut DbConn,
) -> EmptyResult {
let (old, new) = (Self::is_favorite(cipher_uuid, user_uuid, conn).await, favorite); let (old, new) = (Self::is_favorite(cipher_uuid, user_uuid, conn).await, favorite);
match (old, new) { match (old, new) {
(false, true) => { (false, true) => {
@ -62,7 +67,7 @@ impl Favorite {
} }
// Delete all favorite entries associated with the specified cipher. // Delete all favorite entries associated with the specified cipher.
pub async fn delete_all_by_cipher(cipher_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_cipher(cipher_uuid: &CipherId, conn: &mut DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::delete(favorites::table.filter(favorites::cipher_uuid.eq(cipher_uuid))) diesel::delete(favorites::table.filter(favorites::cipher_uuid.eq(cipher_uuid)))
.execute(conn) .execute(conn)
@ -81,12 +86,12 @@ impl Favorite {
/// Return a vec with (cipher_uuid) this will only contain favorite flagged ciphers /// Return a vec with (cipher_uuid) this will only contain favorite flagged ciphers
/// This is used during a full sync so we only need one query for all favorite cipher matches. /// This is used during a full sync so we only need one query for all favorite cipher matches.
pub async fn get_all_cipher_uuid_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<String> { pub async fn get_all_cipher_uuid_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<CipherId> {
db_run! { conn: { db_run! { conn: {
favorites::table favorites::table
.filter(favorites::user_uuid.eq(user_uuid)) .filter(favorites::user_uuid.eq(user_uuid))
.select(favorites::cipher_uuid) .select(favorites::cipher_uuid)
.load::<String>(conn) .load::<CipherId>(conn)
.unwrap_or_default() .unwrap_or_default()
}} }}
} }

Datei anzeigen

@ -1,7 +1,7 @@
use chrono::{NaiveDateTime, Utc}; use chrono::{NaiveDateTime, Utc};
use serde_json::Value; use serde_json::Value;
use super::{User, UserId}; use super::{CipherId, User, UserId};
db_object! { db_object! {
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
@ -19,7 +19,7 @@ db_object! {
#[diesel(table_name = folders_ciphers)] #[diesel(table_name = folders_ciphers)]
#[diesel(primary_key(cipher_uuid, folder_uuid))] #[diesel(primary_key(cipher_uuid, folder_uuid))]
pub struct FolderCipher { pub struct FolderCipher {
pub cipher_uuid: String, pub cipher_uuid: CipherId,
pub folder_uuid: String, pub folder_uuid: String,
} }
} }
@ -52,10 +52,10 @@ impl Folder {
} }
impl FolderCipher { impl FolderCipher {
pub fn new(folder_uuid: &str, cipher_uuid: &str) -> Self { pub fn new(folder_uuid: &str, cipher_uuid: &CipherId) -> Self {
Self { Self {
folder_uuid: folder_uuid.to_string(), folder_uuid: folder_uuid.to_string(),
cipher_uuid: cipher_uuid.to_string(), cipher_uuid: cipher_uuid.clone(),
} }
} }
} }
@ -177,7 +177,7 @@ impl FolderCipher {
}} }}
} }
pub async fn delete_all_by_cipher(cipher_uuid: &str, conn: &mut DbConn) -> EmptyResult { pub async fn delete_all_by_cipher(cipher_uuid: &CipherId, conn: &mut DbConn) -> EmptyResult {
db_run! { conn: { db_run! { conn: {
diesel::delete(folders_ciphers::table.filter(folders_ciphers::cipher_uuid.eq(cipher_uuid))) diesel::delete(folders_ciphers::table.filter(folders_ciphers::cipher_uuid.eq(cipher_uuid)))
.execute(conn) .execute(conn)
@ -193,7 +193,11 @@ impl FolderCipher {
}} }}
} }
pub async fn find_by_folder_and_cipher(folder_uuid: &str, cipher_uuid: &str, conn: &mut DbConn) -> Option<Self> { pub async fn find_by_folder_and_cipher(
folder_uuid: &str,
cipher_uuid: &CipherId,
conn: &mut DbConn,
) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
folders_ciphers::table folders_ciphers::table
.filter(folders_ciphers::folder_uuid.eq(folder_uuid)) .filter(folders_ciphers::folder_uuid.eq(folder_uuid))
@ -216,13 +220,13 @@ impl FolderCipher {
/// Return a vec with (cipher_uuid, folder_uuid) /// Return a vec with (cipher_uuid, folder_uuid)
/// This is used during a full sync so we only need one query for all folder matches. /// This is used during a full sync so we only need one query for all folder matches.
pub async fn find_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<(String, String)> { pub async fn find_by_user(user_uuid: &UserId, conn: &mut DbConn) -> Vec<(CipherId, String)> {
db_run! { conn: { db_run! { conn: {
folders_ciphers::table folders_ciphers::table
.inner_join(folders::table) .inner_join(folders::table)
.filter(folders::user_uuid.eq(user_uuid)) .filter(folders::user_uuid.eq(user_uuid))
.select(folders_ciphers::all_columns) .select(folders_ciphers::all_columns)
.load::<(String, String)>(conn) .load::<(CipherId, String)>(conn)
.unwrap_or_default() .unwrap_or_default()
}} }}
} }

Datei anzeigen

@ -18,7 +18,7 @@ mod user;
pub use self::attachment::Attachment; pub use self::attachment::Attachment;
pub use self::auth_request::AuthRequest; pub use self::auth_request::AuthRequest;
pub use self::cipher::{Cipher, RepromptType}; pub use self::cipher::{Cipher, CipherId, RepromptType};
pub use self::collection::{Collection, CollectionCipher, CollectionId, CollectionUser}; pub use self::collection::{Collection, CollectionCipher, CollectionId, CollectionUser};
pub use self::device::{Device, DeviceType}; pub use self::device::{Device, DeviceType};
pub use self::emergency_access::{EmergencyAccess, EmergencyAccessStatus, EmergencyAccessType}; pub use self::emergency_access::{EmergencyAccess, EmergencyAccessStatus, EmergencyAccessType};

Datei anzeigen

@ -11,8 +11,8 @@ use std::{
}; };
use super::{ use super::{
Collection, CollectionGroup, CollectionId, CollectionUser, Group, GroupId, GroupUser, OrgPolicy, OrgPolicyType, CipherId, Collection, CollectionGroup, CollectionId, CollectionUser, Group, GroupId, GroupUser, OrgPolicy,
TwoFactor, User, UserId, OrgPolicyType, TwoFactor, User, UserId,
}; };
use crate::CONFIG; use crate::CONFIG;
@ -897,7 +897,11 @@ impl Membership {
}} }}
} }
pub async fn find_by_cipher_and_org(cipher_uuid: &str, org_uuid: &OrganizationId, conn: &mut DbConn) -> Vec<Self> { pub async fn find_by_cipher_and_org(
cipher_uuid: &CipherId,
org_uuid: &OrganizationId,
conn: &mut DbConn,
) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::org_uuid.eq(org_uuid)) .filter(users_organizations::org_uuid.eq(org_uuid))
@ -921,7 +925,7 @@ impl Membership {
} }
pub async fn find_by_cipher_and_org_with_group( pub async fn find_by_cipher_and_org_with_group(
cipher_uuid: &str, cipher_uuid: &CipherId,
org_uuid: &OrganizationId, org_uuid: &OrganizationId,
conn: &mut DbConn, conn: &mut DbConn,
) -> Vec<Self> { ) -> Vec<Self> {
@ -950,7 +954,11 @@ impl Membership {
}} }}
} }
pub async fn user_has_ge_admin_access_to_cipher(user_uuid: &UserId, cipher_uuid: &str, conn: &mut DbConn) -> bool { pub async fn user_has_ge_admin_access_to_cipher(
user_uuid: &UserId,
cipher_uuid: &CipherId,
conn: &mut DbConn,
) -> bool {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.inner_join(ciphers::table.on(ciphers::uuid.eq(cipher_uuid).and(ciphers::organization_uuid.eq(users_organizations::org_uuid.nullable())))) .inner_join(ciphers::table.on(ciphers::uuid.eq(cipher_uuid).and(ciphers::organization_uuid.eq(users_organizations::org_uuid.nullable()))))