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    /// UID for anonymous users, if enabled
124    #[cfg_attr(docsrs, doc(cfg(feature = "anonymous")))]
125    #[cfg(feature = "anonymous")]
126    pub anonymous_uid: Option<u32>,
127}
128
129impl MDBConfig {
130    /// Get the key ID for encrypting samples
131    #[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/// VT record information for files in Malware DB
141#[cfg_attr(docsrs, doc(cfg(feature = "vt")))]
142#[cfg(feature = "vt")]
143#[derive(Debug, Clone, Copy)]
144pub struct VtStats {
145    /// Files marked as clean
146    pub clean_records: u32,
147
148    /// Files marked as malicious
149    pub hits_records: u32,
150
151    /// Files without VT records
152    pub files_without_records: u32,
153}
154
155impl DatabaseType {
156    /// Get a database connection from a configuration string
157    ///
158    /// # Errors
159    ///
160    /// * If there's a connectivity issue to Postgres, an error will result
161    /// * If the `SQLite` file cannot be created or opened, an error will result
162    /// * If `SQLite` is the type but Malware DB wasn't compiled with the sqlite feature, an error will result
163    /// * If the format or database type isn't known, an error will result
164    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    /// Get a database connection from a configuration string and perform a migration, if needed
171    ///
172    /// # Errors
173    ///
174    /// * If there's a connectivity issue to Postgres, an error will result
175    /// * If the `SQLite` file cannot be created or opened, an error will result
176    /// * If `SQLite` is the type but Malware DB wasn't compiled with the sqlite feature, an error will result
177    /// * If the format or database type isn't known, an error will result
178    #[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    /// Set the flag allowing uploads to Virus Total.
204    ///
205    /// # Errors
206    ///
207    /// If there's a connectivity issue to Postgres, an error will result
208    #[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    /// Set the flag preventing uploads to Virus Total.
219    ///
220    /// # Errors
221    ///
222    /// If there's a connectivity issue to Postgres, an error will result
223    #[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    /// Get the SHA-256 hashes of the files which don't have VT records
234    ///
235    /// # Errors
236    ///
237    /// If there's a connectivity issue to Postgres, an error will result
238    #[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    /// Store the VT results: AV hits and detailed report, or lack of any AV hits
249    ///
250    /// # Errors
251    ///
252    /// If there's a connectivity issue to Postgres, an error will result
253    #[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    /// Quick statistics regarding the data contained for VT information for our samples
264    ///
265    /// # Errors
266    ///
267    /// If there's a connectivity issue to Postgres, an error will result
268    #[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    /// Add the Yara search to the database
279    ///
280    /// # Errors
281    ///
282    /// If there's a connectivity issue to Postgres, an error will result
283    #[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    /// Get unfinished Yara tasks for processing.
299    ///
300    /// # Errors
301    ///
302    /// If there's a connectivity issue to Postgres, an error will result
303    #[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    /// Add a Yara match to the database
314    ///
315    /// # Errors
316    ///
317    /// If there's a connectivity issue to Postgres, an error will result
318    #[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    /// Indicate that the Yara search task has finished
334    ///
335    /// # Errors
336    ///
337    /// If there's a connectivity issue to Postgres, an error will result
338    #[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    /// Add the last file ID for the next iteration
349    ///
350    /// # Errors
351    ///
352    /// If there's a connectivity issue to Postgres, an error will result
353    #[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    /// Get the Yara search results
364    ///
365    /// # Errors
366    ///
367    /// If there's a connectivity issue to Postgres, an error will result
368    #[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    /// Get the configuration which is stored in the database
383    ///
384    /// # Errors
385    ///
386    /// If there's a connectivity issue to Postgres, an error will result
387    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    /// Check user credentials, return the API key. Generate if it doesn't exist.
396    ///
397    /// # Errors
398    ///
399    /// * If there's a connectivity issue to Postgres, an error will result
400    /// * If the username and/or password aren't correct, an error will result
401    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    /// Get the user's ID from their API key
410    ///
411    /// # Errors
412    ///
413    /// * If there's a connectivity issue to Postgres, an error will result
414    /// * If the api key isn't valid, an error will result
415    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    /// Retrieve information about the database
425    ///
426    /// # Errors
427    ///
428    /// * If there's a connectivity issue to Postgres, an error will result
429    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    /// Retrieve the names of the groups and sources the user is part of and has access to
438    ///
439    /// # Errors
440    ///
441    /// * If there's a connectivity issue to Postgres, an error will result
442    /// * If the user ID isn't valid, an error will result
443    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    /// Retrieve the source information available to the specified user
452    ///
453    /// # Errors
454    ///
455    /// * If there's a connectivity issue to Postgres, an error will result
456    /// * If the user ID isn't valid, an error will result
457    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    /// Let the user clear their own API key to log out from all systems
466    ///
467    /// # Errors
468    ///
469    /// * If there's a connectivity issue to Postgres, an error will result
470    /// * If the user ID isn't valid, an error will result
471    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    /// Retrieve the supported data type information
480    ///
481    /// # Errors
482    ///
483    /// If there's a connectivity issue to Postgres, an error will result
484    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    /// Get all labels from Malware DB
493    ///
494    /// # Errors
495    ///
496    /// If there's a connectivity issue to Postgres, an error will result
497    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    /// Get the corresponding type ID for a buffer representing a file
506    ///
507    /// # Errors
508    ///
509    /// If there's a connectivity issue to Postgres, an error will result
510    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    /// Check that a user has been granted access data for the specific source
519    ///
520    /// # Errors
521    ///
522    /// * If there's a connectivity issue to Postgres, an error will result
523    /// * If the user or source ID(s) aren't valid, an error will result
524    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    /// Check to see if the user is an administrator. The user must be a member of the
533    /// admin group (group ID 0), or a one group below (a group with the parent group id of 0).
534    ///
535    /// # Errors
536    ///
537    /// * If there's a connectivity issue to Postgres, an error will result
538    /// * If the user ID isn't valid, an error will result
539    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    /// Add a file's metadata to the database, returning true if this is a new entry
548    ///
549    /// # Errors
550    ///
551    /// * If there's a connectivity issue to Postgres, an error will result
552    /// * If the source doesn't exist or the user isn't part of a member group, an error will result
553    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    /// Search for allowed samples based on partial search and/or file name
572    ///
573    /// # Errors
574    ///
575    /// * If there's a connectivity issue to Postgres, an error will result
576    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    /// Delete old pagination searches
585    ///
586    /// # Errors
587    ///
588    /// An error would occur if the Postgres server couldn't be reached.
589    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    /// Retrieve the SHA-256 hash of the sample while checking that the user is permitted
598    /// to access to it
599    ///
600    /// # Errors
601    ///
602    /// * If there's a connectivity issue to Postgres, an error will result
603    /// * If the file doesn't exist or the user isn't allowed access, an error will result
604    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    /// Retrieve a report for a given sample, if allowed.
613    ///
614    /// # Errors
615    ///
616    /// * If there's a connectivity issue to Postgres, an error will result
617    /// * If the file doesn't exist or the user isn't allowed access, an error will result
618    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    /// Given a collection of similarity hashes, find samples which are similar.
631    ///
632    /// # Errors
633    ///
634    /// If there's a connectivity issue to Postgres, an error will result
635    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    /// For a given user ID, return the file hashes the person is allowed to know about and the last
648    /// file ID, which can be provided as `next` to get the next batch of hashes.
649    ///
650    /// # Errors
651    ///
652    /// If there's a connectivity issue to Postgres, an error will result
653    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    // Private functions
666
667    /// Get the file encryption keys
668    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    /// Get the key and AES nonce for a specific file identified by SHA-256 hash
677    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    /// Set the AES nonce for a file specified by SHA-256 hash, a `None` value removes it.
689    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    /// If the config changes to remove the encryption and the rewrite function is called, clear
698    /// the crypto.
699    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    /// Checks if a migration is needed, erroring if action is to check and the schema has changed.
708    ///
709    /// # Errors
710    ///
711    /// If there's a connectivity issue to Postgres, an error will result; if a migration is needed
712    /// and not an administrative action, an error results.
713    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    // Administrative functions
722
723    /// Set the instance name
724    ///
725    /// # Errors
726    ///
727    /// If there's a connectivity issue to Postgres, an error will result
728    #[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    /// Set the compression flag
739    ///
740    /// # Errors
741    ///
742    /// If there's a connectivity issue to Postgres, an error will result
743    #[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    /// Unset the compression flag, does not go and decompress files already compressed!
754    ///
755    /// # Errors
756    ///
757    /// If there's a connectivity issue to Postgres, an error will result
758    #[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    /// Set the keep unknown files flag
769    ///
770    /// # Errors
771    ///
772    /// If there's a connectivity issue to Postgres, an error will result
773    #[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    /// Unset the keep unknown files flag, does not go and remove unknown files!
784    ///
785    /// # Errors
786    ///
787    /// If there's a connectivity issue to Postgres, an error will result
788    #[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    /// Add an encryption key to the database, set it as the default, and return the key ID
799    ///
800    /// # Errors
801    ///
802    /// * If there is a connectivity with Postgres, an error will result
803    #[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    /// Get the key ID and algorithm names
814    ///
815    /// # Errors
816    ///
817    /// If there is a connectivity with Postgres, an error will result
818    #[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    /// Create a user account, return the user ID.
829    ///
830    /// # Errors
831    ///
832    /// If there's a connectivity issue to Postgres, an error will result
833    #[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    /// Clear all API keys, either in case of suspected activity, or part of policy
859    ///
860    /// # Errors
861    ///
862    /// If there's a connectivity issue to Postgres, an error will result
863    #[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    /// Set a user's password
874    ///
875    /// # Errors
876    ///
877    /// * If there's a connectivity issue to Postgres, an error will result
878    /// * If the user doesn't exist, an error will result
879    #[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    /// Specify the user account which is to be the user representing anonymous connections.
890    ///
891    /// # Errors
892    ///
893    /// Returns an error if there's a connection issue with the database.
894    #[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    /// Clear the user account which is used for anonymous connections. This does not delete
905    /// the account nor remove any data uploaded by anonymous users.
906    ///
907    /// # Errors
908    ///
909    /// Returns an error if there's a connection issue with the database.
910    #[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    /// Get the complete list of users
921    ///
922    /// # Errors
923    ///
924    /// If there's a connectivity issue to Postgres, an error will result
925    #[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    /// Get the ID of a group from name
936    ///
937    /// # Errors
938    ///
939    /// * If there's a connectivity issue to Postgres, an error will result
940    /// * If the group name isn't valid, an error will result
941    #[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    /// Update the record for a group
952    ///
953    /// # Errors
954    ///
955    /// * If there's a connectivity issue to Postgres, an error will result
956    /// * If the group doesn't exist, an error will result
957    #[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    /// Get the complete list of groups
974    ///
975    /// # Errors
976    ///
977    /// If there's a connectivity issue to Postgres, an error will result
978    #[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    /// Grant a user membership to a group, both by id.
989    ///
990    /// # Errors
991    ///
992    /// * If there's a connectivity issue to Postgres, an error will result
993    /// * If the user or group doesn't exist, an error will result
994    #[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    /// Grand a group access to a source, both by id.
1005    ///
1006    /// # Errors
1007    ///
1008    /// * If there's a connectivity issue to Postgres, an error will result
1009    /// * If the group or source doesn't exist, an error will result
1010    #[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    /// Create a new group, returning the group ID
1021    ///
1022    /// # Errors
1023    ///
1024    /// * If there's a connectivity issue to Postgres, an error will result
1025    /// * If the group name is already taken, an error will result
1026    #[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    /// Get the complete list of sources
1042    ///
1043    /// # Errors
1044    ///
1045    /// If there's a connectivity issue to Postgres, an error will result
1046    #[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    /// Create a source, returning the source ID
1057    ///
1058    /// # Errors
1059    ///
1060    /// * If there's a connectivity issue to Postgres, an error will result
1061    /// * If the source already exists, an error will result
1062    #[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    /// Edit a user account setting the specified field values, primarily used by the Admin gui
1086    ///
1087    /// # Errors
1088    ///
1089    /// * If there's a connectivity issue to Postgres, an error will result
1090    /// * If the user isn't valid, an error will result
1091    #[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    /// Set a user to be read-only
1113    ///
1114    /// # Errors
1115    ///
1116    /// Returns an error if the ID isn't valid or if there are network issues
1117    #[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    /// Set a user to be read-write
1128    ///
1129    /// # Errors
1130    ///
1131    /// Returns an error if the ID isn't valid or if there are network issues
1132    #[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    /// Deactivate, but don't delete, a user's account
1143    ///
1144    /// # Errors
1145    ///
1146    /// * If there's a connectivity issue to Postgres, an error will result
1147    /// * If the user ID isn't valid, an error will result
1148    #[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    /// File types and number of files per type
1159    ///
1160    /// # Errors
1161    ///
1162    /// * If there's a connectivity issue to Postgres, an error will result
1163    #[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    /// Create a new label, returning the label ID
1174    ///
1175    /// # Errors
1176    ///
1177    /// * If there's a connectivity issue to Postgres, an error will result
1178    /// * If the parent label doesn't exist, an error will result
1179    /// * If the label name already exists, an error will result
1180    #[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    /// Edit a label name or parent
1191    ///
1192    /// # Errors
1193    ///
1194    /// * If there's a connectivity issue to Postgres, an error will result
1195    /// * If the parent label doesn't exist, an error will result
1196    /// * If the label name already exists, an error will result
1197    #[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    /// Return the ID for a given label name
1208    ///
1209    /// # Errors
1210    ///
1211    /// * If there's a connectivity issue to Postgres, an error will result
1212    /// * If the label ID is invalid, an error will result
1213    #[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    /// Associate an existing label by its IDs with a file.
1224    ///
1225    /// # Errors
1226    ///
1227    /// * Incorrect IDs will result in an error
1228    /// * If the file already has the given label associated
1229    /// * If there is a network or connection issue with Postgres
1230    #[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
1241/// Hash a password string with [Argon2]
1242///
1243/// # Errors
1244///
1245/// An error may result if Argon has an error
1246pub 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/// Generate a new, random API key
1255#[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        // create user malwaredbtesting with password 'malwaredbtesting';
1311        // create database malwaredbtesting owner malwaredbtesting;
1312        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            // Get the port number to run in Github CI
1316            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        // Re-create the Postgres object so we can do some clean-up
1373        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        // TODO: Figure out why SQLite makes the group_id = 2, but with Postgres it's 3.
1633        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        // Search with several fields
1785        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        // Ensure we get a partial result just with the magic
1803        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        // Should be valid yet return nothing since it's the wrong file type
1821        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        // Invalid search request should return empty results
1843        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        // Invalid search pagination should return empty results
1851        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        // Ensure a type representing an unknown type isn't found
1859        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        // Use this file as a stand-un for an unknown file type
2148        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}