malwaredb 0.3.6

Service for storing malicious, benign, or unknown files and related metadata and relationships.
// SPDX-License-Identifier: Apache-2.0

use malwaredb_server::State;

use std::process::ExitCode;

use anyhow::Result;
use clap::Parser;
use dialoguer::{Confirm, Password};

/// Create a new user
#[derive(Clone, Debug, Parser, PartialEq)]
pub struct Create {
    /// Username
    #[arg(short, long)]
    pub uname: String,

    /// Email address
    #[arg(short, long)]
    pub email: String,

    /// First name
    #[arg(short, long)]
    pub fname: String,

    /// Last name
    #[arg(short, long)]
    pub lname: String,

    /// Organisation
    #[arg(short, long)]
    pub org: Option<String>,

    /// Prompt for the user's password (or leave empty)
    #[arg(short, long, default_value = "false")]
    pub password: bool,

    /// Create a read-only account, usually for guest access or demonstration
    #[arg(short, long, default_value_t = false)]
    pub readonly: bool,
}

impl Create {
    pub async fn execute(&self, state: State) -> Result<ExitCode> {
        let password = if self.password {
            let password = Password::new()
                .with_prompt(format!("New Password for {}", self.uname))
                .with_confirmation("Confirm password", "Passwords mismatching")
                .interact()?;
            Some(password)
        } else {
            None
        };

        state
            .db_type
            .create_user(
                &self.uname,
                &self.fname,
                &self.lname,
                &self.email,
                password,
                self.org.as_ref(),
                self.readonly,
            )
            .await?;

        Ok(ExitCode::SUCCESS)
    }
}

/// Reset (clear) all API tokens to force re-login
#[derive(Clone, Debug, Parser, PartialEq)]
pub struct ResetAPIKeys {
    /// Bypass confirmation prompt
    #[arg(short, long, default_value = "false")]
    pub y: bool,
}

impl ResetAPIKeys {
    pub async fn execute(&self, state: State) -> Result<ExitCode> {
        if self.y
            || Confirm::new()
                .with_prompt("Do you want to continue?")
                .interact()?
        {
            let reset = state.db_type.reset_api_keys().await?;
            println!("Cleared {reset} API keys");
        } else {
            println!("Cancelled.");
        }

        Ok(ExitCode::SUCCESS)
    }
}

/// Change a user's password
#[derive(Clone, Debug, Parser, PartialEq)]
pub struct ResetPassword {
    /// Username
    #[arg(short, long)]
    pub uname: String,
}

impl ResetPassword {
    pub async fn execute(&self, state: State) -> Result<ExitCode> {
        let password = Password::new()
            .with_prompt(format!("New Password for {}", self.uname))
            .with_confirmation("Confirm password", "Passwords mismatching")
            .interact()?;
        state.db_type.set_password(&self.uname, &password).await?;

        Ok(ExitCode::SUCCESS)
    }
}

/// List users
#[derive(Clone, Debug, Parser, PartialEq)]
pub struct List {}

impl List {
    pub async fn execute(&self, state: State) -> Result<ExitCode> {
        for user in state.db_type.list_users().await? {
            println!("{user}");
        }

        Ok(ExitCode::SUCCESS)
    }
}

/// Toggle read-only status for a user account
#[derive(Clone, Debug, Parser, PartialEq)]
pub struct ToggleReadOnly {
    /// User ID to have R/O status toggled
    pub uid: u32,
}

impl ToggleReadOnly {
    pub async fn execute(&self, state: State) -> Result<ExitCode> {
        let uinfo = state.db_type.get_user_info(self.uid).await?;

        if uinfo.is_readonly {
            state.db_type.set_user_rw(self.uid).await?;
            println!("{} is now read-write", uinfo.username);
        } else {
            state.db_type.set_user_ro(self.uid).await?;
            println!("{} is now read-only", uinfo.username);
        }

        Ok(ExitCode::SUCCESS)
    }
}

/// Indicate the user account to be used to represent anonymous users
#[cfg(feature = "anonymous")]
#[derive(Clone, Debug, Parser, PartialEq)]
pub struct SpecifyAnonymousUser {
    /// Enable anonymous user access by specifying the user ID
    pub uid: u32,
}

#[cfg(feature = "anonymous")]
impl SpecifyAnonymousUser {
    pub async fn execute(&self, state: State) -> Result<ExitCode> {
        if let Some(uid) = state.db_config.anonymous_uid {
            let uname = state.db_type.get_user_info(uid).await?.username;
            eprintln!("Current anonymous user is {uname} with uid {uid}");
        }

        let anon_info = state.db_type.get_user_info(self.uid).await?;
        if anon_info.is_admin {
            eprintln!("Refusing to allow an admin account to be used for anonymous connections!");
            return Ok(ExitCode::FAILURE);
        }

        state.db_type.set_anonymous_user(self.uid).await?;
        println!("Anonymous access enabled as user {}", anon_info.username);

        Ok(ExitCode::SUCCESS)
    }
}

/// Clear the anonymous user setting, effectively disabling anonymous user access
#[cfg(feature = "anonymous")]
pub async fn clear_anonymous_user(state: State) -> Result<ExitCode> {
    state.db_type.clear_anonymous_user().await?;
    println!("Anonymous user access disabled");

    Ok(ExitCode::SUCCESS)
}