1#[cfg(any(test, feature = "admin"))]
10pub mod admin;
11mod pg;
13
14#[cfg(any(test, feature = "sqlite"))]
16mod sqlite;
17
18#[cfg(any(test, feature = "sqlite"))]
20mod sqlite_functions;
21
22pub mod types;
24
25#[cfg(any(test, feature = "admin"))]
26use crate::crypto::EncryptionOption;
27use crate::crypto::FileEncryption;
28use crate::db::pg::Postgres;
29#[cfg(any(test, feature = "sqlite"))]
30use crate::db::sqlite::Sqlite;
31use crate::db::types::{FileMetadata, FileType};
32use malwaredb_api::{
33 GetUserInfoResponse, Labels, SearchRequest, SearchResponse, Sources, digest::HashType,
34};
35use malwaredb_types::KnownType;
36
37use std::collections::HashMap;
38use std::path::PathBuf;
39
40use anyhow::{Result, bail, ensure};
41use argon2::password_hash::{SaltString, rand_core::OsRng};
42use argon2::{Argon2, PasswordHasher};
43#[cfg(any(test, feature = "admin"))]
44use chrono::Local;
45#[cfg(feature = "vt")]
46use malwaredb_virustotal::filereport::ScanResultAttributes;
47
48pub const PARTIAL_SEARCH_LIMIT: u32 = 100;
50
51#[derive(Copy, Clone)]
53pub enum Migration {
54 Check,
56
57 #[cfg(any(test, feature = "admin"))]
59 Migrate,
60}
61
62#[derive(Debug)]
64pub enum DatabaseType {
65 Postgres(Postgres),
67
68 #[cfg(any(test, feature = "sqlite"))]
70 SQLite(Sqlite),
71}
72
73#[derive(Debug)]
75pub struct DatabaseInformation {
76 pub version: String,
78
79 pub size: String,
81
82 pub num_files: u64,
84
85 pub num_users: u32,
87
88 pub num_groups: u32,
90
91 pub num_sources: u32,
93}
94
95pub struct FileAddedResult {
97 pub file_id: u64,
99
100 pub is_new: bool,
103}
104
105#[derive(Debug)]
107pub struct MDBConfig {
108 pub name: String,
110
111 pub compression: bool,
113
114 pub send_samples_to_vt: bool,
116
117 pub keep_unknown_files: bool,
119
120 pub(crate) default_key: Option<u32>,
122
123 #[cfg_attr(docsrs, doc(cfg(feature = "anonymous")))]
125 #[cfg(feature = "anonymous")]
126 pub anonymous_uid: Option<u32>,
127}
128
129impl MDBConfig {
130 #[inline]
132 #[must_use]
133 #[cfg(feature = "admin")]
134 #[cfg_attr(docsrs, doc(cfg(feature = "admin")))]
135 pub fn get_default_key(&self) -> Option<u32> {
136 self.default_key
137 }
138}
139
140#[cfg_attr(docsrs, doc(cfg(feature = "vt")))]
142#[cfg(feature = "vt")]
143#[derive(Debug, Clone, Copy)]
144pub struct VtStats {
145 pub clean_records: u32,
147
148 pub hits_records: u32,
150
151 pub files_without_records: u32,
153}
154
155impl DatabaseType {
156 pub async fn from_string(arg: &str, server_ca: Option<PathBuf>) -> Result<Self> {
165 let db = Self::init_from_string(arg, server_ca).await?;
166 db.migrate_check(Migration::Check).await?;
167 Ok(db)
168 }
169
170 #[cfg(feature = "admin")]
179 pub async fn migrate(arg: &str, server_ca: Option<PathBuf>) -> Result<Self> {
180 let db = Self::init_from_string(arg, server_ca).await?;
181 db.migrate_check(Migration::Migrate).await?;
182 Ok(db)
183 }
184
185 async fn init_from_string(arg: &str, server_ca: Option<PathBuf>) -> Result<Self> {
186 #[cfg(any(test, feature = "sqlite"))]
187 if arg.starts_with("file:") {
188 let new_conn_str = arg.trim_start_matches("file:");
189 let db = DatabaseType::SQLite(Sqlite::new(new_conn_str)?);
190 return Ok(db);
191 }
192
193 if arg.starts_with("postgres") {
194 let new_conn_str = arg.trim_start_matches("postgres");
195 let db = DatabaseType::Postgres(Postgres::new(new_conn_str, server_ca).await?);
196
197 return Ok(db);
198 }
199
200 bail!("unknown database type `{arg}`")
201 }
202
203 #[cfg_attr(docsrs, doc(cfg(feature = "vt")))]
209 #[cfg(feature = "vt")]
210 pub async fn enable_vt_upload(&self) -> Result<()> {
211 match self {
212 DatabaseType::Postgres(pg) => pg.enable_vt_upload().await,
213 #[cfg(any(test, feature = "sqlite"))]
214 DatabaseType::SQLite(sl) => sl.enable_vt_upload(),
215 }
216 }
217
218 #[cfg_attr(docsrs, doc(cfg(feature = "vt")))]
224 #[cfg(feature = "vt")]
225 pub async fn disable_vt_upload(&self) -> Result<()> {
226 match self {
227 DatabaseType::Postgres(pg) => pg.disable_vt_upload().await,
228 #[cfg(any(test, feature = "sqlite"))]
229 DatabaseType::SQLite(sl) => sl.disable_vt_upload(),
230 }
231 }
232
233 #[cfg_attr(docsrs, doc(cfg(feature = "vt")))]
239 #[cfg(feature = "vt")]
240 pub async fn files_without_vt_records(&self, limit: u32) -> Result<Vec<String>> {
241 match self {
242 DatabaseType::Postgres(pg) => pg.files_without_vt_records(limit).await,
243 #[cfg(any(test, feature = "sqlite"))]
244 DatabaseType::SQLite(sl) => sl.files_without_vt_records(limit),
245 }
246 }
247
248 #[cfg_attr(docsrs, doc(cfg(feature = "vt")))]
254 #[cfg(feature = "vt")]
255 pub async fn store_vt_record(&self, results: &ScanResultAttributes) -> Result<()> {
256 match self {
257 DatabaseType::Postgres(pg) => pg.store_vt_record(results).await,
258 #[cfg(any(test, feature = "sqlite"))]
259 DatabaseType::SQLite(sl) => sl.store_vt_record(results),
260 }
261 }
262
263 #[cfg_attr(docsrs, doc(cfg(feature = "vt")))]
269 #[cfg(feature = "vt")]
270 pub async fn get_vt_stats(&self) -> Result<VtStats> {
271 match self {
272 DatabaseType::Postgres(pg) => pg.get_vt_stats().await,
273 #[cfg(any(test, feature = "sqlite"))]
274 DatabaseType::SQLite(sl) => sl.get_vt_stats(),
275 }
276 }
277
278 #[cfg_attr(docsrs, doc(cfg(feature = "yara")))]
284 #[cfg(feature = "yara")]
285 pub async fn add_yara_search(
286 &self,
287 uid: u32,
288 yara_string: &str,
289 yara_bytes: &[u8],
290 ) -> Result<uuid::Uuid> {
291 match self {
292 DatabaseType::Postgres(pg) => pg.add_yara_search(uid, yara_string, yara_bytes).await,
293 #[cfg(any(test, feature = "sqlite"))]
294 DatabaseType::SQLite(sl) => sl.add_yara_search(uid, yara_string, yara_bytes),
295 }
296 }
297
298 #[cfg_attr(docsrs, doc(cfg(feature = "yara")))]
304 #[cfg(feature = "yara")]
305 pub async fn get_unfinished_yara_tasks(&self) -> Result<Vec<crate::yara::YaraTask>> {
306 match self {
307 DatabaseType::Postgres(pg) => pg.get_unfinished_yara_tasks().await,
308 #[cfg(any(test, feature = "sqlite"))]
309 DatabaseType::SQLite(sl) => sl.get_unfinished_yara_tasks(),
310 }
311 }
312
313 #[cfg_attr(docsrs, doc(cfg(feature = "yara")))]
319 #[cfg(feature = "yara")]
320 pub async fn add_yara_match(
321 &self,
322 id: uuid::Uuid,
323 rule_name: &str,
324 file_sha256: &str,
325 ) -> Result<()> {
326 match self {
327 DatabaseType::Postgres(pg) => pg.add_yara_match(id, rule_name, file_sha256).await,
328 #[cfg(any(test, feature = "sqlite"))]
329 DatabaseType::SQLite(sl) => sl.add_yara_match(id, rule_name, file_sha256),
330 }
331 }
332
333 #[cfg_attr(docsrs, doc(cfg(feature = "yara")))]
339 #[cfg(feature = "yara")]
340 pub async fn mark_yara_task_as_finished(&self, id: uuid::Uuid) -> Result<()> {
341 match self {
342 DatabaseType::Postgres(pg) => pg.mark_yara_task_as_finished(id).await,
343 #[cfg(any(test, feature = "sqlite"))]
344 DatabaseType::SQLite(sl) => sl.mark_yara_task_as_finished(id),
345 }
346 }
347
348 #[cfg_attr(docsrs, doc(cfg(feature = "yara")))]
354 #[cfg(feature = "yara")]
355 pub async fn yara_add_next_file_id(&self, id: uuid::Uuid, file_id: u64) -> Result<()> {
356 match self {
357 DatabaseType::Postgres(pg) => pg.yara_add_next_file_id(id, file_id).await,
358 #[cfg(any(test, feature = "sqlite"))]
359 DatabaseType::SQLite(sl) => sl.yara_add_next_file_id(id, file_id),
360 }
361 }
362
363 #[cfg_attr(docsrs, doc(cfg(feature = "yara")))]
369 #[cfg(feature = "yara")]
370 pub async fn get_yara_results(
371 &self,
372 id: uuid::Uuid,
373 user_id: u32,
374 ) -> Result<malwaredb_api::YaraSearchResponse> {
375 match self {
376 DatabaseType::Postgres(pg) => pg.get_yara_results(id, user_id).await,
377 #[cfg(any(test, feature = "sqlite"))]
378 DatabaseType::SQLite(sl) => sl.get_yara_results(id, user_id),
379 }
380 }
381
382 pub async fn get_config(&self) -> Result<MDBConfig> {
388 match self {
389 DatabaseType::Postgres(pg) => pg.get_config().await,
390 #[cfg(any(test, feature = "sqlite"))]
391 DatabaseType::SQLite(sl) => sl.get_config(),
392 }
393 }
394
395 pub async fn authenticate(&self, uname: &str, password: &str) -> Result<String> {
402 match self {
403 DatabaseType::Postgres(pg) => pg.authenticate(uname, password).await,
404 #[cfg(any(test, feature = "sqlite"))]
405 DatabaseType::SQLite(sl) => sl.authenticate(uname, password),
406 }
407 }
408
409 pub async fn get_uid(&self, apikey: &str) -> Result<u32> {
416 ensure!(!apikey.is_empty(), "API key was empty");
417 match self {
418 DatabaseType::Postgres(pg) => pg.get_uid(apikey).await,
419 #[cfg(any(test, feature = "sqlite"))]
420 DatabaseType::SQLite(sl) => sl.get_uid(apikey),
421 }
422 }
423
424 pub async fn db_info(&self) -> Result<DatabaseInformation> {
430 match self {
431 DatabaseType::Postgres(pg) => pg.db_info().await,
432 #[cfg(any(test, feature = "sqlite"))]
433 DatabaseType::SQLite(sl) => sl.db_info(),
434 }
435 }
436
437 pub async fn get_user_info(&self, uid: u32) -> Result<GetUserInfoResponse> {
444 match self {
445 DatabaseType::Postgres(pg) => pg.get_user_info(uid).await,
446 #[cfg(any(test, feature = "sqlite"))]
447 DatabaseType::SQLite(sl) => sl.get_user_info(uid),
448 }
449 }
450
451 pub async fn get_user_sources(&self, uid: u32) -> Result<Sources> {
458 match self {
459 DatabaseType::Postgres(pg) => pg.get_user_sources(uid).await,
460 #[cfg(any(test, feature = "sqlite"))]
461 DatabaseType::SQLite(sl) => sl.get_user_sources(uid),
462 }
463 }
464
465 pub async fn reset_own_api_key(&self, uid: u32) -> Result<()> {
472 match self {
473 DatabaseType::Postgres(pg) => pg.reset_own_api_key(uid).await,
474 #[cfg(any(test, feature = "sqlite"))]
475 DatabaseType::SQLite(sl) => sl.reset_own_api_key(uid),
476 }
477 }
478
479 pub async fn get_known_data_types(&self) -> Result<Vec<FileType>> {
485 match self {
486 DatabaseType::Postgres(pg) => pg.get_known_data_types().await,
487 #[cfg(any(test, feature = "sqlite"))]
488 DatabaseType::SQLite(sl) => sl.get_known_data_types(),
489 }
490 }
491
492 pub async fn get_labels(&self) -> Result<Labels> {
498 match self {
499 DatabaseType::Postgres(pg) => pg.get_labels().await,
500 #[cfg(any(test, feature = "sqlite"))]
501 DatabaseType::SQLite(sl) => sl.get_labels(),
502 }
503 }
504
505 pub async fn get_type_id_for_bytes(&self, data: &[u8]) -> Result<u32> {
511 match self {
512 DatabaseType::Postgres(pg) => pg.get_type_id_for_bytes(data).await,
513 #[cfg(any(test, feature = "sqlite"))]
514 DatabaseType::SQLite(sl) => sl.get_type_id_for_bytes(data),
515 }
516 }
517
518 pub async fn allowed_user_source(&self, uid: u32, sid: u32) -> Result<bool> {
525 match self {
526 DatabaseType::Postgres(pg) => pg.allowed_user_source(uid, sid).await,
527 #[cfg(any(test, feature = "sqlite"))]
528 DatabaseType::SQLite(sl) => sl.allowed_user_source(uid, sid),
529 }
530 }
531
532 pub async fn user_is_admin(&self, uid: u32) -> Result<bool> {
540 match self {
541 DatabaseType::Postgres(pg) => pg.user_is_admin(uid).await,
542 #[cfg(any(test, feature = "sqlite"))]
543 DatabaseType::SQLite(sl) => sl.user_is_admin(uid),
544 }
545 }
546
547 pub async fn add_file(
554 &self,
555 meta: &FileMetadata,
556 known_type: KnownType<'_>,
557 uid: u32,
558 sid: u32,
559 ftype: u32,
560 parent: Option<u64>,
561 ) -> Result<FileAddedResult> {
562 match self {
563 DatabaseType::Postgres(pg) => {
564 pg.add_file(meta, known_type, uid, sid, ftype, parent).await
565 }
566 #[cfg(any(test, feature = "sqlite"))]
567 DatabaseType::SQLite(sl) => sl.add_file(meta, &known_type, uid, sid, ftype, parent),
568 }
569 }
570
571 pub async fn partial_search(&self, uid: u32, search: SearchRequest) -> Result<SearchResponse> {
577 match self {
578 DatabaseType::Postgres(pg) => pg.partial_search(uid, search).await,
579 #[cfg(any(test, feature = "sqlite"))]
580 DatabaseType::SQLite(sl) => sl.partial_search(uid, search),
581 }
582 }
583
584 pub async fn cleanup(&self) -> Result<u64> {
590 match self {
591 DatabaseType::Postgres(pg) => pg.cleanup().await,
592 #[cfg(any(test, feature = "sqlite"))]
593 DatabaseType::SQLite(sl) => sl.cleanup(),
594 }
595 }
596
597 pub async fn retrieve_sample(&self, uid: u32, hash: &HashType) -> Result<String> {
605 match self {
606 DatabaseType::Postgres(pg) => pg.retrieve_sample(uid, hash).await,
607 #[cfg(any(test, feature = "sqlite"))]
608 DatabaseType::SQLite(sl) => sl.retrieve_sample(uid, hash),
609 }
610 }
611
612 pub async fn get_sample_report(
619 &self,
620 uid: u32,
621 hash: &HashType,
622 ) -> Result<malwaredb_api::Report> {
623 match self {
624 DatabaseType::Postgres(pg) => pg.get_sample_report(uid, hash).await,
625 #[cfg(any(test, feature = "sqlite"))]
626 DatabaseType::SQLite(sl) => sl.get_sample_report(uid, hash),
627 }
628 }
629
630 pub async fn find_similar_samples(
636 &self,
637 uid: u32,
638 sim: &[(malwaredb_api::SimilarityHashType, String)],
639 ) -> Result<Vec<malwaredb_api::SimilarSample>> {
640 match self {
641 DatabaseType::Postgres(pg) => pg.find_similar_samples(uid, sim).await,
642 #[cfg(any(test, feature = "sqlite"))]
643 DatabaseType::SQLite(sl) => sl.find_similar_samples(uid, sim),
644 }
645 }
646
647 pub async fn user_allowed_files_by_sha256(
654 &self,
655 uid: u32,
656 next: Option<u64>,
657 ) -> Result<(Vec<String>, u64)> {
658 match self {
659 DatabaseType::Postgres(pg) => pg.user_allowed_files_by_sha256(uid, next).await,
660 #[cfg(any(test, feature = "sqlite"))]
661 DatabaseType::SQLite(sl) => sl.user_allowed_files_by_sha256(uid, next),
662 }
663 }
664
665 pub(crate) async fn get_encryption_keys(&self) -> Result<HashMap<u32, FileEncryption>> {
669 match self {
670 DatabaseType::Postgres(pg) => pg.get_encryption_keys().await,
671 #[cfg(any(test, feature = "sqlite"))]
672 DatabaseType::SQLite(sl) => sl.get_encryption_keys(),
673 }
674 }
675
676 pub(crate) async fn get_file_encryption_key_id(
678 &self,
679 hash: &str,
680 ) -> Result<(Option<u32>, Option<Vec<u8>>)> {
681 match self {
682 DatabaseType::Postgres(pg) => pg.get_file_encryption_key_id(hash).await,
683 #[cfg(any(test, feature = "sqlite"))]
684 DatabaseType::SQLite(sl) => sl.get_file_encryption_key_id(hash),
685 }
686 }
687
688 pub(crate) async fn set_file_nonce(&self, hash: &str, nonce: Option<&[u8]>) -> Result<()> {
690 match self {
691 DatabaseType::Postgres(pg) => pg.set_file_nonce(hash, nonce).await,
692 #[cfg(any(test, feature = "sqlite"))]
693 DatabaseType::SQLite(sl) => sl.set_file_nonce(hash, nonce),
694 }
695 }
696
697 pub(crate) async fn clear_file_crypto(&self, hash: &str) -> Result<()> {
700 match self {
701 DatabaseType::Postgres(pg) => pg.clear_file_crypto(hash).await,
702 #[cfg(any(test, feature = "sqlite"))]
703 DatabaseType::SQLite(sl) => sl.clear_file_crypto(hash),
704 }
705 }
706
707 pub async fn migrate_check(&self, action: Migration) -> Result<()> {
714 match self {
715 DatabaseType::Postgres(pg) => pg.migrate(action).await,
716 #[cfg(any(test, feature = "sqlite"))]
717 DatabaseType::SQLite(sl) => sl.migrate(action),
718 }
719 }
720
721 #[cfg_attr(docsrs, doc(cfg(feature = "admin")))]
729 #[cfg(any(test, feature = "admin"))]
730 pub async fn set_name(&self, name: &str) -> Result<()> {
731 match self {
732 DatabaseType::Postgres(pg) => pg.set_name(name).await,
733 #[cfg(any(test, feature = "sqlite"))]
734 DatabaseType::SQLite(sl) => sl.set_name(name),
735 }
736 }
737
738 #[cfg_attr(docsrs, doc(cfg(feature = "admin")))]
744 #[cfg(any(test, feature = "admin"))]
745 pub async fn enable_compression(&self) -> Result<()> {
746 match self {
747 DatabaseType::Postgres(pg) => pg.enable_compression().await,
748 #[cfg(any(test, feature = "sqlite"))]
749 DatabaseType::SQLite(sl) => sl.enable_compression(),
750 }
751 }
752
753 #[cfg_attr(docsrs, doc(cfg(feature = "admin")))]
759 #[cfg(any(test, feature = "admin"))]
760 pub async fn disable_compression(&self) -> Result<()> {
761 match self {
762 DatabaseType::Postgres(pg) => pg.disable_compression().await,
763 #[cfg(any(test, feature = "sqlite"))]
764 DatabaseType::SQLite(sl) => sl.disable_compression(),
765 }
766 }
767
768 #[cfg_attr(docsrs, doc(cfg(feature = "admin")))]
774 #[cfg(any(test, feature = "admin"))]
775 pub async fn enable_keep_unknown_files(&self) -> Result<()> {
776 match self {
777 DatabaseType::Postgres(pg) => pg.enable_keep_unknown_files().await,
778 #[cfg(any(test, feature = "sqlite"))]
779 DatabaseType::SQLite(sl) => sl.enable_keep_unknown_files(),
780 }
781 }
782
783 #[cfg_attr(docsrs, doc(cfg(feature = "admin")))]
789 #[cfg(any(test, feature = "admin"))]
790 pub async fn disable_keep_unknown_files(&self) -> Result<()> {
791 match self {
792 DatabaseType::Postgres(pg) => pg.disable_keep_unknown_files().await,
793 #[cfg(any(test, feature = "sqlite"))]
794 DatabaseType::SQLite(sl) => sl.disable_keep_unknown_files(),
795 }
796 }
797
798 #[cfg_attr(docsrs, doc(cfg(feature = "admin")))]
804 #[cfg(any(test, feature = "admin"))]
805 pub async fn add_file_encryption_key(&self, key: &FileEncryption) -> Result<u32> {
806 match self {
807 DatabaseType::Postgres(pg) => pg.add_file_encryption_key(key).await,
808 #[cfg(any(test, feature = "sqlite"))]
809 DatabaseType::SQLite(sl) => sl.add_file_encryption_key(key),
810 }
811 }
812
813 #[cfg_attr(docsrs, doc(cfg(feature = "admin")))]
819 #[cfg(any(test, feature = "admin"))]
820 pub async fn get_encryption_key_names_ids(&self) -> Result<Vec<(u32, EncryptionOption)>> {
821 match self {
822 DatabaseType::Postgres(pg) => pg.get_encryption_key_names_ids().await,
823 #[cfg(any(test, feature = "sqlite"))]
824 DatabaseType::SQLite(sl) => sl.get_encryption_key_names_ids(),
825 }
826 }
827
828 #[allow(clippy::too_many_arguments)]
834 #[cfg_attr(docsrs, doc(cfg(feature = "admin")))]
835 #[cfg(any(test, feature = "admin"))]
836 pub async fn create_user(
837 &self,
838 uname: &str,
839 fname: &str,
840 lname: &str,
841 email: &str,
842 password: Option<String>,
843 organisation: Option<&String>,
844 readonly: bool,
845 ) -> Result<u32> {
846 match self {
847 DatabaseType::Postgres(pg) => {
848 pg.create_user(uname, fname, lname, email, password, organisation, readonly)
849 .await
850 }
851 #[cfg(any(test, feature = "sqlite"))]
852 DatabaseType::SQLite(sl) => {
853 sl.create_user(uname, fname, lname, email, password, organisation, readonly)
854 }
855 }
856 }
857
858 #[cfg_attr(docsrs, doc(cfg(feature = "admin")))]
864 #[cfg(any(test, feature = "admin"))]
865 pub async fn reset_api_keys(&self) -> Result<u64> {
866 match self {
867 DatabaseType::Postgres(pg) => pg.reset_api_keys().await,
868 #[cfg(any(test, feature = "sqlite"))]
869 DatabaseType::SQLite(sl) => sl.reset_api_keys(),
870 }
871 }
872
873 #[cfg_attr(docsrs, doc(cfg(feature = "admin")))]
880 #[cfg(any(test, feature = "admin"))]
881 pub async fn set_password(&self, uname: &str, password: &str) -> Result<()> {
882 match self {
883 DatabaseType::Postgres(pg) => pg.set_password(uname, password).await,
884 #[cfg(any(test, feature = "sqlite"))]
885 DatabaseType::SQLite(sl) => sl.set_password(uname, password),
886 }
887 }
888
889 #[cfg_attr(docsrs, doc(cfg(all(feature = "admin", feature = "anonymous"))))]
895 #[cfg(any(test, all(feature = "admin", feature = "anonymous")))]
896 pub async fn set_anonymous_user(&self, uid: u32) -> Result<()> {
897 match self {
898 DatabaseType::Postgres(pg) => pg.set_anonymous_user(uid).await,
899 #[cfg(any(test, feature = "sqlite"))]
900 DatabaseType::SQLite(sl) => sl.set_anonymous_user(uid),
901 }
902 }
903
904 #[cfg_attr(docsrs, doc(cfg(all(feature = "admin", feature = "anonymous"))))]
911 #[cfg(any(test, all(feature = "admin", feature = "anonymous")))]
912 pub async fn clear_anonymous_user(&self) -> Result<()> {
913 match self {
914 DatabaseType::Postgres(pg) => pg.clear_anonymous_user().await,
915 #[cfg(any(test, feature = "sqlite"))]
916 DatabaseType::SQLite(sl) => sl.clear_anonymous_user(),
917 }
918 }
919
920 #[cfg_attr(docsrs, doc(cfg(feature = "admin")))]
926 #[cfg(any(test, feature = "admin"))]
927 pub async fn list_users(&self) -> Result<Vec<admin::User>> {
928 match self {
929 DatabaseType::Postgres(pg) => pg.list_users().await,
930 #[cfg(any(test, feature = "sqlite"))]
931 DatabaseType::SQLite(sl) => sl.list_users(),
932 }
933 }
934
935 #[cfg_attr(docsrs, doc(cfg(feature = "admin")))]
942 #[cfg(any(test, feature = "admin"))]
943 pub async fn group_id_from_name(&self, name: &str) -> Result<i32> {
944 match self {
945 DatabaseType::Postgres(pg) => pg.group_id_from_name(name).await,
946 #[cfg(any(test, feature = "sqlite"))]
947 DatabaseType::SQLite(sl) => sl.group_id_from_name(name),
948 }
949 }
950
951 #[cfg_attr(docsrs, doc(cfg(feature = "admin")))]
958 #[cfg(any(test, feature = "admin"))]
959 pub async fn edit_group(
960 &self,
961 gid: u32,
962 name: &str,
963 desc: &str,
964 parent: Option<u32>,
965 ) -> Result<()> {
966 match self {
967 DatabaseType::Postgres(pg) => pg.edit_group(gid, name, desc, parent).await,
968 #[cfg(any(test, feature = "sqlite"))]
969 DatabaseType::SQLite(sl) => sl.edit_group(gid, name, desc, parent),
970 }
971 }
972
973 #[cfg_attr(docsrs, doc(cfg(feature = "admin")))]
979 #[cfg(any(test, feature = "admin"))]
980 pub async fn list_groups(&self) -> Result<Vec<admin::Group>> {
981 match self {
982 DatabaseType::Postgres(pg) => pg.list_groups().await,
983 #[cfg(any(test, feature = "sqlite"))]
984 DatabaseType::SQLite(sl) => sl.list_groups(),
985 }
986 }
987
988 #[cfg_attr(docsrs, doc(cfg(feature = "admin")))]
995 #[cfg(any(test, feature = "admin"))]
996 pub async fn add_user_to_group(&self, uid: u32, gid: u32) -> Result<()> {
997 match self {
998 DatabaseType::Postgres(pg) => pg.add_user_to_group(uid, gid).await,
999 #[cfg(any(test, feature = "sqlite"))]
1000 DatabaseType::SQLite(sl) => sl.add_user_to_group(uid, gid),
1001 }
1002 }
1003
1004 #[cfg_attr(docsrs, doc(cfg(feature = "admin")))]
1011 #[cfg(any(test, feature = "admin"))]
1012 pub async fn add_group_to_source(&self, gid: u32, sid: u32) -> Result<()> {
1013 match self {
1014 DatabaseType::Postgres(pg) => pg.add_group_to_source(gid, sid).await,
1015 #[cfg(any(test, feature = "sqlite"))]
1016 DatabaseType::SQLite(sl) => sl.add_group_to_source(gid, sid),
1017 }
1018 }
1019
1020 #[cfg_attr(docsrs, doc(cfg(feature = "admin")))]
1027 #[cfg(any(test, feature = "admin"))]
1028 pub async fn create_group(
1029 &self,
1030 name: &str,
1031 description: &str,
1032 parent: Option<u32>,
1033 ) -> Result<u32> {
1034 match self {
1035 DatabaseType::Postgres(pg) => pg.create_group(name, description, parent).await,
1036 #[cfg(any(test, feature = "sqlite"))]
1037 DatabaseType::SQLite(sl) => sl.create_group(name, description, parent),
1038 }
1039 }
1040
1041 #[cfg_attr(docsrs, doc(cfg(feature = "admin")))]
1047 #[cfg(any(test, feature = "admin"))]
1048 pub async fn list_sources(&self) -> Result<Vec<admin::Source>> {
1049 match self {
1050 DatabaseType::Postgres(pg) => pg.list_sources().await,
1051 #[cfg(any(test, feature = "sqlite"))]
1052 DatabaseType::SQLite(sl) => sl.list_sources(),
1053 }
1054 }
1055
1056 #[cfg_attr(docsrs, doc(cfg(feature = "admin")))]
1063 #[cfg(any(test, feature = "admin"))]
1064 pub async fn create_source(
1065 &self,
1066 name: &str,
1067 description: Option<&str>,
1068 url: Option<&str>,
1069 date: chrono::DateTime<Local>,
1070 releasable: bool,
1071 malicious: Option<bool>,
1072 ) -> Result<u32> {
1073 match self {
1074 DatabaseType::Postgres(pg) => {
1075 pg.create_source(name, description, url, date, releasable, malicious)
1076 .await
1077 }
1078 #[cfg(any(test, feature = "sqlite"))]
1079 DatabaseType::SQLite(sl) => {
1080 sl.create_source(name, description, url, date, releasable, malicious)
1081 }
1082 }
1083 }
1084
1085 #[cfg_attr(docsrs, doc(cfg(feature = "admin")))]
1092 #[cfg(any(test, feature = "admin"))]
1093 pub async fn edit_user(
1094 &self,
1095 uid: u32,
1096 uname: &str,
1097 fname: &str,
1098 lname: &str,
1099 email: &str,
1100 readonly: bool,
1101 ) -> Result<()> {
1102 match self {
1103 DatabaseType::Postgres(pg) => {
1104 pg.edit_user(uid, uname, fname, lname, email, readonly)
1105 .await
1106 }
1107 #[cfg(any(test, feature = "sqlite"))]
1108 DatabaseType::SQLite(sl) => sl.edit_user(uid, uname, fname, lname, email, readonly),
1109 }
1110 }
1111
1112 #[cfg_attr(docsrs, doc(cfg(feature = "admin")))]
1118 #[cfg(any(test, feature = "admin"))]
1119 pub async fn set_user_ro(&self, uid: u32) -> Result<()> {
1120 match self {
1121 DatabaseType::Postgres(pg) => pg.set_user_ro(uid).await,
1122 #[cfg(any(test, feature = "sqlite"))]
1123 DatabaseType::SQLite(sl) => sl.set_user_ro(uid),
1124 }
1125 }
1126
1127 #[cfg_attr(docsrs, doc(cfg(feature = "admin")))]
1133 #[cfg(any(test, feature = "admin"))]
1134 pub async fn set_user_rw(&self, uid: u32) -> Result<()> {
1135 match self {
1136 DatabaseType::Postgres(pg) => pg.set_user_rw(uid).await,
1137 #[cfg(any(test, feature = "sqlite"))]
1138 DatabaseType::SQLite(sl) => sl.set_user_rw(uid),
1139 }
1140 }
1141
1142 #[cfg_attr(docsrs, doc(cfg(feature = "admin")))]
1149 #[cfg(any(test, feature = "admin"))]
1150 pub async fn deactivate_user(&self, uid: u32) -> Result<()> {
1151 match self {
1152 DatabaseType::Postgres(pg) => pg.deactivate_user(uid).await,
1153 #[cfg(any(test, feature = "sqlite"))]
1154 DatabaseType::SQLite(sl) => sl.deactivate_user(uid),
1155 }
1156 }
1157
1158 #[cfg_attr(docsrs, doc(cfg(feature = "admin")))]
1164 #[cfg(any(test, feature = "admin"))]
1165 pub async fn file_types_counts(&self) -> Result<HashMap<String, u32>> {
1166 match self {
1167 DatabaseType::Postgres(pg) => pg.file_types_counts().await,
1168 #[cfg(any(test, feature = "sqlite"))]
1169 DatabaseType::SQLite(sl) => sl.file_types_counts(),
1170 }
1171 }
1172
1173 #[cfg_attr(docsrs, doc(cfg(feature = "admin")))]
1181 #[cfg(any(test, feature = "admin"))]
1182 pub async fn create_label(&self, name: &str, parent: Option<u64>) -> Result<u64> {
1183 match self {
1184 DatabaseType::Postgres(pg) => pg.create_label(name, parent).await,
1185 #[cfg(any(test, feature = "sqlite"))]
1186 DatabaseType::SQLite(sl) => sl.create_label(name, parent),
1187 }
1188 }
1189
1190 #[cfg_attr(docsrs, doc(cfg(feature = "admin")))]
1198 #[cfg(any(test, feature = "admin"))]
1199 pub async fn edit_label(&self, id: u64, name: &str, parent: Option<u64>) -> Result<()> {
1200 match self {
1201 DatabaseType::Postgres(pg) => pg.edit_label(id, name, parent).await,
1202 #[cfg(any(test, feature = "sqlite"))]
1203 DatabaseType::SQLite(sl) => sl.edit_label(id, name, parent),
1204 }
1205 }
1206
1207 #[cfg_attr(docsrs, doc(cfg(feature = "admin")))]
1214 #[cfg(any(test, feature = "admin"))]
1215 pub async fn label_id_from_name(&self, name: &str) -> Result<u64> {
1216 match self {
1217 DatabaseType::Postgres(pg) => pg.label_id_from_name(name).await,
1218 #[cfg(any(test, feature = "sqlite"))]
1219 DatabaseType::SQLite(sl) => sl.label_id_from_name(name),
1220 }
1221 }
1222
1223 #[cfg_attr(docsrs, doc(cfg(feature = "admin")))]
1231 #[cfg(any(test, feature = "admin"))]
1232 pub async fn label_file(&self, file_id: u64, label_id: u64) -> Result<()> {
1233 match self {
1234 DatabaseType::Postgres(pg) => pg.label_file(file_id, label_id).await,
1235 #[cfg(any(test, feature = "sqlite"))]
1236 DatabaseType::SQLite(sl) => sl.label_file(file_id, label_id),
1237 }
1238 }
1239}
1240
1241pub fn hash_password(password: &str) -> Result<String> {
1247 let salt = SaltString::generate(&mut OsRng);
1248 let argon2 = Argon2::default();
1249 Ok(argon2
1250 .hash_password(password.as_bytes(), &salt)?
1251 .to_string())
1252}
1253
1254#[must_use]
1256pub fn random_bytes_api_key() -> String {
1257 let key1 = uuid::Uuid::new_v4();
1258 let key2 = uuid::Uuid::new_v4();
1259 let key1 = key1.to_string().replace('-', "");
1260 let key2 = key2.to_string().replace('-', "");
1261 format!("{key1}{key2}")
1262}
1263
1264#[cfg(test)]
1265mod tests {
1266 use super::*;
1267 #[cfg(feature = "vt")]
1268 use crate::vt::VtUpdater;
1269
1270 use std::fs;
1271 #[cfg(feature = "vt")]
1272 use std::sync::Arc;
1273 #[cfg(feature = "vt")]
1274 use std::time::SystemTime;
1275
1276 use anyhow::Context;
1277 use fuzzyhash::FuzzyHash;
1278 use malwaredb_api::{PartialHashSearchType, SearchRequestParameters, SearchType};
1279 use malwaredb_lzjd2::lzjd::LzDigest;
1280 use tlsh_fixed::TlshBuilder;
1281 use uuid::Uuid;
1282
1283 const MALWARE_LABEL: &str = "malware";
1284 const RANSOMWARE_LABEL: &str = "ransomware";
1285
1286 fn generate_similarity_request(data: &[u8]) -> malwaredb_api::SimilarSamplesRequest {
1287 let mut hashes = vec![];
1288
1289 hashes.push((malwaredb_api::SimilarityHashType::SSDeep, FuzzyHash::new(data).to_string()));
1290
1291 let mut builder = TlshBuilder::new(
1292 tlsh_fixed::BucketKind::Bucket256,
1293 tlsh_fixed::ChecksumKind::ThreeByte,
1294 tlsh_fixed::Version::Version4,
1295 );
1296
1297 builder.update(data);
1298
1299 if let Ok(hasher) = builder.build() {
1300 hashes.push((malwaredb_api::SimilarityHashType::TLSH, hasher.hash()));
1301 }
1302
1303 let lzjd_str = LzDigest::from(data).to_string();
1304 hashes.push((malwaredb_api::SimilarityHashType::LZJD, lzjd_str));
1305
1306 malwaredb_api::SimilarSamplesRequest { hashes }
1307 }
1308
1309 async fn pg_config() -> Postgres {
1310 const CONNECTION_STRING: &str = "user=malwaredbtesting password=malwaredbtesting dbname=malwaredbtesting host=localhost sslmode=disable";
1313
1314 if let Ok(pg_port) = std::env::var("PG_PORT") {
1315 let conn_string = format!("{CONNECTION_STRING} port={pg_port}");
1317 Postgres::new(&conn_string, None)
1318 .await
1319 .context(format!("failed to connect to postgres with specified port {pg_port}"))
1320 .unwrap()
1321 } else {
1322 Postgres::new(CONNECTION_STRING, None).await.unwrap()
1323 }
1324 }
1325
1326 #[tokio::test]
1327 #[ignore = "don't run this in CI"]
1328 async fn pg() {
1329 let psql = pg_config().await;
1330 psql.delete().await.unwrap();
1331
1332 let psql = pg_config().await;
1333 let db = DatabaseType::Postgres(psql);
1334 everything(&db).await.unwrap();
1335
1336 #[cfg(feature = "vt")]
1337 {
1338 let db_config = db.get_config().await.unwrap();
1339 let state = crate::State {
1340 port: 8080,
1341 directory: None,
1342 max_upload: 10 * 1024 * 1024,
1343 ip: "127.0.0.1".parse().unwrap(),
1344 db_type: Arc::new(db),
1345 db_config,
1346 keys: HashMap::new(),
1347 started: SystemTime::now(),
1348 vt_client: std::env::var("VT_API_KEY")
1349 .map_or(None, |e| Some(malwaredb_virustotal::VirusTotalClient::new(e))),
1350 tls_config: None,
1351 mdns: None,
1352 };
1353
1354 let vt: VtUpdater = state.try_into().expect("failed to create VtUpdater");
1355
1356 vt.updater().await.unwrap();
1357 println!("PG: Did VT ops!");
1358
1359 let psql = pg_config().await;
1360
1361 let vt_stats = psql
1362 .get_vt_stats()
1363 .await
1364 .context("failed to get Postgres VT Stats")
1365 .unwrap();
1366 println!("{vt_stats:?}");
1367 assert!(
1368 vt_stats.files_without_records + vt_stats.clean_records + vt_stats.hits_records > 2
1369 );
1370 }
1371
1372 let psql = pg_config().await;
1374 psql.delete().await.unwrap();
1375 }
1376
1377 #[tokio::test]
1378 async fn sqlite() {
1379 const DB_FILE: &str = "testing_sqlite.db";
1380 if std::path::Path::new(DB_FILE).exists() {
1381 fs::remove_file(DB_FILE)
1382 .context(format!("failed to delete old SQLite file {DB_FILE}"))
1383 .unwrap();
1384 }
1385
1386 let sqlite = Sqlite::new(DB_FILE)
1387 .context(format!("failed to create SQLite instance for {DB_FILE}"))
1388 .unwrap();
1389
1390 let db = DatabaseType::SQLite(sqlite);
1391 everything(&db).await.unwrap();
1392
1393 #[cfg(feature = "vt")]
1394 {
1395 let db_config = db.get_config().await.unwrap();
1396 let state = crate::State {
1397 port: 8080,
1398 directory: None,
1399 max_upload: 10 * 1024 * 1024,
1400 ip: "127.0.0.1".parse().unwrap(),
1401 db_type: Arc::new(db),
1402 db_config,
1403 keys: HashMap::new(),
1404 started: SystemTime::now(),
1405 vt_client: std::env::var("VT_API_KEY")
1406 .map_or(None, |e| Some(malwaredb_virustotal::VirusTotalClient::new(e))),
1407 tls_config: None,
1408 mdns: None,
1409 };
1410
1411 let sqlite_second = Sqlite::new(DB_FILE)
1412 .context(format!("failed to create SQLite instance for {DB_FILE}"))
1413 .unwrap();
1414
1415 let vt: VtUpdater = state.try_into().expect("failed to create VtUpdater");
1416
1417 vt.updater().await.unwrap();
1418 println!("Sqlite: Did VT ops!");
1419 let vt_stats = sqlite_second
1420 .get_vt_stats()
1421 .context("failed to get Sqlite VT Stats")
1422 .unwrap();
1423 println!("{vt_stats:?}");
1424 assert!(
1425 vt_stats.files_without_records + vt_stats.clean_records + vt_stats.hits_records > 2
1426 );
1427 }
1428
1429 fs::remove_file(DB_FILE)
1430 .context(format!("failed to delete SQLite file {DB_FILE}"))
1431 .unwrap();
1432 }
1433
1434 #[allow(clippy::too_many_lines)]
1435 async fn everything(db: &DatabaseType) -> Result<()> {
1436 const ADMIN_UNAME: &str = "admin";
1437 const ADMIN_PASSWORD: &str = "super_secure_password_dont_tell_anyone!";
1438
1439 db.set_name("Testing Database")
1440 .await
1441 .context("setting instance name failed")?;
1442
1443 assert!(
1444 db.authenticate(ADMIN_UNAME, ADMIN_PASSWORD).await.is_err(),
1445 "Authentication without password should have failed."
1446 );
1447
1448 db.set_password(ADMIN_UNAME, ADMIN_PASSWORD)
1449 .await
1450 .context("failed to set admin password")?;
1451
1452 let admin_api_key = db
1453 .authenticate(ADMIN_UNAME, ADMIN_PASSWORD)
1454 .await
1455 .context("unable to get api key for admin")?;
1456 println!("API key: {admin_api_key}");
1457 assert_eq!(admin_api_key.len(), 64);
1458
1459 assert_eq!(db.get_uid(&admin_api_key).await?, 0, "Unable to get UID given the API key");
1460
1461 let admin_api_key_again = db
1462 .authenticate(ADMIN_UNAME, ADMIN_PASSWORD)
1463 .await
1464 .context("unable to get api key a second time for admin")?;
1465
1466 assert_eq!(admin_api_key, admin_api_key_again, "API keys didn't match the second time.");
1467
1468 let bad_password = "this_is_totally_not_my_password!!";
1469 eprintln!("Testing API login with incorrect password.");
1470 assert!(
1471 db.authenticate(ADMIN_UNAME, bad_password).await.is_err(),
1472 "Authenticating as admin with a bad password should have failed."
1473 );
1474
1475 let admin_is_admin = db
1476 .user_is_admin(0)
1477 .await
1478 .context("unable to see if admin (uid 0) is an admin")?;
1479 assert!(admin_is_admin);
1480
1481 let new_user_uname = "testuser";
1482 let new_user_email = "test@example.com";
1483 let new_user_password = "some_awesome_password_++";
1484 let new_id = db
1485 .create_user(
1486 new_user_uname,
1487 new_user_uname,
1488 new_user_uname,
1489 new_user_email,
1490 Some(new_user_password.into()),
1491 None,
1492 false,
1493 )
1494 .await
1495 .context(format!("failed to create user {new_user_uname}"))?;
1496
1497 let passwordless_user_id = db
1498 .create_user(
1499 "passwordless_user",
1500 "passwordless_user",
1501 "passwordless_user",
1502 "passwordless_user@example.com",
1503 None,
1504 None,
1505 false,
1506 )
1507 .await
1508 .context("failed to create passwordless_user")?;
1509
1510 for user in &db.list_users().await.context("failed to list users")? {
1511 if user.id == passwordless_user_id {
1512 assert_eq!(user.uname, "passwordless_user");
1513 }
1514 }
1515
1516 db.edit_user(
1517 passwordless_user_id,
1518 "passwordless_user_2",
1519 "passwordless_user_2",
1520 "passwordless_user_2",
1521 "passwordless_user_2@something.com",
1522 false,
1523 )
1524 .await
1525 .context(format!("failed to alter 'passwordless' user, id {passwordless_user_id}"))?;
1526
1527 for user in &db.list_users().await.context("failed to list users")? {
1528 if user.id == passwordless_user_id {
1529 assert_eq!(user.uname, "passwordless_user_2");
1530 }
1531 }
1532
1533 assert!(new_id > 0, "Weird UID created for user {new_user_uname}: {new_id}");
1534
1535 assert!(
1536 db.create_user(
1537 new_user_uname,
1538 new_user_uname,
1539 new_user_uname,
1540 new_user_email,
1541 Some(new_user_password.into()),
1542 None,
1543 false
1544 )
1545 .await
1546 .is_err(),
1547 "Creating a new user with the same user name should fail"
1548 );
1549
1550 let ro_user_name = "ro_user";
1551 let ro_user_password = "ro_user_password";
1552 db.create_user(
1553 ro_user_name,
1554 "ro_user",
1555 "ro_user",
1556 "ro@example.com",
1557 Some(ro_user_password.into()),
1558 None,
1559 true,
1560 )
1561 .await
1562 .context("failed to create read-only user")?;
1563
1564 let ro_user_api_key = db
1565 .authenticate(ro_user_name, ro_user_password)
1566 .await
1567 .context("unable to get api key for read-only user")?;
1568
1569 let new_user_password_change = "some_new_awesomer_password!_++";
1570 db.set_password(new_user_uname, new_user_password_change)
1571 .await
1572 .context("failed to change the password for testuser")?;
1573
1574 let new_user_api_key = db
1575 .authenticate(new_user_uname, new_user_password_change)
1576 .await
1577 .context("unable to get api key for testuser")?;
1578 eprintln!("{new_user_uname} got API key {new_user_api_key}");
1579
1580 assert_eq!(admin_api_key.len(), new_user_api_key.len());
1581
1582 let users = db.list_users().await.context("failed to list users")?;
1583 assert_eq!(users.len(), 4, "Four users were created, yet there are {} users", users.len());
1584 eprintln!("DB has {} users:", users.len());
1585 let mut passwordless_user_found = false;
1586 for user in users {
1587 println!("{user}");
1588 if user.uname == "passwordless_user_2" {
1589 assert!(!user.has_api_key);
1590 assert!(!user.has_password);
1591 passwordless_user_found = true;
1592 } else {
1593 assert!(user.has_api_key);
1594 assert!(user.has_password);
1595 }
1596 }
1597 assert!(passwordless_user_found);
1598
1599 let new_group_name = "some_new_group";
1600 let new_group_desc = "some_new_group_description";
1601 let new_group_id = 1;
1602 assert_eq!(
1603 db.create_group(new_group_name, new_group_desc, None)
1604 .await
1605 .context("failed to create group")?,
1606 new_group_id,
1607 "New group didn't have the expected ID, expected {new_group_id}"
1608 );
1609
1610 assert!(
1611 db.create_group(new_group_name, new_group_desc, None)
1612 .await
1613 .is_err(),
1614 "Duplicate group name should have failed"
1615 );
1616
1617 db.add_user_to_group(1, 1)
1618 .await
1619 .context("Unable to add uid 1 to gid 1")?;
1620
1621 let ro_user_uid = db
1622 .get_uid(&ro_user_api_key)
1623 .await
1624 .context("Unable to get UID for read-only user")?;
1625 db.add_user_to_group(ro_user_uid, 1)
1626 .await
1627 .context("Unable to add uid 2 to gid 1")?;
1628
1629 let new_admin_group_name = "admin_subgroup";
1630 let new_admin_group_desc = "admin_subgroup_description";
1631 let new_admin_group_id = 2;
1632 assert!(
1634 db.create_group(new_admin_group_name, new_admin_group_desc, Some(0))
1635 .await
1636 .context("failed to create admin sub-group")?
1637 >= new_admin_group_id,
1638 "New group didn't have the expected ID, expected >= {new_admin_group_id}"
1639 );
1640
1641 let groups = db.list_groups().await.context("failed to list groups")?;
1642 assert_eq!(
1643 groups.len(),
1644 3,
1645 "Three groups were created, yet there are {} groups",
1646 groups.len()
1647 );
1648 eprintln!("DB has {} groups:", groups.len());
1649 for group in groups {
1650 println!("{group}");
1651 if group.id == new_admin_group_id {
1652 assert_eq!(group.parent, Some("admin".to_string()));
1653 }
1654 if group.id == 1 {
1655 let test_user_str = String::from(new_user_uname);
1656 let mut found = false;
1657 for member in group.members {
1658 if member.uname == test_user_str {
1659 found = true;
1660 break;
1661 }
1662 }
1663 assert!(found, "new user {test_user_str} wasn't in the group");
1664 }
1665 }
1666
1667 let default_source_name = "default_source".to_string();
1668 let default_source_id = db
1669 .create_source(
1670 &default_source_name,
1671 Some("desc_default_source"),
1672 None,
1673 Local::now(),
1674 true,
1675 Some(false),
1676 )
1677 .await
1678 .context("failed to create source `default_source`")?;
1679
1680 db.add_group_to_source(1, default_source_id)
1681 .await
1682 .context("failed to add group 1 to source 1")?;
1683
1684 let another_source_name = "another_source".to_string();
1685 let another_source_id = db
1686 .create_source(
1687 &another_source_name,
1688 Some("yet another file source"),
1689 None,
1690 Local::now(),
1691 true,
1692 Some(false),
1693 )
1694 .await
1695 .context("failed to create source `another_source`")?;
1696
1697 let empty_source_name = "empty_source".to_string();
1698 db.create_source(
1699 &empty_source_name,
1700 Some("empty and unused file source"),
1701 None,
1702 Local::now(),
1703 true,
1704 Some(false),
1705 )
1706 .await
1707 .context("failed to create source `another_source`")?;
1708
1709 db.add_group_to_source(1, another_source_id)
1710 .await
1711 .context("failed to add group 1 to source 1")?;
1712
1713 let sources = db.list_sources().await.context("failed to list sources")?;
1714 eprintln!("DB has {} sources:", sources.len());
1715 for source in sources {
1716 println!("{source}");
1717 assert_eq!(source.files, 0);
1718 if source.id == default_source_id || source.id == another_source_id {
1719 assert_eq!(
1720 source.groups, 1,
1721 "default source {default_source_name} should have 1 group"
1722 );
1723 } else {
1724 assert_eq!(source.groups, 0, "groups should zero (empty)");
1725 }
1726 }
1727
1728 let uid = db
1729 .get_uid(&new_user_api_key)
1730 .await
1731 .context("failed to user uid from apikey")?;
1732 let user_info = db
1733 .get_user_info(uid)
1734 .await
1735 .context("failed to get user's available groups and sources")?;
1736 assert!(user_info.sources.contains(&default_source_name));
1737 assert!(!user_info.is_admin);
1738 println!("UserInfoResponse: {user_info:?}");
1739
1740 assert!(
1741 db.allowed_user_source(1, default_source_id)
1742 .await
1743 .context(format!(
1744 "failed to check that user 1 has access to source {default_source_id}"
1745 ))?,
1746 "User 1 should should have had access to source {default_source_id}"
1747 );
1748
1749 assert!(
1750 !db.allowed_user_source(1, 5)
1751 .await
1752 .context("failed to check that user 1 has access to source 5")?,
1753 "User 1 should should not have had access to source 5"
1754 );
1755
1756 let test_label_id = db
1757 .create_label("TestLabel", None)
1758 .await
1759 .context("failed to create test label")?;
1760 let test_elf_label_id = db
1761 .create_label("TestELF", Some(test_label_id))
1762 .await
1763 .context("failed to create test label")?;
1764
1765 let test_elf = include_bytes!("../../../types/testdata/elf/elf_linux_ppc64le").to_vec();
1766 let test_elf_meta = FileMetadata::new(&test_elf, Some("elf_linux_ppc64le"));
1767 let elf_type = db.get_type_id_for_bytes(&test_elf).await.unwrap();
1768
1769 let known_type =
1770 KnownType::new(&test_elf).context("failed to parse elf from test crate's test data")?;
1771 assert!(known_type.is_exec(), "ELF should be executable");
1772 eprintln!("ELF type ID: {elf_type}");
1773
1774 let file_addition = db
1775 .add_file(&test_elf_meta, known_type.clone(), 1, default_source_id, elf_type, None)
1776 .await
1777 .context("failed to insert a test elf")?;
1778 assert!(file_addition.is_new, "File should have been added");
1779 eprintln!("Added ELF to the DB");
1780 db.label_file(file_addition.file_id, test_elf_label_id)
1781 .await
1782 .context("failed to label file")?;
1783
1784 let partial_search = SearchRequest {
1786 search: SearchType::Search(SearchRequestParameters {
1787 partial_hash: Some((PartialHashSearchType::SHA1, "fe7d0186".into())),
1788 labels: Some(vec![String::from("TestELF")]),
1789 file_type: Some(String::from("ELF")),
1790 magic: Some(String::from("ELF")),
1791 ..Default::default()
1792 }),
1793 };
1794 assert!(partial_search.is_valid());
1795 let partial_search_response = db.partial_search(1, partial_search).await?;
1796 assert_eq!(partial_search_response.hashes.len(), 1);
1797 assert_eq!(
1798 partial_search_response.hashes[0],
1799 "897541f9f3c673b3ecc7004ff52c70c0b0440e804c7c3eb4854d72d94c317868"
1800 );
1801
1802 let partial_search = SearchRequest {
1804 search: SearchType::Search(SearchRequestParameters {
1805 partial_hash: None,
1806 labels: None,
1807 file_type: None,
1808 magic: Some(String::from("ELF")),
1809 ..Default::default()
1810 }),
1811 };
1812 assert!(partial_search.is_valid());
1813 let partial_search_response = db.partial_search(1, partial_search).await?;
1814 assert_eq!(partial_search_response.hashes.len(), 1);
1815 assert_eq!(
1816 partial_search_response.hashes[0],
1817 "897541f9f3c673b3ecc7004ff52c70c0b0440e804c7c3eb4854d72d94c317868"
1818 );
1819
1820 let partial_search = SearchRequest {
1822 search: SearchType::Search(SearchRequestParameters {
1823 partial_hash: Some((PartialHashSearchType::SHA1, "fe7d0186".into())),
1824 file_type: Some(String::from("PE32")),
1825 ..Default::default()
1826 }),
1827 };
1828 assert!(partial_search.is_valid());
1829 let partial_search_response = db.partial_search(1, partial_search).await?;
1830 assert_eq!(partial_search_response.hashes.len(), 0);
1831
1832 let partial_search = SearchRequest {
1833 search: SearchType::Search(SearchRequestParameters {
1834 file_name: Some("ppc64".into()),
1835 ..Default::default()
1836 }),
1837 };
1838 assert!(partial_search.is_valid());
1839 let partial_search_response = db.partial_search(1, partial_search).await?;
1840 assert_eq!(partial_search_response.hashes.len(), 1);
1841
1842 let partial_search = SearchRequest {
1844 search: SearchType::Search(SearchRequestParameters::default()),
1845 };
1846 assert!(!partial_search.is_valid());
1847 let partial_search_response = db.partial_search(1, partial_search).await?;
1848 assert!(partial_search_response.hashes.is_empty());
1849
1850 let partial_search = SearchRequest {
1852 search: SearchType::Continuation(Uuid::default()),
1853 };
1854 assert!(partial_search.is_valid());
1855 let partial_search_response = db.partial_search(1, partial_search).await?;
1856 assert!(partial_search_response.hashes.is_empty());
1857
1858 assert!(
1860 db.get_type_id_for_bytes(include_bytes!("../../../../MDB_Logo.ico"))
1861 .await
1862 .is_err()
1863 );
1864
1865 assert!(
1866 db.add_file(
1867 &test_elf_meta,
1868 known_type.clone(),
1869 ro_user_uid,
1870 default_source_id,
1871 elf_type,
1872 None
1873 )
1874 .await
1875 .is_err(),
1876 "Read-only user should not be able to add a file"
1877 );
1878
1879 let mut test_elf_meta_different_name = test_elf_meta.clone();
1880 test_elf_meta_different_name.name = Some("completely_different_name.bin".into());
1881
1882 assert!(
1883 !db.add_file(
1884 &test_elf_meta_different_name,
1885 known_type,
1886 1,
1887 another_source_id,
1888 elf_type,
1889 None
1890 )
1891 .await
1892 .context("failed to insert a test elf again for a different source")?
1893 .is_new
1894 );
1895
1896 let sources = db
1897 .list_sources()
1898 .await
1899 .context("failed to re-list sources")?;
1900 eprintln!("DB has {} sources, and a file was added twice:", sources.len());
1901 println!("We should have two sources with one file each, yet only one ELF.");
1902 for source in sources {
1903 println!("{source}");
1904 if source.id == default_source_id || source.id == another_source_id {
1905 assert_eq!(source.files, 1);
1906 } else {
1907 assert_eq!(source.files, 0, "groups should zero (empty)");
1908 }
1909 }
1910
1911 assert!(
1912 !db.get_user_sources(1)
1913 .await
1914 .expect("failed to get user 1's sources")
1915 .sources
1916 .is_empty()
1917 );
1918
1919 let file_types_counts = db
1920 .file_types_counts()
1921 .await
1922 .context("failed to get file types and counts")?;
1923 for (name, count) in file_types_counts {
1924 println!("{name}: {count}");
1925 assert_eq!(name, "ELF");
1926 assert_eq!(count, 1);
1927 }
1928
1929 let mut test_elf_modified = test_elf.clone();
1930 let random_bytes = Uuid::new_v4();
1931 let mut random_bytes = random_bytes.into_bytes().to_vec();
1932 test_elf_modified.append(&mut random_bytes);
1933 let similarity_request = generate_similarity_request(&test_elf_modified);
1934 let similarity_response = db
1935 .find_similar_samples(1, &similarity_request.hashes)
1936 .await
1937 .context("failed to get similarity response")?;
1938 eprintln!("Similarity response: {similarity_response:?}");
1939 let similarity_response = similarity_response.first().unwrap();
1940 assert_eq!(
1941 similarity_response.sha256,
1942 hex::encode(&test_elf_meta.sha256),
1943 "Similarity response should have had the hash of the original ELF"
1944 );
1945 for (algo, sim) in &similarity_response.algorithms {
1946 match algo {
1947 malwaredb_api::SimilarityHashType::LZJD => {
1948 assert!(*sim > 0.0f32);
1949 }
1950 malwaredb_api::SimilarityHashType::SSDeep => {
1951 assert!(*sim > 80.0f32);
1952 }
1953 malwaredb_api::SimilarityHashType::TLSH => {
1954 assert!(*sim <= 20f32);
1955 }
1956 _ => {}
1957 }
1958 }
1959
1960 let test_elf_hashtype = HashType::try_from(test_elf_meta.sha1.as_slice())
1961 .context("failed to get `HashType::SHA1` from string")?;
1962 let response_sha256 = db
1963 .retrieve_sample(1, &test_elf_hashtype)
1964 .await
1965 .context("could not get SHA-256 hash from test sample")
1966 .unwrap();
1967 assert_eq!(response_sha256, hex::encode(&test_elf_meta.sha256));
1968
1969 let test_bogus_hash =
1970 HashType::try_from("d154b8420fc56a629df2e6d918be53310d8ac39a926aa5f60ae59a66298969a0")
1971 .context("failed to get `HashType` from static string")?;
1972 assert!(
1973 db.retrieve_sample(1, &test_bogus_hash).await.is_err(),
1974 "Getting a file with a bogus hash should have failed."
1975 );
1976
1977 let test_pdf = include_bytes!("../../../types/testdata/pdf/test.pdf").to_vec();
1978 let test_pdf_meta = FileMetadata::new(&test_pdf, Some("test.pdf"));
1979 let pdf_type = db.get_type_id_for_bytes(&test_pdf).await.unwrap();
1980
1981 let known_type =
1982 KnownType::new(&test_pdf).context("failed to parse pdf from test crate's test data")?;
1983
1984 assert!(
1985 db.add_file(&test_pdf_meta, known_type, 1, default_source_id, pdf_type, None)
1986 .await
1987 .context("failed to insert a test pdf")?
1988 .is_new
1989 );
1990 eprintln!("Added PDF to the DB");
1991
1992 let test_rtf = include_bytes!("../../../types/testdata/rtf/hello.rtf").to_vec();
1993 let test_rtf_meta = FileMetadata::new(&test_rtf, Some("test.rtf"));
1994 let rtf_type = db
1995 .get_type_id_for_bytes(&test_rtf)
1996 .await
1997 .context("failed to get file type id for rtf")?;
1998
1999 let known_type =
2000 KnownType::new(&test_rtf).context("failed to parse pdf from test crate's test data")?;
2001
2002 assert!(
2003 db.add_file(&test_rtf_meta, known_type, 1, default_source_id, rtf_type, None)
2004 .await
2005 .context("failed to insert a test rtf")?
2006 .is_new
2007 );
2008 eprintln!("Added RTF to the DB");
2009
2010 let report = db
2011 .get_sample_report(1, &HashType::try_from(test_rtf_meta.sha256.as_slice()).unwrap())
2012 .await
2013 .context("failed to get report for test rtf")?;
2014 assert!(
2015 report
2016 .clone()
2017 .filecommand
2018 .unwrap()
2019 .contains("Rich Text Format")
2020 );
2021 println!("Report: {report}");
2022
2023 assert!(
2024 db.get_sample_report(
2025 999,
2026 &HashType::try_from(test_rtf_meta.sha256.as_slice()).unwrap()
2027 )
2028 .await
2029 .is_err()
2030 );
2031
2032 #[cfg(feature = "vt")]
2033 {
2034 assert!(report.vt.is_some());
2035 let files_needing_vt = db
2036 .files_without_vt_records(10)
2037 .await
2038 .context("failed to get files without VT records")?;
2039 assert!(files_needing_vt.len() > 2);
2040 println!("{} files needing VT data: {files_needing_vt:?}", files_needing_vt.len());
2041 }
2042
2043 #[cfg(not(feature = "vt"))]
2044 {
2045 assert!(report.vt.is_none());
2046 }
2047
2048 let reset = db
2049 .reset_api_keys()
2050 .await
2051 .context("failed to reset all API keys")?;
2052 eprintln!("Cleared {reset} api keys.");
2053
2054 let db_info = db.db_info().await.context("failed to get database info")?;
2055 eprintln!("DB Info: {db_info:?}");
2056
2057 let data_types = db
2058 .get_known_data_types()
2059 .await
2060 .context("failed to get data types")?;
2061 for data_type in data_types {
2062 println!("{data_type:?}");
2063 }
2064
2065 let sources = db
2066 .list_sources()
2067 .await
2068 .context("failed to list sources second time")?;
2069 eprintln!("DB has {} sources:", sources.len());
2070 for source in sources {
2071 println!("{source}");
2072 }
2073
2074 let file_types_counts = db
2075 .file_types_counts()
2076 .await
2077 .context("failed to get file types and counts")?;
2078 for (name, count) in file_types_counts {
2079 println!("{name}: {count}");
2080 assert_ne!(name, "Mach-O", "No Mach-O files have been inserted yet!");
2081 }
2082
2083 let fatmacho =
2084 include_bytes!("../../../types/testdata/macho/macho_fat_arm64_ppc_ppc64_x86_64")
2085 .to_vec();
2086 let fatmacho_meta = FileMetadata::new(&fatmacho, Some("macho_fat_arm64_ppc_ppc64_x86_64"));
2087 let fatmacho_type = db
2088 .get_type_id_for_bytes(&fatmacho)
2089 .await
2090 .context("failed to get file type for Fat Mach-O")?;
2091 let known_type = KnownType::new(&fatmacho)
2092 .context("failed to parse Fat Mach-O from type crate's test data")?;
2093
2094 assert!(
2095 db.add_file(&fatmacho_meta, known_type, 1, default_source_id, fatmacho_type, None)
2096 .await
2097 .context("failed to insert a test Fat Mach-O")?
2098 .is_new
2099 );
2100 eprintln!("Added Fat Mach-O to the DB");
2101
2102 let file_types_counts = db
2103 .file_types_counts()
2104 .await
2105 .context("failed to get file types and counts")?;
2106 for (name, count) in &file_types_counts {
2107 println!("{name}: {count}");
2108 }
2109
2110 assert_eq!(
2111 *file_types_counts.get("Mach-O").unwrap(),
2112 4,
2113 "Expected 4 Mach-O files, got {:?}",
2114 file_types_counts.get("Mach-O")
2115 );
2116
2117 let allowed_files = db
2118 .user_allowed_files_by_sha256(1, None)
2119 .await
2120 .context("failed to get allowed files")?;
2121 assert_eq!(allowed_files.0.len(), 8);
2122
2123 let allowed_files = db
2124 .user_allowed_files_by_sha256(1, Some(allowed_files.1))
2125 .await
2126 .context("failed to get allowed files")?;
2127 assert!(allowed_files.0.is_empty());
2128
2129 let malware_label_id = db
2130 .create_label(MALWARE_LABEL, None)
2131 .await
2132 .context("failed to create first label")?;
2133 let ransomware_label_id = db
2134 .create_label(RANSOMWARE_LABEL, Some(malware_label_id))
2135 .await
2136 .context("failed to create malware sub-label")?;
2137 let labels = db.get_labels().await.context("failed to get labels")?;
2138
2139 assert_eq!(labels.len(), 4, "Expected 4 labels, got {labels}");
2140 for label in labels.0 {
2141 if label.name == RANSOMWARE_LABEL {
2142 assert_eq!(label.id, ransomware_label_id);
2143 assert_eq!(label.parent.unwrap(), MALWARE_LABEL);
2144 }
2145 }
2146
2147 let source_code = include_bytes!("mod.rs");
2149 let source_meta = FileMetadata::new(source_code, Some("mod.rs"));
2150 let known_type =
2151 KnownType::new(source_code).context("failed to source code to get `Unknown` type")?;
2152
2153 assert!(matches!(known_type, KnownType::Unknown(_)));
2154
2155 let unknown_type: Vec<FileType> = db
2156 .get_known_data_types()
2157 .await?
2158 .into_iter()
2159 .filter(|t| t.name.eq_ignore_ascii_case("unknown"))
2160 .collect();
2161 let unknown_type_id = unknown_type.first().unwrap().id;
2162 assert!(db.get_type_id_for_bytes(source_code).await.is_err());
2163 db.enable_keep_unknown_files()
2164 .await
2165 .context("failed to enable keeping of unknown files")?;
2166 let source_type = db
2167 .get_type_id_for_bytes(source_code)
2168 .await
2169 .context("failed to type id for source code unknown type example")?;
2170 assert_eq!(source_type, unknown_type_id);
2171 eprintln!("Unknown file type ID: {source_type}");
2172 assert!(
2173 db.add_file(&source_meta, known_type, 1, default_source_id, unknown_type_id, None)
2174 .await
2175 .context("failed to add Rust source code file")?
2176 .is_new
2177 );
2178 eprintln!("Added Rust source code to the DB");
2179
2180 #[cfg(feature = "yara")]
2181 assert!(db.get_unfinished_yara_tasks().await?.is_empty());
2182
2183 db.reset_own_api_key(0)
2184 .await
2185 .context("failed to clear own API key uid 0")?;
2186
2187 db.deactivate_user(0)
2188 .await
2189 .context("failed to clear password and API key for uid 0")?;
2190
2191 Ok(())
2192 }
2193}