Skip to main content

malwaredb_server/db/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Postgres is the database used by MalwareDB.
4//! However, SQLite will be used for unit testing or for small instances of MalwareDB. This option
5//! can be allowed by using the `sqlite` feature flag. When using SQLite, MalwareDB will calculate
6//! the distances for the similarity hashes.
7
8/// Malware DB Administrative functions
9#[cfg(any(test, feature = "admin"))]
10pub mod admin;
11/// Postgres functions
12mod pg;
13
14/// `SQLite` functionality
15#[cfg(any(test, feature = "sqlite"))]
16mod sqlite;
17
18/// Custom `SQLite` functions
19#[cfg(any(test, feature = "sqlite"))]
20mod sqlite_functions;
21
22/// File Metadata convenience data structure
23pub 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
48/// The maximum amount of partial hash and/or partial file name search results to prevent performance issues
49pub const PARTIAL_SEARCH_LIMIT: u32 = 100;
50
51/// Migration action
52#[derive(Copy, Clone)]
53pub enum Migration {
54    /// At run: check if a migration is needed
55    Check,
56
57    /// Admin feature: do the migration
58    #[cfg(any(test, feature = "admin"))]
59    Migrate,
60}
61
62/// Database connection handle
63#[derive(Debug)]
64pub enum DatabaseType {
65    /// Postgres database
66    Postgres(Postgres),
67
68    /// `SQLite` database
69    #[cfg(any(test, feature = "sqlite"))]
70    SQLite(Sqlite),
71}
72
73/// Version information and basic stats for the database
74#[derive(Debug)]
75pub struct DatabaseInformation {
76    /// Version string of the database
77    pub version: String,
78
79    /// Human-readable database size
80    pub size: String,
81
82    /// Number of file samples in Malware DB
83    pub num_files: u64,
84
85    /// Number of user accounts
86    pub num_users: u32,
87
88    /// Number of user groups
89    pub num_groups: u32,
90
91    /// Number of sample sources
92    pub num_sources: u32,
93}
94
95/// Data returned when adding a new sample
96pub struct FileAddedResult {
97    /// File ID
98    pub file_id: u64,
99
100    /// Whether the file was added as a new entry.
101    /// This is false if the sample was already known to Malware DB.
102    pub is_new: bool,
103}
104
105/// Malware DB configuration which is stored in the database
106#[derive(Debug)]
107pub struct MDBConfig {
108    /// The name of this instance of Malware DB
109    pub name: String,
110
111    /// Whether samples are stored compressed
112    pub compression: bool,
113
114    /// Whether Malware DB can send samples to Virus Total
115    pub send_samples_to_vt: bool,
116
117    /// If Malware DB should keep unknown files
118    pub keep_unknown_files: bool,
119
120    /// If samples are to be encrypted, which key?
121    pub(crate) default_key: Option<u32>,
122}
123
124/// VT record information for files in Malware DB
125#[cfg_attr(docsrs, doc(cfg(feature = "vt")))]
126#[cfg(feature = "vt")]
127#[derive(Debug, Clone, Copy)]
128pub struct VtStats {
129    /// Files marked as clean
130    pub clean_records: u32,
131
132    /// Files marked as malicious
133    pub hits_records: u32,
134
135    /// Files without VT records
136    pub files_without_records: u32,
137}
138
139impl DatabaseType {
140    /// Get a database connection from a configuration string
141    ///
142    /// # Errors
143    ///
144    /// * If there's a connectivity issue to Postgres, an error will result
145    /// * If the `SQLite` file cannot be created or opened, an error will result
146    /// * If `SQLite` is the type but Malware DB wasn't compiled with the sqlite feature, an error will result
147    /// * If the format or database type isn't known, an error will result
148    pub async fn from_string(arg: &str, server_ca: Option<PathBuf>) -> Result<Self> {
149        let db = Self::init_from_string(arg, server_ca).await?;
150        db.migrate_check(Migration::Check).await?;
151        Ok(db)
152    }
153
154    /// Get a database connection from a configuration string and perform a migration, if needed
155    ///
156    /// # Errors
157    ///
158    /// * If there's a connectivity issue to Postgres, an error will result
159    /// * If the `SQLite` file cannot be created or opened, an error will result
160    /// * If `SQLite` is the type but Malware DB wasn't compiled with the sqlite feature, an error will result
161    /// * If the format or database type isn't known, an error will result
162    #[cfg(feature = "admin")]
163    pub async fn migrate(arg: &str, server_ca: Option<PathBuf>) -> Result<Self> {
164        let db = Self::init_from_string(arg, server_ca).await?;
165        db.migrate_check(Migration::Migrate).await?;
166        Ok(db)
167    }
168
169    async fn init_from_string(arg: &str, server_ca: Option<PathBuf>) -> Result<Self> {
170        #[cfg(any(test, feature = "sqlite"))]
171        if arg.starts_with("file:") {
172            let new_conn_str = arg.trim_start_matches("file:");
173            let db = DatabaseType::SQLite(Sqlite::new(new_conn_str)?);
174            return Ok(db);
175        }
176
177        if arg.starts_with("postgres") {
178            let new_conn_str = arg.trim_start_matches("postgres");
179            let db = DatabaseType::Postgres(Postgres::new(new_conn_str, server_ca).await?);
180
181            return Ok(db);
182        }
183
184        bail!("unknown database type `{arg}`")
185    }
186
187    /// Set the flag allowing uploads to Virus Total.
188    ///
189    /// # Errors
190    ///
191    /// If there's a connectivity issue to Postgres, an error will result
192    #[cfg_attr(docsrs, doc(cfg(feature = "vt")))]
193    #[cfg(feature = "vt")]
194    pub async fn enable_vt_upload(&self) -> Result<()> {
195        match self {
196            DatabaseType::Postgres(pg) => pg.enable_vt_upload().await,
197            #[cfg(any(test, feature = "sqlite"))]
198            DatabaseType::SQLite(sl) => sl.enable_vt_upload(),
199        }
200    }
201
202    /// Set the flag preventing uploads to Virus Total.
203    ///
204    /// # Errors
205    ///
206    /// If there's a connectivity issue to Postgres, an error will result
207    #[cfg_attr(docsrs, doc(cfg(feature = "vt")))]
208    #[cfg(feature = "vt")]
209    pub async fn disable_vt_upload(&self) -> Result<()> {
210        match self {
211            DatabaseType::Postgres(pg) => pg.disable_vt_upload().await,
212            #[cfg(any(test, feature = "sqlite"))]
213            DatabaseType::SQLite(sl) => sl.disable_vt_upload(),
214        }
215    }
216
217    /// Get the SHA-256 hashes of the files which don't have VT records
218    ///
219    /// # Errors
220    ///
221    /// If there's a connectivity issue to Postgres, an error will result
222    #[cfg_attr(docsrs, doc(cfg(feature = "vt")))]
223    #[cfg(feature = "vt")]
224    pub async fn files_without_vt_records(&self, limit: u32) -> Result<Vec<String>> {
225        match self {
226            DatabaseType::Postgres(pg) => pg.files_without_vt_records(limit).await,
227            #[cfg(any(test, feature = "sqlite"))]
228            DatabaseType::SQLite(sl) => sl.files_without_vt_records(limit),
229        }
230    }
231
232    /// Store the VT results: AV hits and detailed report, or lack of any AV hits
233    ///
234    /// # Errors
235    ///
236    /// If there's a connectivity issue to Postgres, an error will result
237    #[cfg_attr(docsrs, doc(cfg(feature = "vt")))]
238    #[cfg(feature = "vt")]
239    pub async fn store_vt_record(&self, results: &ScanResultAttributes) -> Result<()> {
240        match self {
241            DatabaseType::Postgres(pg) => pg.store_vt_record(results).await,
242            #[cfg(any(test, feature = "sqlite"))]
243            DatabaseType::SQLite(sl) => sl.store_vt_record(results),
244        }
245    }
246
247    /// Quick statistics regarding the data contained for VT information for our samples
248    ///
249    /// # Errors
250    ///
251    /// If there's a connectivity issue to Postgres, an error will result
252    #[cfg_attr(docsrs, doc(cfg(feature = "vt")))]
253    #[cfg(feature = "vt")]
254    pub async fn get_vt_stats(&self) -> Result<VtStats> {
255        match self {
256            DatabaseType::Postgres(pg) => pg.get_vt_stats().await,
257            #[cfg(any(test, feature = "sqlite"))]
258            DatabaseType::SQLite(sl) => sl.get_vt_stats(),
259        }
260    }
261
262    /// Add the Yara search to the database
263    ///
264    /// # Errors
265    ///
266    /// If there's a connectivity issue to Postgres, an error will result
267    #[cfg_attr(docsrs, doc(cfg(feature = "yara")))]
268    #[cfg(feature = "yara")]
269    pub async fn add_yara_search(
270        &self,
271        uid: u32,
272        yara_string: &str,
273        yara_bytes: &[u8],
274    ) -> Result<uuid::Uuid> {
275        match self {
276            DatabaseType::Postgres(pg) => pg.add_yara_search(uid, yara_string, yara_bytes).await,
277            #[cfg(any(test, feature = "sqlite"))]
278            DatabaseType::SQLite(sl) => sl.add_yara_search(uid, yara_string, yara_bytes),
279        }
280    }
281
282    /// Get unfinished Yara tasks for processing.
283    ///
284    /// # Errors
285    ///
286    /// If there's a connectivity issue to Postgres, an error will result
287    #[cfg_attr(docsrs, doc(cfg(feature = "yara")))]
288    #[cfg(feature = "yara")]
289    pub async fn get_unfinished_yara_tasks(&self) -> Result<Vec<crate::yara::YaraTask>> {
290        match self {
291            DatabaseType::Postgres(pg) => pg.get_unfinished_yara_tasks().await,
292            #[cfg(any(test, feature = "sqlite"))]
293            DatabaseType::SQLite(sl) => sl.get_unfinished_yara_tasks(),
294        }
295    }
296
297    /// Add a Yara match to the database
298    ///
299    /// # Errors
300    ///
301    /// If there's a connectivity issue to Postgres, an error will result
302    #[cfg_attr(docsrs, doc(cfg(feature = "yara")))]
303    #[cfg(feature = "yara")]
304    pub async fn add_yara_match(
305        &self,
306        id: uuid::Uuid,
307        rule_name: &str,
308        file_sha256: &str,
309    ) -> Result<()> {
310        match self {
311            DatabaseType::Postgres(pg) => pg.add_yara_match(id, rule_name, file_sha256).await,
312            #[cfg(any(test, feature = "sqlite"))]
313            DatabaseType::SQLite(sl) => sl.add_yara_match(id, rule_name, file_sha256),
314        }
315    }
316
317    /// Indicate that the Yara search task has finished
318    ///
319    /// # Errors
320    ///
321    /// If there's a connectivity issue to Postgres, an error will result
322    #[cfg_attr(docsrs, doc(cfg(feature = "yara")))]
323    #[cfg(feature = "yara")]
324    pub async fn mark_yara_task_as_finished(&self, id: uuid::Uuid) -> Result<()> {
325        match self {
326            DatabaseType::Postgres(pg) => pg.mark_yara_task_as_finished(id).await,
327            #[cfg(any(test, feature = "sqlite"))]
328            DatabaseType::SQLite(sl) => sl.mark_yara_task_as_finished(id),
329        }
330    }
331
332    /// Add the last file ID for the next iteration
333    ///
334    /// # Errors
335    ///
336    /// If there's a connectivity issue to Postgres, an error will result
337    #[cfg_attr(docsrs, doc(cfg(feature = "yara")))]
338    #[cfg(feature = "yara")]
339    pub async fn yara_add_next_file_id(&self, id: uuid::Uuid, file_id: u64) -> Result<()> {
340        match self {
341            DatabaseType::Postgres(pg) => pg.yara_add_next_file_id(id, file_id).await,
342            #[cfg(any(test, feature = "sqlite"))]
343            DatabaseType::SQLite(sl) => sl.yara_add_next_file_id(id, file_id),
344        }
345    }
346
347    /// Get the Yara search results
348    ///
349    /// # Errors
350    ///
351    /// If there's a connectivity issue to Postgres, an error will result
352    #[cfg_attr(docsrs, doc(cfg(feature = "yara")))]
353    #[cfg(feature = "yara")]
354    pub async fn get_yara_results(
355        &self,
356        id: uuid::Uuid,
357        user_id: u32,
358    ) -> Result<malwaredb_api::YaraSearchResponse> {
359        match self {
360            DatabaseType::Postgres(pg) => pg.get_yara_results(id, user_id).await,
361            #[cfg(any(test, feature = "sqlite"))]
362            DatabaseType::SQLite(sl) => sl.get_yara_results(id, user_id),
363        }
364    }
365
366    /// Get the configuration which is stored in the database
367    ///
368    /// # Errors
369    ///
370    /// If there's a connectivity issue to Postgres, an error will result
371    pub async fn get_config(&self) -> Result<MDBConfig> {
372        match self {
373            DatabaseType::Postgres(pg) => pg.get_config().await,
374            #[cfg(any(test, feature = "sqlite"))]
375            DatabaseType::SQLite(sl) => sl.get_config(),
376        }
377    }
378
379    /// Check user credentials, return the API key. Generate if it doesn't exist.
380    ///
381    /// # Errors
382    ///
383    /// * If there's a connectivity issue to Postgres, an error will result
384    /// * If the username and/or password aren't correct, an error will result
385    pub async fn authenticate(&self, uname: &str, password: &str) -> Result<String> {
386        match self {
387            DatabaseType::Postgres(pg) => pg.authenticate(uname, password).await,
388            #[cfg(any(test, feature = "sqlite"))]
389            DatabaseType::SQLite(sl) => sl.authenticate(uname, password),
390        }
391    }
392
393    /// Get the user's ID from their API key
394    ///
395    /// # Errors
396    ///
397    /// * If there's a connectivity issue to Postgres, an error will result
398    /// * If the api key isn't valid, an error will result
399    pub async fn get_uid(&self, apikey: &str) -> Result<u32> {
400        ensure!(!apikey.is_empty(), "API key was empty");
401        match self {
402            DatabaseType::Postgres(pg) => pg.get_uid(apikey).await,
403            #[cfg(any(test, feature = "sqlite"))]
404            DatabaseType::SQLite(sl) => sl.get_uid(apikey),
405        }
406    }
407
408    /// Retrieve information about the database
409    ///
410    /// # Errors
411    ///
412    /// * If there's a connectivity issue to Postgres, an error will result
413    pub async fn db_info(&self) -> Result<DatabaseInformation> {
414        match self {
415            DatabaseType::Postgres(pg) => pg.db_info().await,
416            #[cfg(any(test, feature = "sqlite"))]
417            DatabaseType::SQLite(sl) => sl.db_info(),
418        }
419    }
420
421    /// Retrieve the names of the groups and sources the user is part of and has access to
422    ///
423    /// # Errors
424    ///
425    /// * If there's a connectivity issue to Postgres, an error will result
426    /// * If the user ID isn't valid, an error will result
427    pub async fn get_user_info(&self, uid: u32) -> Result<GetUserInfoResponse> {
428        match self {
429            DatabaseType::Postgres(pg) => pg.get_user_info(uid).await,
430            #[cfg(any(test, feature = "sqlite"))]
431            DatabaseType::SQLite(sl) => sl.get_user_info(uid),
432        }
433    }
434
435    /// Retrieve the source information available to the specified user
436    ///
437    /// # Errors
438    ///
439    /// * If there's a connectivity issue to Postgres, an error will result
440    /// * If the user ID isn't valid, an error will result
441    pub async fn get_user_sources(&self, uid: u32) -> Result<Sources> {
442        match self {
443            DatabaseType::Postgres(pg) => pg.get_user_sources(uid).await,
444            #[cfg(any(test, feature = "sqlite"))]
445            DatabaseType::SQLite(sl) => sl.get_user_sources(uid),
446        }
447    }
448
449    /// Let the user clear their own API key to log out from all systems
450    ///
451    /// # Errors
452    ///
453    /// * If there's a connectivity issue to Postgres, an error will result
454    /// * If the user ID isn't valid, an error will result
455    pub async fn reset_own_api_key(&self, uid: u32) -> Result<()> {
456        match self {
457            DatabaseType::Postgres(pg) => pg.reset_own_api_key(uid).await,
458            #[cfg(any(test, feature = "sqlite"))]
459            DatabaseType::SQLite(sl) => sl.reset_own_api_key(uid),
460        }
461    }
462
463    /// Retrieve the supported data type information
464    ///
465    /// # Errors
466    ///
467    /// If there's a connectivity issue to Postgres, an error will result
468    pub async fn get_known_data_types(&self) -> Result<Vec<FileType>> {
469        match self {
470            DatabaseType::Postgres(pg) => pg.get_known_data_types().await,
471            #[cfg(any(test, feature = "sqlite"))]
472            DatabaseType::SQLite(sl) => sl.get_known_data_types(),
473        }
474    }
475
476    /// Get all labels from Malware DB
477    ///
478    /// # Errors
479    ///
480    /// If there's a connectivity issue to Postgres, an error will result
481    pub async fn get_labels(&self) -> Result<Labels> {
482        match self {
483            DatabaseType::Postgres(pg) => pg.get_labels().await,
484            #[cfg(any(test, feature = "sqlite"))]
485            DatabaseType::SQLite(sl) => sl.get_labels(),
486        }
487    }
488
489    /// Get the corresponding type ID for a buffer representing a file
490    ///
491    /// # Errors
492    ///
493    /// If there's a connectivity issue to Postgres, an error will result
494    pub async fn get_type_id_for_bytes(&self, data: &[u8]) -> Result<u32> {
495        match self {
496            DatabaseType::Postgres(pg) => pg.get_type_id_for_bytes(data).await,
497            #[cfg(any(test, feature = "sqlite"))]
498            DatabaseType::SQLite(sl) => sl.get_type_id_for_bytes(data),
499        }
500    }
501
502    /// Check that a user has been granted access data for the specific source
503    ///
504    /// # Errors
505    ///
506    /// * If there's a connectivity issue to Postgres, an error will result
507    /// * If the user or source ID(s) aren't valid, an error will result
508    pub async fn allowed_user_source(&self, uid: u32, sid: u32) -> Result<bool> {
509        match self {
510            DatabaseType::Postgres(pg) => pg.allowed_user_source(uid, sid).await,
511            #[cfg(any(test, feature = "sqlite"))]
512            DatabaseType::SQLite(sl) => sl.allowed_user_source(uid, sid),
513        }
514    }
515
516    /// Check to see if the user is an administrator. The user must be a member of the
517    /// admin group (group ID 0), or a one group below (a group with the parent group id of 0).
518    ///
519    /// # Errors
520    ///
521    /// * If there's a connectivity issue to Postgres, an error will result
522    /// * If the user ID isn't valid, an error will result
523    pub async fn user_is_admin(&self, uid: u32) -> Result<bool> {
524        match self {
525            DatabaseType::Postgres(pg) => pg.user_is_admin(uid).await,
526            #[cfg(any(test, feature = "sqlite"))]
527            DatabaseType::SQLite(sl) => sl.user_is_admin(uid),
528        }
529    }
530
531    /// Add a file's metadata to the database, returning true if this is a new entry
532    ///
533    /// # Errors
534    ///
535    /// * If there's a connectivity issue to Postgres, an error will result
536    /// * If the source doesn't exist or the user isn't part of a member group, an error will result
537    pub async fn add_file(
538        &self,
539        meta: &FileMetadata,
540        known_type: KnownType<'_>,
541        uid: u32,
542        sid: u32,
543        ftype: u32,
544        parent: Option<u64>,
545    ) -> Result<FileAddedResult> {
546        match self {
547            DatabaseType::Postgres(pg) => {
548                pg.add_file(meta, known_type, uid, sid, ftype, parent).await
549            }
550            #[cfg(any(test, feature = "sqlite"))]
551            DatabaseType::SQLite(sl) => sl.add_file(meta, &known_type, uid, sid, ftype, parent),
552        }
553    }
554
555    /// Search for allowed samples based on partial search and/or file name
556    ///
557    /// # Errors
558    ///
559    /// * If there's a connectivity issue to Postgres, an error will result
560    pub async fn partial_search(&self, uid: u32, search: SearchRequest) -> Result<SearchResponse> {
561        match self {
562            DatabaseType::Postgres(pg) => pg.partial_search(uid, search).await,
563            #[cfg(any(test, feature = "sqlite"))]
564            DatabaseType::SQLite(sl) => sl.partial_search(uid, search),
565        }
566    }
567
568    /// Delete old pagination searches
569    ///
570    /// # Errors
571    ///
572    /// An error would occur if the Postgres server couldn't be reached.
573    pub async fn cleanup(&self) -> Result<u64> {
574        match self {
575            DatabaseType::Postgres(pg) => pg.cleanup().await,
576            #[cfg(any(test, feature = "sqlite"))]
577            DatabaseType::SQLite(sl) => sl.cleanup(),
578        }
579    }
580
581    /// Retrieve the SHA-256 hash of the sample while checking that the user is permitted
582    /// to access to it
583    ///
584    /// # Errors
585    ///
586    /// * If there's a connectivity issue to Postgres, an error will result
587    /// * If the file doesn't exist or the user isn't allowed access, an error will result
588    pub async fn retrieve_sample(&self, uid: u32, hash: &HashType) -> Result<String> {
589        match self {
590            DatabaseType::Postgres(pg) => pg.retrieve_sample(uid, hash).await,
591            #[cfg(any(test, feature = "sqlite"))]
592            DatabaseType::SQLite(sl) => sl.retrieve_sample(uid, hash),
593        }
594    }
595
596    /// Retrieve a report for a given sample, if allowed.
597    ///
598    /// # Errors
599    ///
600    /// * If there's a connectivity issue to Postgres, an error will result
601    /// * If the file doesn't exist or the user isn't allowed access, an error will result
602    pub async fn get_sample_report(
603        &self,
604        uid: u32,
605        hash: &HashType,
606    ) -> Result<malwaredb_api::Report> {
607        match self {
608            DatabaseType::Postgres(pg) => pg.get_sample_report(uid, hash).await,
609            #[cfg(any(test, feature = "sqlite"))]
610            DatabaseType::SQLite(sl) => sl.get_sample_report(uid, hash),
611        }
612    }
613
614    /// Given a collection of similarity hashes, find samples which are similar.
615    ///
616    /// # Errors
617    ///
618    /// If there's a connectivity issue to Postgres, an error will result
619    pub async fn find_similar_samples(
620        &self,
621        uid: u32,
622        sim: &[(malwaredb_api::SimilarityHashType, String)],
623    ) -> Result<Vec<malwaredb_api::SimilarSample>> {
624        match self {
625            DatabaseType::Postgres(pg) => pg.find_similar_samples(uid, sim).await,
626            #[cfg(any(test, feature = "sqlite"))]
627            DatabaseType::SQLite(sl) => sl.find_similar_samples(uid, sim),
628        }
629    }
630
631    /// For a given user ID, return the file hashes the person is allowed to know about and the last
632    /// file ID, which can be provided as `next` to get the next batch of hashes.
633    ///
634    /// # Errors
635    ///
636    /// If there's a connectivity issue to Postgres, an error will result
637    pub async fn user_allowed_files_by_sha256(
638        &self,
639        uid: u32,
640        next: Option<u64>,
641    ) -> Result<(Vec<String>, u64)> {
642        match self {
643            DatabaseType::Postgres(pg) => pg.user_allowed_files_by_sha256(uid, next).await,
644            #[cfg(any(test, feature = "sqlite"))]
645            DatabaseType::SQLite(sl) => sl.user_allowed_files_by_sha256(uid, next),
646        }
647    }
648
649    // Private functions
650
651    /// Get the file encryption keys
652    pub(crate) async fn get_encryption_keys(&self) -> Result<HashMap<u32, FileEncryption>> {
653        match self {
654            DatabaseType::Postgres(pg) => pg.get_encryption_keys().await,
655            #[cfg(any(test, feature = "sqlite"))]
656            DatabaseType::SQLite(sl) => sl.get_encryption_keys(),
657        }
658    }
659
660    /// Get the key and AES nonce for a specific file identified by SHA-256 hash
661    pub(crate) async fn get_file_encryption_key_id(
662        &self,
663        hash: &str,
664    ) -> Result<(Option<u32>, Option<Vec<u8>>)> {
665        match self {
666            DatabaseType::Postgres(pg) => pg.get_file_encryption_key_id(hash).await,
667            #[cfg(any(test, feature = "sqlite"))]
668            DatabaseType::SQLite(sl) => sl.get_file_encryption_key_id(hash),
669        }
670    }
671
672    /// Set the AES nonce for a file specified by SHA-256 hash, a `None` value removes it.
673    pub(crate) async fn set_file_nonce(&self, hash: &str, nonce: Option<&[u8]>) -> Result<()> {
674        match self {
675            DatabaseType::Postgres(pg) => pg.set_file_nonce(hash, nonce).await,
676            #[cfg(any(test, feature = "sqlite"))]
677            DatabaseType::SQLite(sl) => sl.set_file_nonce(hash, nonce),
678        }
679    }
680
681    /// If the config changes to remove the encryption and the rewrite function is called, clear
682    /// the crypto.
683    pub(crate) async fn clear_file_crypto(&self, hash: &str) -> Result<()> {
684        match self {
685            DatabaseType::Postgres(pg) => pg.clear_file_crypto(hash).await,
686            #[cfg(any(test, feature = "sqlite"))]
687            DatabaseType::SQLite(sl) => sl.clear_file_crypto(hash),
688        }
689    }
690
691    /// Checks if a migration is needed, erroring if action is to check and the schema has changed.
692    ///
693    /// # Errors
694    ///
695    /// If there's a connectivity issue to Postgres, an error will result; if a migration is needed
696    /// and not an administrative action, an error results.
697    pub async fn migrate_check(&self, action: Migration) -> Result<()> {
698        match self {
699            DatabaseType::Postgres(pg) => pg.migrate(action).await,
700            #[cfg(any(test, feature = "sqlite"))]
701            DatabaseType::SQLite(sl) => sl.migrate(action),
702        }
703    }
704
705    // Administrative functions
706
707    /// Set the instance name
708    ///
709    /// # Errors
710    ///
711    /// If there's a connectivity issue to Postgres, an error will result
712    #[cfg(any(test, feature = "admin"))]
713    pub async fn set_name(&self, name: &str) -> Result<()> {
714        match self {
715            DatabaseType::Postgres(pg) => pg.set_name(name).await,
716            #[cfg(any(test, feature = "sqlite"))]
717            DatabaseType::SQLite(sl) => sl.set_name(name),
718        }
719    }
720
721    /// Set the compression flag
722    ///
723    /// # Errors
724    ///
725    /// If there's a connectivity issue to Postgres, an error will result
726    #[cfg(any(test, feature = "admin"))]
727    pub async fn enable_compression(&self) -> Result<()> {
728        match self {
729            DatabaseType::Postgres(pg) => pg.enable_compression().await,
730            #[cfg(any(test, feature = "sqlite"))]
731            DatabaseType::SQLite(sl) => sl.enable_compression(),
732        }
733    }
734
735    /// Unset the compression flag, does not go and decompress files already compressed!
736    ///
737    /// # Errors
738    ///
739    /// If there's a connectivity issue to Postgres, an error will result
740    #[cfg(any(test, feature = "admin"))]
741    pub async fn disable_compression(&self) -> Result<()> {
742        match self {
743            DatabaseType::Postgres(pg) => pg.disable_compression().await,
744            #[cfg(any(test, feature = "sqlite"))]
745            DatabaseType::SQLite(sl) => sl.disable_compression(),
746        }
747    }
748
749    /// Set the keep unknown files flag
750    ///
751    /// # Errors
752    ///
753    /// If there's a connectivity issue to Postgres, an error will result
754    #[cfg(any(test, feature = "admin"))]
755    pub async fn enable_keep_unknown_files(&self) -> Result<()> {
756        match self {
757            DatabaseType::Postgres(pg) => pg.enable_keep_unknown_files().await,
758            #[cfg(any(test, feature = "sqlite"))]
759            DatabaseType::SQLite(sl) => sl.enable_keep_unknown_files(),
760        }
761    }
762
763    /// Unset the keep unknown files flag, does not go and remove unknown files!
764    ///
765    /// # Errors
766    ///
767    /// If there's a connectivity issue to Postgres, an error will result
768    #[cfg(any(test, feature = "admin"))]
769    pub async fn disable_keep_unknown_files(&self) -> Result<()> {
770        match self {
771            DatabaseType::Postgres(pg) => pg.disable_keep_unknown_files().await,
772            #[cfg(any(test, feature = "sqlite"))]
773            DatabaseType::SQLite(sl) => sl.disable_keep_unknown_files(),
774        }
775    }
776
777    /// Add an encryption key to the database, set it as the default, and return the key ID
778    ///
779    /// # Errors
780    ///
781    /// * If there is a connectivity with Postgres, an error will result
782    #[cfg(any(test, feature = "admin"))]
783    pub async fn add_file_encryption_key(&self, key: &FileEncryption) -> Result<u32> {
784        match self {
785            DatabaseType::Postgres(pg) => pg.add_file_encryption_key(key).await,
786            #[cfg(any(test, feature = "sqlite"))]
787            DatabaseType::SQLite(sl) => sl.add_file_encryption_key(key),
788        }
789    }
790
791    /// Get the key ID and algorithm names
792    ///
793    /// # Errors
794    ///
795    /// If there is a connectivity with Postgres, an error will result
796    #[cfg(any(test, feature = "admin"))]
797    pub async fn get_encryption_key_names_ids(&self) -> Result<Vec<(u32, EncryptionOption)>> {
798        match self {
799            DatabaseType::Postgres(pg) => pg.get_encryption_key_names_ids().await,
800            #[cfg(any(test, feature = "sqlite"))]
801            DatabaseType::SQLite(sl) => sl.get_encryption_key_names_ids(),
802        }
803    }
804
805    /// Create a user account, return the user ID.
806    ///
807    /// # Errors
808    ///
809    /// If there's a connectivity issue to Postgres, an error will result
810    #[allow(clippy::too_many_arguments)]
811    #[cfg(any(test, feature = "admin"))]
812    pub async fn create_user(
813        &self,
814        uname: &str,
815        fname: &str,
816        lname: &str,
817        email: &str,
818        password: Option<String>,
819        organisation: Option<&String>,
820        readonly: bool,
821    ) -> Result<u32> {
822        match self {
823            DatabaseType::Postgres(pg) => {
824                pg.create_user(uname, fname, lname, email, password, organisation, readonly)
825                    .await
826            }
827            #[cfg(any(test, feature = "sqlite"))]
828            DatabaseType::SQLite(sl) => {
829                sl.create_user(uname, fname, lname, email, password, organisation, readonly)
830            }
831        }
832    }
833
834    /// Clear all API keys, either in case of suspected activity, or part of policy
835    ///
836    /// # Errors
837    ///
838    /// If there's a connectivity issue to Postgres, an error will result
839    #[cfg(any(test, feature = "admin"))]
840    pub async fn reset_api_keys(&self) -> Result<u64> {
841        match self {
842            DatabaseType::Postgres(pg) => pg.reset_api_keys().await,
843            #[cfg(any(test, feature = "sqlite"))]
844            DatabaseType::SQLite(sl) => sl.reset_api_keys(),
845        }
846    }
847
848    /// Set a user's password
849    ///
850    /// # Errors
851    ///
852    /// * If there's a connectivity issue to Postgres, an error will result
853    /// * If the user doesn't exist, an error will result
854    #[cfg(any(test, feature = "admin"))]
855    pub async fn set_password(&self, uname: &str, password: &str) -> Result<()> {
856        match self {
857            DatabaseType::Postgres(pg) => pg.set_password(uname, password).await,
858            #[cfg(any(test, feature = "sqlite"))]
859            DatabaseType::SQLite(sl) => sl.set_password(uname, password),
860        }
861    }
862
863    /// Get the complete list of users
864    ///
865    /// # Errors
866    ///
867    /// If there's a connectivity issue to Postgres, an error will result
868    #[cfg(any(test, feature = "admin"))]
869    pub async fn list_users(&self) -> Result<Vec<admin::User>> {
870        match self {
871            DatabaseType::Postgres(pg) => pg.list_users().await,
872            #[cfg(any(test, feature = "sqlite"))]
873            DatabaseType::SQLite(sl) => sl.list_users(),
874        }
875    }
876
877    /// Get the ID of a group from name
878    ///
879    /// # Errors
880    ///
881    /// * If there's a connectivity issue to Postgres, an error will result
882    /// * If the group name isn't valid, an error will result
883    #[cfg(any(test, feature = "admin"))]
884    pub async fn group_id_from_name(&self, name: &str) -> Result<i32> {
885        match self {
886            DatabaseType::Postgres(pg) => pg.group_id_from_name(name).await,
887            #[cfg(any(test, feature = "sqlite"))]
888            DatabaseType::SQLite(sl) => sl.group_id_from_name(name),
889        }
890    }
891
892    /// Update the record for a group
893    ///
894    /// # Errors
895    ///
896    /// * If there's a connectivity issue to Postgres, an error will result
897    /// * If the group doesn't exist, an error will result
898    #[cfg(any(test, feature = "admin"))]
899    pub async fn edit_group(
900        &self,
901        gid: u32,
902        name: &str,
903        desc: &str,
904        parent: Option<u32>,
905    ) -> Result<()> {
906        match self {
907            DatabaseType::Postgres(pg) => pg.edit_group(gid, name, desc, parent).await,
908            #[cfg(any(test, feature = "sqlite"))]
909            DatabaseType::SQLite(sl) => sl.edit_group(gid, name, desc, parent),
910        }
911    }
912
913    /// Get the complete list of groups
914    ///
915    /// # Errors
916    ///
917    /// If there's a connectivity issue to Postgres, an error will result
918    #[cfg(any(test, feature = "admin"))]
919    pub async fn list_groups(&self) -> Result<Vec<admin::Group>> {
920        match self {
921            DatabaseType::Postgres(pg) => pg.list_groups().await,
922            #[cfg(any(test, feature = "sqlite"))]
923            DatabaseType::SQLite(sl) => sl.list_groups(),
924        }
925    }
926
927    /// Grant a user membership to a group, both by id.
928    ///
929    /// # Errors
930    ///
931    /// * If there's a connectivity issue to Postgres, an error will result
932    /// * If the user or group doesn't exist, an error will result
933    #[cfg(any(test, feature = "admin"))]
934    pub async fn add_user_to_group(&self, uid: u32, gid: u32) -> Result<()> {
935        match self {
936            DatabaseType::Postgres(pg) => pg.add_user_to_group(uid, gid).await,
937            #[cfg(any(test, feature = "sqlite"))]
938            DatabaseType::SQLite(sl) => sl.add_user_to_group(uid, gid),
939        }
940    }
941
942    /// Grand a group access to a source, both by id.
943    ///
944    /// # Errors
945    ///
946    /// * If there's a connectivity issue to Postgres, an error will result
947    /// * If the group or source doesn't exist, an error will result
948    #[cfg(any(test, feature = "admin"))]
949    pub async fn add_group_to_source(&self, gid: u32, sid: u32) -> Result<()> {
950        match self {
951            DatabaseType::Postgres(pg) => pg.add_group_to_source(gid, sid).await,
952            #[cfg(any(test, feature = "sqlite"))]
953            DatabaseType::SQLite(sl) => sl.add_group_to_source(gid, sid),
954        }
955    }
956
957    /// Create a new group, returning the group ID
958    ///
959    /// # Errors
960    ///
961    /// * If there's a connectivity issue to Postgres, an error will result
962    /// * If the group name is already taken, an error will result
963    #[cfg(any(test, feature = "admin"))]
964    pub async fn create_group(
965        &self,
966        name: &str,
967        description: &str,
968        parent: Option<u32>,
969    ) -> Result<u32> {
970        match self {
971            DatabaseType::Postgres(pg) => pg.create_group(name, description, parent).await,
972            #[cfg(any(test, feature = "sqlite"))]
973            DatabaseType::SQLite(sl) => sl.create_group(name, description, parent),
974        }
975    }
976
977    /// Get the complete list of sources
978    ///
979    /// # Errors
980    ///
981    /// If there's a connectivity issue to Postgres, an error will result
982    #[cfg(any(test, feature = "admin"))]
983    pub async fn list_sources(&self) -> Result<Vec<admin::Source>> {
984        match self {
985            DatabaseType::Postgres(pg) => pg.list_sources().await,
986            #[cfg(any(test, feature = "sqlite"))]
987            DatabaseType::SQLite(sl) => sl.list_sources(),
988        }
989    }
990
991    /// Create a source, returning the source ID
992    ///
993    /// # Errors
994    ///
995    /// * If there's a connectivity issue to Postgres, an error will result
996    /// * If the source already exists, an error will result
997    #[cfg(any(test, feature = "admin"))]
998    pub async fn create_source(
999        &self,
1000        name: &str,
1001        description: Option<&str>,
1002        url: Option<&str>,
1003        date: chrono::DateTime<Local>,
1004        releasable: bool,
1005        malicious: Option<bool>,
1006    ) -> Result<u32> {
1007        match self {
1008            DatabaseType::Postgres(pg) => {
1009                pg.create_source(name, description, url, date, releasable, malicious)
1010                    .await
1011            }
1012            #[cfg(any(test, feature = "sqlite"))]
1013            DatabaseType::SQLite(sl) => {
1014                sl.create_source(name, description, url, date, releasable, malicious)
1015            }
1016        }
1017    }
1018
1019    /// Edit a user account setting the specified field values, primarily used by the Admin gui
1020    ///
1021    /// # Errors
1022    ///
1023    /// * If there's a connectivity issue to Postgres, an error will result
1024    /// * If the user isn't valid, an error will result
1025    #[cfg(any(test, feature = "admin"))]
1026    pub async fn edit_user(
1027        &self,
1028        uid: u32,
1029        uname: &str,
1030        fname: &str,
1031        lname: &str,
1032        email: &str,
1033        readonly: bool,
1034    ) -> Result<()> {
1035        match self {
1036            DatabaseType::Postgres(pg) => {
1037                pg.edit_user(uid, uname, fname, lname, email, readonly)
1038                    .await
1039            }
1040            #[cfg(any(test, feature = "sqlite"))]
1041            DatabaseType::SQLite(sl) => sl.edit_user(uid, uname, fname, lname, email, readonly),
1042        }
1043    }
1044
1045    /// Deactivate, but don't delete, a user's account
1046    ///
1047    /// # Errors
1048    ///
1049    /// * If there's a connectivity issue to Postgres, an error will result
1050    /// * If the user ID isn't valid, an error will result
1051    #[cfg(any(test, feature = "admin"))]
1052    pub async fn deactivate_user(&self, uid: u32) -> Result<()> {
1053        match self {
1054            DatabaseType::Postgres(pg) => pg.deactivate_user(uid).await,
1055            #[cfg(any(test, feature = "sqlite"))]
1056            DatabaseType::SQLite(sl) => sl.deactivate_user(uid),
1057        }
1058    }
1059
1060    /// File types and number of files per type
1061    ///
1062    /// # Errors
1063    ///
1064    /// * If there's a connectivity issue to Postgres, an error will result
1065    #[cfg(any(test, feature = "admin"))]
1066    pub async fn file_types_counts(&self) -> Result<HashMap<String, u32>> {
1067        match self {
1068            DatabaseType::Postgres(pg) => pg.file_types_counts().await,
1069            #[cfg(any(test, feature = "sqlite"))]
1070            DatabaseType::SQLite(sl) => sl.file_types_counts(),
1071        }
1072    }
1073
1074    /// Create a new label, returning the label ID
1075    ///
1076    /// # Errors
1077    ///
1078    /// * If there's a connectivity issue to Postgres, an error will result
1079    /// * If the parent label doesn't exist, an error will result
1080    /// * If the label name already exists, an error will result
1081    #[cfg(any(test, feature = "admin"))]
1082    pub async fn create_label(&self, name: &str, parent: Option<u64>) -> Result<u64> {
1083        match self {
1084            DatabaseType::Postgres(pg) => pg.create_label(name, parent).await,
1085            #[cfg(any(test, feature = "sqlite"))]
1086            DatabaseType::SQLite(sl) => sl.create_label(name, parent),
1087        }
1088    }
1089
1090    /// Edit a label name or parent
1091    ///
1092    /// # Errors
1093    ///
1094    /// * If there's a connectivity issue to Postgres, an error will result
1095    /// * If the parent label doesn't exist, an error will result
1096    /// * If the label name already exists, an error will result
1097    #[cfg(any(test, feature = "admin"))]
1098    pub async fn edit_label(&self, id: u64, name: &str, parent: Option<u64>) -> Result<()> {
1099        match self {
1100            DatabaseType::Postgres(pg) => pg.edit_label(id, name, parent).await,
1101            #[cfg(any(test, feature = "sqlite"))]
1102            DatabaseType::SQLite(sl) => sl.edit_label(id, name, parent),
1103        }
1104    }
1105
1106    /// Return the ID for a given label name
1107    ///
1108    /// # Errors
1109    ///
1110    /// * If there's a connectivity issue to Postgres, an error will result
1111    /// * If the label ID is invalid, an error will result
1112    #[cfg(any(test, feature = "admin"))]
1113    pub async fn label_id_from_name(&self, name: &str) -> Result<u64> {
1114        match self {
1115            DatabaseType::Postgres(pg) => pg.label_id_from_name(name).await,
1116            #[cfg(any(test, feature = "sqlite"))]
1117            DatabaseType::SQLite(sl) => sl.label_id_from_name(name),
1118        }
1119    }
1120
1121    /// Associate an existing label by its IDs with a file.
1122    ///
1123    /// # Errors
1124    ///
1125    /// * Incorrect IDs will result in an error
1126    /// * If the file already has the given label associated
1127    /// * If there is a network or connection issue with Postgres
1128    #[cfg(any(test, feature = "admin"))]
1129    pub async fn label_file(&self, file_id: u64, label_id: u64) -> Result<()> {
1130        match self {
1131            DatabaseType::Postgres(pg) => pg.label_file(file_id, label_id).await,
1132            #[cfg(any(test, feature = "sqlite"))]
1133            DatabaseType::SQLite(sl) => sl.label_file(file_id, label_id),
1134        }
1135    }
1136}
1137
1138/// Hash a password string with [Argon2]
1139///
1140/// # Errors
1141///
1142/// An error may result if Argon has an error
1143pub fn hash_password(password: &str) -> Result<String> {
1144    let salt = SaltString::generate(&mut OsRng);
1145    let argon2 = Argon2::default();
1146    Ok(argon2
1147        .hash_password(password.as_bytes(), &salt)?
1148        .to_string())
1149}
1150
1151/// Generate a new, random API key
1152#[must_use]
1153pub fn random_bytes_api_key() -> String {
1154    let key1 = uuid::Uuid::new_v4();
1155    let key2 = uuid::Uuid::new_v4();
1156    let key1 = key1.to_string().replace('-', "");
1157    let key2 = key2.to_string().replace('-', "");
1158    format!("{key1}{key2}")
1159}
1160
1161#[cfg(test)]
1162mod tests {
1163    use super::*;
1164    #[cfg(feature = "vt")]
1165    use crate::vt::VtUpdater;
1166
1167    use std::fs;
1168    #[cfg(feature = "vt")]
1169    use std::sync::Arc;
1170    #[cfg(feature = "vt")]
1171    use std::time::SystemTime;
1172
1173    use anyhow::Context;
1174    use fuzzyhash::FuzzyHash;
1175    use malwaredb_api::{PartialHashSearchType, SearchRequestParameters, SearchType};
1176    use malwaredb_lzjd::{LZDict, Murmur3HashState};
1177    use tlsh_fixed::TlshBuilder;
1178    use uuid::Uuid;
1179
1180    const MALWARE_LABEL: &str = "malware";
1181    const RANSOMWARE_LABEL: &str = "ransomware";
1182
1183    fn generate_similarity_request(data: &[u8]) -> malwaredb_api::SimilarSamplesRequest {
1184        let mut hashes = vec![];
1185
1186        hashes.push((malwaredb_api::SimilarityHashType::SSDeep, FuzzyHash::new(data).to_string()));
1187
1188        let mut builder = TlshBuilder::new(
1189            tlsh_fixed::BucketKind::Bucket256,
1190            tlsh_fixed::ChecksumKind::ThreeByte,
1191            tlsh_fixed::Version::Version4,
1192        );
1193
1194        builder.update(data);
1195
1196        if let Ok(hasher) = builder.build() {
1197            hashes.push((malwaredb_api::SimilarityHashType::TLSH, hasher.hash()));
1198        }
1199
1200        let build_hasher = Murmur3HashState::default();
1201        let lzjd_str = LZDict::from_bytes_stream(data.iter().copied(), &build_hasher).to_string();
1202        hashes.push((malwaredb_api::SimilarityHashType::LZJD, lzjd_str));
1203
1204        malwaredb_api::SimilarSamplesRequest { hashes }
1205    }
1206
1207    async fn pg_config() -> Postgres {
1208        // create user malwaredbtesting with password 'malwaredbtesting';
1209        // create database malwaredbtesting owner malwaredbtesting;
1210        const CONNECTION_STRING: &str = "user=malwaredbtesting password=malwaredbtesting dbname=malwaredbtesting host=localhost sslmode=disable";
1211
1212        if let Ok(pg_port) = std::env::var("PG_PORT") {
1213            // Get the port number to run in Github CI
1214            let conn_string = format!("{CONNECTION_STRING} port={pg_port}");
1215            Postgres::new(&conn_string, None)
1216                .await
1217                .context(format!("failed to connect to postgres with specified port {pg_port}"))
1218                .unwrap()
1219        } else {
1220            Postgres::new(CONNECTION_STRING, None).await.unwrap()
1221        }
1222    }
1223
1224    #[tokio::test]
1225    #[ignore = "don't run this in CI"]
1226    async fn pg() {
1227        let psql = pg_config().await;
1228        psql.delete().await.unwrap();
1229
1230        let psql = pg_config().await;
1231        let db = DatabaseType::Postgres(psql);
1232        everything(&db).await.unwrap();
1233
1234        #[cfg(feature = "vt")]
1235        {
1236            let db_config = db.get_config().await.unwrap();
1237            let state = crate::State {
1238                port: 8080,
1239                directory: None,
1240                max_upload: 10 * 1024 * 1024,
1241                ip: "127.0.0.1".parse().unwrap(),
1242                db_type: Arc::new(db),
1243                db_config,
1244                keys: HashMap::new(),
1245                started: SystemTime::now(),
1246                vt_client: std::env::var("VT_API_KEY")
1247                    .map_or(None, |e| Some(malwaredb_virustotal::VirusTotalClient::new(e))),
1248                tls_config: None,
1249                mdns: None,
1250            };
1251
1252            let vt: VtUpdater = state.try_into().expect("failed to create VtUpdater");
1253
1254            vt.updater().await.unwrap();
1255            println!("PG: Did VT ops!");
1256
1257            let psql = pg_config().await;
1258
1259            let vt_stats = psql
1260                .get_vt_stats()
1261                .await
1262                .context("failed to get Postgres VT Stats")
1263                .unwrap();
1264            println!("{vt_stats:?}");
1265            assert!(
1266                vt_stats.files_without_records + vt_stats.clean_records + vt_stats.hits_records > 2
1267            );
1268        }
1269
1270        // Re-create the Postgres object so we can do some clean-up
1271        let psql = pg_config().await;
1272        psql.delete().await.unwrap();
1273    }
1274
1275    #[tokio::test]
1276    async fn sqlite() {
1277        const DB_FILE: &str = "testing_sqlite.db";
1278        if std::path::Path::new(DB_FILE).exists() {
1279            fs::remove_file(DB_FILE)
1280                .context(format!("failed to delete old SQLite file {DB_FILE}"))
1281                .unwrap();
1282        }
1283
1284        let sqlite = Sqlite::new(DB_FILE)
1285            .context(format!("failed to create SQLite instance for {DB_FILE}"))
1286            .unwrap();
1287
1288        let db = DatabaseType::SQLite(sqlite);
1289        everything(&db).await.unwrap();
1290
1291        #[cfg(feature = "vt")]
1292        {
1293            let db_config = db.get_config().await.unwrap();
1294            let state = crate::State {
1295                port: 8080,
1296                directory: None,
1297                max_upload: 10 * 1024 * 1024,
1298                ip: "127.0.0.1".parse().unwrap(),
1299                db_type: Arc::new(db),
1300                db_config,
1301                keys: HashMap::new(),
1302                started: SystemTime::now(),
1303                vt_client: std::env::var("VT_API_KEY")
1304                    .map_or(None, |e| Some(malwaredb_virustotal::VirusTotalClient::new(e))),
1305                tls_config: None,
1306                mdns: None,
1307            };
1308
1309            let sqlite_second = Sqlite::new(DB_FILE)
1310                .context(format!("failed to create SQLite instance for {DB_FILE}"))
1311                .unwrap();
1312
1313            let vt: VtUpdater = state.try_into().expect("failed to create VtUpdater");
1314
1315            vt.updater().await.unwrap();
1316            println!("Sqlite: Did VT ops!");
1317            let vt_stats = sqlite_second
1318                .get_vt_stats()
1319                .context("failed to get Sqlite VT Stats")
1320                .unwrap();
1321            println!("{vt_stats:?}");
1322            assert!(
1323                vt_stats.files_without_records + vt_stats.clean_records + vt_stats.hits_records > 2
1324            );
1325        }
1326
1327        fs::remove_file(DB_FILE)
1328            .context(format!("failed to delete SQLite file {DB_FILE}"))
1329            .unwrap();
1330    }
1331
1332    #[allow(clippy::too_many_lines)]
1333    async fn everything(db: &DatabaseType) -> Result<()> {
1334        const ADMIN_UNAME: &str = "admin";
1335        const ADMIN_PASSWORD: &str = "super_secure_password_dont_tell_anyone!";
1336
1337        db.set_name("Testing Database")
1338            .await
1339            .context("setting instance name failed")?;
1340
1341        assert!(
1342            db.authenticate(ADMIN_UNAME, ADMIN_PASSWORD).await.is_err(),
1343            "Authentication without password should have failed."
1344        );
1345
1346        db.set_password(ADMIN_UNAME, ADMIN_PASSWORD)
1347            .await
1348            .context("failed to set admin password")?;
1349
1350        let admin_api_key = db
1351            .authenticate(ADMIN_UNAME, ADMIN_PASSWORD)
1352            .await
1353            .context("unable to get api key for admin")?;
1354        println!("API key: {admin_api_key}");
1355        assert_eq!(admin_api_key.len(), 64);
1356
1357        assert_eq!(db.get_uid(&admin_api_key).await?, 0, "Unable to get UID given the API key");
1358
1359        let admin_api_key_again = db
1360            .authenticate(ADMIN_UNAME, ADMIN_PASSWORD)
1361            .await
1362            .context("unable to get api key a second time for admin")?;
1363
1364        assert_eq!(admin_api_key, admin_api_key_again, "API keys didn't match the second time.");
1365
1366        let bad_password = "this_is_totally_not_my_password!!";
1367        eprintln!("Testing API login with incorrect password.");
1368        assert!(
1369            db.authenticate(ADMIN_UNAME, bad_password).await.is_err(),
1370            "Authenticating as admin with a bad password should have failed."
1371        );
1372
1373        let admin_is_admin = db
1374            .user_is_admin(0)
1375            .await
1376            .context("unable to see if admin (uid 0) is an admin")?;
1377        assert!(admin_is_admin);
1378
1379        let new_user_uname = "testuser";
1380        let new_user_email = "test@example.com";
1381        let new_user_password = "some_awesome_password_++";
1382        let new_id = db
1383            .create_user(
1384                new_user_uname,
1385                new_user_uname,
1386                new_user_uname,
1387                new_user_email,
1388                Some(new_user_password.into()),
1389                None,
1390                false,
1391            )
1392            .await
1393            .context(format!("failed to create user {new_user_uname}"))?;
1394
1395        let passwordless_user_id = db
1396            .create_user(
1397                "passwordless_user",
1398                "passwordless_user",
1399                "passwordless_user",
1400                "passwordless_user@example.com",
1401                None,
1402                None,
1403                false,
1404            )
1405            .await
1406            .context("failed to create passwordless_user")?;
1407
1408        for user in &db.list_users().await.context("failed to list users")? {
1409            if user.id == passwordless_user_id {
1410                assert_eq!(user.uname, "passwordless_user");
1411            }
1412        }
1413
1414        db.edit_user(
1415            passwordless_user_id,
1416            "passwordless_user_2",
1417            "passwordless_user_2",
1418            "passwordless_user_2",
1419            "passwordless_user_2@something.com",
1420            false,
1421        )
1422        .await
1423        .context(format!("failed to alter 'passwordless' user, id {passwordless_user_id}"))?;
1424
1425        for user in &db.list_users().await.context("failed to list users")? {
1426            if user.id == passwordless_user_id {
1427                assert_eq!(user.uname, "passwordless_user_2");
1428            }
1429        }
1430
1431        assert!(new_id > 0, "Weird UID created for user {new_user_uname}: {new_id}");
1432
1433        assert!(
1434            db.create_user(
1435                new_user_uname,
1436                new_user_uname,
1437                new_user_uname,
1438                new_user_email,
1439                Some(new_user_password.into()),
1440                None,
1441                false
1442            )
1443            .await
1444            .is_err(),
1445            "Creating a new user with the same user name should fail"
1446        );
1447
1448        let ro_user_name = "ro_user";
1449        let ro_user_password = "ro_user_password";
1450        db.create_user(
1451            ro_user_name,
1452            "ro_user",
1453            "ro_user",
1454            "ro@example.com",
1455            Some(ro_user_password.into()),
1456            None,
1457            true,
1458        )
1459        .await
1460        .context("failed to create read-only user")?;
1461
1462        let ro_user_api_key = db
1463            .authenticate(ro_user_name, ro_user_password)
1464            .await
1465            .context("unable to get api key for read-only user")?;
1466
1467        let new_user_password_change = "some_new_awesomer_password!_++";
1468        db.set_password(new_user_uname, new_user_password_change)
1469            .await
1470            .context("failed to change the password for testuser")?;
1471
1472        let new_user_api_key = db
1473            .authenticate(new_user_uname, new_user_password_change)
1474            .await
1475            .context("unable to get api key for testuser")?;
1476        eprintln!("{new_user_uname} got API key {new_user_api_key}");
1477
1478        assert_eq!(admin_api_key.len(), new_user_api_key.len());
1479
1480        let users = db.list_users().await.context("failed to list users")?;
1481        assert_eq!(users.len(), 4, "Four users were created, yet there are {} users", users.len());
1482        eprintln!("DB has {} users:", users.len());
1483        let mut passwordless_user_found = false;
1484        for user in users {
1485            println!("{user}");
1486            if user.uname == "passwordless_user_2" {
1487                assert!(!user.has_api_key);
1488                assert!(!user.has_password);
1489                passwordless_user_found = true;
1490            } else {
1491                assert!(user.has_api_key);
1492                assert!(user.has_password);
1493            }
1494        }
1495        assert!(passwordless_user_found);
1496
1497        let new_group_name = "some_new_group";
1498        let new_group_desc = "some_new_group_description";
1499        let new_group_id = 1;
1500        assert_eq!(
1501            db.create_group(new_group_name, new_group_desc, None)
1502                .await
1503                .context("failed to create group")?,
1504            new_group_id,
1505            "New group didn't have the expected ID, expected {new_group_id}"
1506        );
1507
1508        assert!(
1509            db.create_group(new_group_name, new_group_desc, None)
1510                .await
1511                .is_err(),
1512            "Duplicate group name should have failed"
1513        );
1514
1515        db.add_user_to_group(1, 1)
1516            .await
1517            .context("Unable to add uid 1 to gid 1")?;
1518
1519        let ro_user_uid = db
1520            .get_uid(&ro_user_api_key)
1521            .await
1522            .context("Unable to get UID for read-only user")?;
1523        db.add_user_to_group(ro_user_uid, 1)
1524            .await
1525            .context("Unable to add uid 2 to gid 1")?;
1526
1527        let new_admin_group_name = "admin_subgroup";
1528        let new_admin_group_desc = "admin_subgroup_description";
1529        let new_admin_group_id = 2;
1530        // TODO: Figure out why SQLite makes the group_id = 2, but with Postgres it's 3.
1531        assert!(
1532            db.create_group(new_admin_group_name, new_admin_group_desc, Some(0))
1533                .await
1534                .context("failed to create admin sub-group")?
1535                >= new_admin_group_id,
1536            "New group didn't have the expected ID, expected >= {new_admin_group_id}"
1537        );
1538
1539        let groups = db.list_groups().await.context("failed to list groups")?;
1540        assert_eq!(
1541            groups.len(),
1542            3,
1543            "Three groups were created, yet there are {} groups",
1544            groups.len()
1545        );
1546        eprintln!("DB has {} groups:", groups.len());
1547        for group in groups {
1548            println!("{group}");
1549            if group.id == new_admin_group_id {
1550                assert_eq!(group.parent, Some("admin".to_string()));
1551            }
1552            if group.id == 1 {
1553                let test_user_str = String::from(new_user_uname);
1554                let mut found = false;
1555                for member in group.members {
1556                    if member.uname == test_user_str {
1557                        found = true;
1558                        break;
1559                    }
1560                }
1561                assert!(found, "new user {test_user_str} wasn't in the group");
1562            }
1563        }
1564
1565        let default_source_name = "default_source".to_string();
1566        let default_source_id = db
1567            .create_source(
1568                &default_source_name,
1569                Some("desc_default_source"),
1570                None,
1571                Local::now(),
1572                true,
1573                Some(false),
1574            )
1575            .await
1576            .context("failed to create source `default_source`")?;
1577
1578        db.add_group_to_source(1, default_source_id)
1579            .await
1580            .context("failed to add group 1 to source 1")?;
1581
1582        let another_source_name = "another_source".to_string();
1583        let another_source_id = db
1584            .create_source(
1585                &another_source_name,
1586                Some("yet another file source"),
1587                None,
1588                Local::now(),
1589                true,
1590                Some(false),
1591            )
1592            .await
1593            .context("failed to create source `another_source`")?;
1594
1595        let empty_source_name = "empty_source".to_string();
1596        db.create_source(
1597            &empty_source_name,
1598            Some("empty and unused file source"),
1599            None,
1600            Local::now(),
1601            true,
1602            Some(false),
1603        )
1604        .await
1605        .context("failed to create source `another_source`")?;
1606
1607        db.add_group_to_source(1, another_source_id)
1608            .await
1609            .context("failed to add group 1 to source 1")?;
1610
1611        let sources = db.list_sources().await.context("failed to list sources")?;
1612        eprintln!("DB has {} sources:", sources.len());
1613        for source in sources {
1614            println!("{source}");
1615            assert_eq!(source.files, 0);
1616            if source.id == default_source_id || source.id == another_source_id {
1617                assert_eq!(
1618                    source.groups, 1,
1619                    "default source {default_source_name} should have 1 group"
1620                );
1621            } else {
1622                assert_eq!(source.groups, 0, "groups should zero (empty)");
1623            }
1624        }
1625
1626        let uid = db
1627            .get_uid(&new_user_api_key)
1628            .await
1629            .context("failed to user uid from apikey")?;
1630        let user_info = db
1631            .get_user_info(uid)
1632            .await
1633            .context("failed to get user's available groups and sources")?;
1634        assert!(user_info.sources.contains(&default_source_name));
1635        assert!(!user_info.is_admin);
1636        println!("UserInfoResponse: {user_info:?}");
1637
1638        assert!(
1639            db.allowed_user_source(1, default_source_id)
1640                .await
1641                .context(format!(
1642                    "failed to check that user 1 has access to source {default_source_id}"
1643                ))?,
1644            "User 1 should should have had access to source {default_source_id}"
1645        );
1646
1647        assert!(
1648            !db.allowed_user_source(1, 5)
1649                .await
1650                .context("failed to check that user 1 has access to source 5")?,
1651            "User 1 should should not have had access to source 5"
1652        );
1653
1654        let test_label_id = db
1655            .create_label("TestLabel", None)
1656            .await
1657            .context("failed to create test label")?;
1658        let test_elf_label_id = db
1659            .create_label("TestELF", Some(test_label_id))
1660            .await
1661            .context("failed to create test label")?;
1662
1663        let test_elf = include_bytes!("../../../types/testdata/elf/elf_linux_ppc64le").to_vec();
1664        let test_elf_meta = FileMetadata::new(&test_elf, Some("elf_linux_ppc64le"));
1665        let elf_type = db.get_type_id_for_bytes(&test_elf).await.unwrap();
1666
1667        let known_type =
1668            KnownType::new(&test_elf).context("failed to parse elf from test crate's test data")?;
1669        assert!(known_type.is_exec(), "ELF should be executable");
1670        eprintln!("ELF type ID: {elf_type}");
1671
1672        let file_addition = db
1673            .add_file(&test_elf_meta, known_type.clone(), 1, default_source_id, elf_type, None)
1674            .await
1675            .context("failed to insert a test elf")?;
1676        assert!(file_addition.is_new, "File should have been added");
1677        eprintln!("Added ELF to the DB");
1678        db.label_file(file_addition.file_id, test_elf_label_id)
1679            .await
1680            .context("failed to label file")?;
1681
1682        // Search with several fields
1683        let partial_search = SearchRequest {
1684            search: SearchType::Search(SearchRequestParameters {
1685                partial_hash: Some((PartialHashSearchType::SHA1, "fe7d0186".into())),
1686                labels: Some(vec![String::from("TestELF")]),
1687                file_type: Some(String::from("ELF")),
1688                magic: Some(String::from("ELF")),
1689                ..Default::default()
1690            }),
1691        };
1692        assert!(partial_search.is_valid());
1693        let partial_search_response = db.partial_search(1, partial_search).await?;
1694        assert_eq!(partial_search_response.hashes.len(), 1);
1695        assert_eq!(
1696            partial_search_response.hashes[0],
1697            "897541f9f3c673b3ecc7004ff52c70c0b0440e804c7c3eb4854d72d94c317868"
1698        );
1699
1700        // Ensure we get a partial result just with the magic
1701        let partial_search = SearchRequest {
1702            search: SearchType::Search(SearchRequestParameters {
1703                partial_hash: None,
1704                labels: None,
1705                file_type: None,
1706                magic: Some(String::from("ELF")),
1707                ..Default::default()
1708            }),
1709        };
1710        assert!(partial_search.is_valid());
1711        let partial_search_response = db.partial_search(1, partial_search).await?;
1712        assert_eq!(partial_search_response.hashes.len(), 1);
1713        assert_eq!(
1714            partial_search_response.hashes[0],
1715            "897541f9f3c673b3ecc7004ff52c70c0b0440e804c7c3eb4854d72d94c317868"
1716        );
1717
1718        // Should be valid yet return nothing since it's the wrong file type
1719        let partial_search = SearchRequest {
1720            search: SearchType::Search(SearchRequestParameters {
1721                partial_hash: Some((PartialHashSearchType::SHA1, "fe7d0186".into())),
1722                file_type: Some(String::from("PE32")),
1723                ..Default::default()
1724            }),
1725        };
1726        assert!(partial_search.is_valid());
1727        let partial_search_response = db.partial_search(1, partial_search).await?;
1728        assert_eq!(partial_search_response.hashes.len(), 0);
1729
1730        let partial_search = SearchRequest {
1731            search: SearchType::Search(SearchRequestParameters {
1732                file_name: Some("ppc64".into()),
1733                ..Default::default()
1734            }),
1735        };
1736        assert!(partial_search.is_valid());
1737        let partial_search_response = db.partial_search(1, partial_search).await?;
1738        assert_eq!(partial_search_response.hashes.len(), 1);
1739
1740        // Invalid search request should return empty results
1741        let partial_search = SearchRequest {
1742            search: SearchType::Search(SearchRequestParameters::default()),
1743        };
1744        assert!(!partial_search.is_valid());
1745        let partial_search_response = db.partial_search(1, partial_search).await?;
1746        assert!(partial_search_response.hashes.is_empty());
1747
1748        // Invalid search pagination should return empty results
1749        let partial_search = SearchRequest {
1750            search: SearchType::Continuation(Uuid::default()),
1751        };
1752        assert!(partial_search.is_valid());
1753        let partial_search_response = db.partial_search(1, partial_search).await?;
1754        assert!(partial_search_response.hashes.is_empty());
1755
1756        // Ensure a type representing an unknown type isn't found
1757        assert!(
1758            db.get_type_id_for_bytes(include_bytes!("../../../../MDB_Logo.ico"))
1759                .await
1760                .is_err()
1761        );
1762
1763        assert!(
1764            db.add_file(
1765                &test_elf_meta,
1766                known_type.clone(),
1767                ro_user_uid,
1768                default_source_id,
1769                elf_type,
1770                None
1771            )
1772            .await
1773            .is_err(),
1774            "Read-only user should not be able to add a file"
1775        );
1776
1777        let mut test_elf_meta_different_name = test_elf_meta.clone();
1778        test_elf_meta_different_name.name = Some("completely_different_name.bin".into());
1779
1780        assert!(
1781            !db.add_file(
1782                &test_elf_meta_different_name,
1783                known_type,
1784                1,
1785                another_source_id,
1786                elf_type,
1787                None
1788            )
1789            .await
1790            .context("failed to insert a test elf again for a different source")?
1791            .is_new
1792        );
1793
1794        let sources = db
1795            .list_sources()
1796            .await
1797            .context("failed to re-list sources")?;
1798        eprintln!("DB has {} sources, and a file was added twice:", sources.len());
1799        println!("We should have two sources with one file each, yet only one ELF.");
1800        for source in sources {
1801            println!("{source}");
1802            if source.id == default_source_id || source.id == another_source_id {
1803                assert_eq!(source.files, 1);
1804            } else {
1805                assert_eq!(source.files, 0, "groups should zero (empty)");
1806            }
1807        }
1808
1809        assert!(
1810            !db.get_user_sources(1)
1811                .await
1812                .expect("failed to get user 1's sources")
1813                .sources
1814                .is_empty()
1815        );
1816
1817        let file_types_counts = db
1818            .file_types_counts()
1819            .await
1820            .context("failed to get file types and counts")?;
1821        for (name, count) in file_types_counts {
1822            println!("{name}: {count}");
1823            assert_eq!(name, "ELF");
1824            assert_eq!(count, 1);
1825        }
1826
1827        let mut test_elf_modified = test_elf.clone();
1828        let random_bytes = Uuid::new_v4();
1829        let mut random_bytes = random_bytes.into_bytes().to_vec();
1830        test_elf_modified.append(&mut random_bytes);
1831        let similarity_request = generate_similarity_request(&test_elf_modified);
1832        let similarity_response = db
1833            .find_similar_samples(1, &similarity_request.hashes)
1834            .await
1835            .context("failed to get similarity response")?;
1836        eprintln!("Similarity response: {similarity_response:?}");
1837        let similarity_response = similarity_response.first().unwrap();
1838        assert_eq!(
1839            similarity_response.sha256,
1840            hex::encode(&test_elf_meta.sha256),
1841            "Similarity response should have had the hash of the original ELF"
1842        );
1843        for (algo, sim) in &similarity_response.algorithms {
1844            match algo {
1845                malwaredb_api::SimilarityHashType::LZJD => {
1846                    assert!(*sim > 0.0f32);
1847                }
1848                malwaredb_api::SimilarityHashType::SSDeep => {
1849                    assert!(*sim > 80.0f32);
1850                }
1851                malwaredb_api::SimilarityHashType::TLSH => {
1852                    assert!(*sim <= 20f32);
1853                }
1854                _ => {}
1855            }
1856        }
1857
1858        let test_elf_hashtype = HashType::try_from(test_elf_meta.sha1.as_slice())
1859            .context("failed to get `HashType::SHA1` from string")?;
1860        let response_sha256 = db
1861            .retrieve_sample(1, &test_elf_hashtype)
1862            .await
1863            .context("could not get SHA-256 hash from test sample")
1864            .unwrap();
1865        assert_eq!(response_sha256, hex::encode(&test_elf_meta.sha256));
1866
1867        let test_bogus_hash =
1868            HashType::try_from("d154b8420fc56a629df2e6d918be53310d8ac39a926aa5f60ae59a66298969a0")
1869                .context("failed to get `HashType` from static string")?;
1870        assert!(
1871            db.retrieve_sample(1, &test_bogus_hash).await.is_err(),
1872            "Getting a file with a bogus hash should have failed."
1873        );
1874
1875        let test_pdf = include_bytes!("../../../types/testdata/pdf/test.pdf").to_vec();
1876        let test_pdf_meta = FileMetadata::new(&test_pdf, Some("test.pdf"));
1877        let pdf_type = db.get_type_id_for_bytes(&test_pdf).await.unwrap();
1878
1879        let known_type =
1880            KnownType::new(&test_pdf).context("failed to parse pdf from test crate's test data")?;
1881
1882        assert!(
1883            db.add_file(&test_pdf_meta, known_type, 1, default_source_id, pdf_type, None)
1884                .await
1885                .context("failed to insert a test pdf")?
1886                .is_new
1887        );
1888        eprintln!("Added PDF to the DB");
1889
1890        let test_rtf = include_bytes!("../../../types/testdata/rtf/hello.rtf").to_vec();
1891        let test_rtf_meta = FileMetadata::new(&test_rtf, Some("test.rtf"));
1892        let rtf_type = db
1893            .get_type_id_for_bytes(&test_rtf)
1894            .await
1895            .context("failed to get file type id for rtf")?;
1896
1897        let known_type =
1898            KnownType::new(&test_rtf).context("failed to parse pdf from test crate's test data")?;
1899
1900        assert!(
1901            db.add_file(&test_rtf_meta, known_type, 1, default_source_id, rtf_type, None)
1902                .await
1903                .context("failed to insert a test rtf")?
1904                .is_new
1905        );
1906        eprintln!("Added RTF to the DB");
1907
1908        let report = db
1909            .get_sample_report(1, &HashType::try_from(test_rtf_meta.sha256.as_slice()).unwrap())
1910            .await
1911            .context("failed to get report for test rtf")?;
1912        assert!(
1913            report
1914                .clone()
1915                .filecommand
1916                .unwrap()
1917                .contains("Rich Text Format")
1918        );
1919        println!("Report: {report}");
1920
1921        assert!(
1922            db.get_sample_report(
1923                999,
1924                &HashType::try_from(test_rtf_meta.sha256.as_slice()).unwrap()
1925            )
1926            .await
1927            .is_err()
1928        );
1929
1930        #[cfg(feature = "vt")]
1931        {
1932            assert!(report.vt.is_some());
1933            let files_needing_vt = db
1934                .files_without_vt_records(10)
1935                .await
1936                .context("failed to get files without VT records")?;
1937            assert!(files_needing_vt.len() > 2);
1938            println!("{} files needing VT data: {files_needing_vt:?}", files_needing_vt.len());
1939        }
1940
1941        #[cfg(not(feature = "vt"))]
1942        {
1943            assert!(report.vt.is_none());
1944        }
1945
1946        let reset = db
1947            .reset_api_keys()
1948            .await
1949            .context("failed to reset all API keys")?;
1950        eprintln!("Cleared {reset} api keys.");
1951
1952        let db_info = db.db_info().await.context("failed to get database info")?;
1953        eprintln!("DB Info: {db_info:?}");
1954
1955        let data_types = db
1956            .get_known_data_types()
1957            .await
1958            .context("failed to get data types")?;
1959        for data_type in data_types {
1960            println!("{data_type:?}");
1961        }
1962
1963        let sources = db
1964            .list_sources()
1965            .await
1966            .context("failed to list sources second time")?;
1967        eprintln!("DB has {} sources:", sources.len());
1968        for source in sources {
1969            println!("{source}");
1970        }
1971
1972        let file_types_counts = db
1973            .file_types_counts()
1974            .await
1975            .context("failed to get file types and counts")?;
1976        for (name, count) in file_types_counts {
1977            println!("{name}: {count}");
1978            assert_ne!(name, "Mach-O", "No Mach-O files have been inserted yet!");
1979        }
1980
1981        let fatmacho =
1982            include_bytes!("../../../types/testdata/macho/macho_fat_arm64_ppc_ppc64_x86_64")
1983                .to_vec();
1984        let fatmacho_meta = FileMetadata::new(&fatmacho, Some("macho_fat_arm64_ppc_ppc64_x86_64"));
1985        let fatmacho_type = db
1986            .get_type_id_for_bytes(&fatmacho)
1987            .await
1988            .context("failed to get file type for Fat Mach-O")?;
1989        let known_type = KnownType::new(&fatmacho)
1990            .context("failed to parse Fat Mach-O from type crate's test data")?;
1991
1992        assert!(
1993            db.add_file(&fatmacho_meta, known_type, 1, default_source_id, fatmacho_type, None)
1994                .await
1995                .context("failed to insert a test Fat Mach-O")?
1996                .is_new
1997        );
1998        eprintln!("Added Fat Mach-O to the DB");
1999
2000        let file_types_counts = db
2001            .file_types_counts()
2002            .await
2003            .context("failed to get file types and counts")?;
2004        for (name, count) in &file_types_counts {
2005            println!("{name}: {count}");
2006        }
2007
2008        assert_eq!(
2009            *file_types_counts.get("Mach-O").unwrap(),
2010            4,
2011            "Expected 4 Mach-O files, got {:?}",
2012            file_types_counts.get("Mach-O")
2013        );
2014
2015        let allowed_files = db
2016            .user_allowed_files_by_sha256(1, None)
2017            .await
2018            .context("failed to get allowed files")?;
2019        assert_eq!(allowed_files.0.len(), 8);
2020
2021        let allowed_files = db
2022            .user_allowed_files_by_sha256(1, Some(allowed_files.1))
2023            .await
2024            .context("failed to get allowed files")?;
2025        assert!(allowed_files.0.is_empty());
2026
2027        let malware_label_id = db
2028            .create_label(MALWARE_LABEL, None)
2029            .await
2030            .context("failed to create first label")?;
2031        let ransomware_label_id = db
2032            .create_label(RANSOMWARE_LABEL, Some(malware_label_id))
2033            .await
2034            .context("failed to create malware sub-label")?;
2035        let labels = db.get_labels().await.context("failed to get labels")?;
2036
2037        assert_eq!(labels.len(), 4, "Expected 4 labels, got {labels}");
2038        for label in labels.0 {
2039            if label.name == RANSOMWARE_LABEL {
2040                assert_eq!(label.id, ransomware_label_id);
2041                assert_eq!(label.parent.unwrap(), MALWARE_LABEL);
2042            }
2043        }
2044
2045        // Use this file as a stand-un for an unknown file type
2046        let source_code = include_bytes!("mod.rs");
2047        let source_meta = FileMetadata::new(source_code, Some("mod.rs"));
2048        let known_type =
2049            KnownType::new(source_code).context("failed to source code to get `Unknown` type")?;
2050
2051        assert!(matches!(known_type, KnownType::Unknown(_)));
2052
2053        let unknown_type: Vec<FileType> = db
2054            .get_known_data_types()
2055            .await?
2056            .into_iter()
2057            .filter(|t| t.name.eq_ignore_ascii_case("unknown"))
2058            .collect();
2059        let unknown_type_id = unknown_type.first().unwrap().id;
2060        assert!(db.get_type_id_for_bytes(source_code).await.is_err());
2061        db.enable_keep_unknown_files()
2062            .await
2063            .context("failed to enable keeping of unknown files")?;
2064        let source_type = db
2065            .get_type_id_for_bytes(source_code)
2066            .await
2067            .context("failed to type id for source code unknown type example")?;
2068        assert_eq!(source_type, unknown_type_id);
2069        eprintln!("Unknown file type ID: {source_type}");
2070        assert!(
2071            db.add_file(&source_meta, known_type, 1, default_source_id, unknown_type_id, None)
2072                .await
2073                .context("failed to add Rust source code file")?
2074                .is_new
2075        );
2076        eprintln!("Added Rust source code to the DB");
2077
2078        #[cfg(feature = "yara")]
2079        assert!(db.get_unfinished_yara_tasks().await?.is_empty());
2080
2081        db.reset_own_api_key(0)
2082            .await
2083            .context("failed to clear own API key uid 0")?;
2084
2085        db.deactivate_user(0)
2086            .await
2087            .context("failed to clear password and API key for uid 0")?;
2088
2089        Ok(())
2090    }
2091}