Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  mod.rs   Sprache: unbekannt

 
Spracherkennung für: .rs vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

//! Common structures and functionality for the CLI.
//!
//! Note: Prefer placing CLI implementations next to the associated structures, if possible.
use core::cell::RefCell;
use std::rc::Rc;

use anyhow::bail;
use clap::{Args, ValueEnum};
use tracing::trace;

use crate::{
    common::{
        ChatServerGroup, ClientInfo, CspDeviceId, D2xDeviceId, DeviceCookie, ThreemaId,
        config::{
            Config, ConfigEnvironment, Flavor, OnPremConfig, OnPremLicense, WorkContext, WorkCredentials,
            WorkFlavor,
        },
        keys::{
            ClientKey, DeviceGroupKey, RawClientKey, RawDeviceGroupKey, RemoteSecretAuthenticationToken,
            RemoteSecretHash,
        },
    },
    csp::CspProtocolContextInit,
    csp_e2e::{CspE2eContextInit, D2xContextInit},
    d2m::D2mContext,
    model::provider::NonceStorage,
    protobuf,
    remote_secret::{
        monitor::{RemoteSecretMonitorContext, RemoteSecretVerifier},
        setup::RemoteSecretSetupContext,
    },
};

/// Config environment option for CLI, reduced to variants for Consumer and Work. Irrelevant for OnPrem.
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum ConfigEnvironmentOption {
    /// Sandbox environment.
    Sandbox,

    /// Production environment.
    Production,
}
impl From<ConfigEnvironmentOption> for ConfigEnvironment {
    fn from(config: ConfigEnvironmentOption) -> Self {
        match config {
            ConfigEnvironmentOption::Sandbox => ConfigEnvironment::Sandbox,
            ConfigEnvironmentOption::Production => ConfigEnvironment::Production,
        }
    }
}

/// Common configuration options for CLI.
#[derive(Debug, Args)]
#[group(required = true)]
pub struct CommonConfigOptions {
    /// Consumer environment.
    #[arg(long)]
    pub consumer: Option<ConfigEnvironmentOption>,

    /// Work environment.
    #[arg(long, requires = "credentials")]
    pub work: Option<ConfigEnvironmentOption>,

    /// OnPrem environment license URL, i.e.
    /// `threemaonprem://license?username=<username>&password=<password>&server=<oppf-file-url>`.
    #[arg(long)]
    pub onprem: Option<String>,

    /// Work / OnPrem credentials, i.e. `<username:password>`.
    #[arg(
        long,
        requires = "work",
        value_parser = WorkCredentials::from_colon_separated_str,
    )]
    pub credentials: Option<WorkCredentials>,
}

/// Common configuration for the CLI.
pub struct CommonConfig {
    /// Configuration.
    pub config: Rc<Config>,

    /// General flavour of the application.
    pub flavor: Flavor,
}
impl CommonConfig {
    /// Create a [`CommonConfig`] from [`CommonConfigOptions`].
    ///
    /// Note: This is only an asynchronous process for an OnPrem configuration in which case the OPPF file
    /// will be downloaded and verified.
    ///
    /// # Errors
    ///
    /// Returns an error if the options are invalid, or in case of an OnPrem configuration in case the
    /// OPPF file could not be downloaded or verified.
    pub async fn from_options(
        http_client: &reqwest::Client,
        options: CommonConfigOptions,
    ) -> anyhow::Result<Self> {
        let config = match (
            options.consumer,
            options.work,
            options.onprem,
            options.credentials,
        ) {
            (Some(consumer_config), None, None, None) => Self {
                config: Rc::new(Config::from(ConfigEnvironment::from(consumer_config))),
                flavor: Flavor::Consumer,
            },

            (None, Some(work_config), None, Some(credentials)) => Self {
                config: Rc::new(Config::from(ConfigEnvironment::from(work_config))),
                flavor: Flavor::Work(WorkContext {
                    credentials: credentials.clone(),
                    flavor: WorkFlavor::Work,
                }),
            },

            (None, None, Some(onprem_license_url), None) => {
                // Parse license URL
                trace!(onprem_license_url, "Decoding license URL");
                let onprem_license = OnPremLicense::from_url(&onprem_license_url)?;

                // Download and decode configuration
                trace!(?onprem_license, "Retrieving OnPrem configuration");
                let onprem_config = OnPremConfig::decode_and_verify(
                    &onprem_license.download_configuration(http_client).await?,
                )?;

                // Apply configuration
                trace!(?onprem_config, "Applying OnPrem configuration");
                Self {
                    config: Rc::new(Config::from(ConfigEnvironment::OnPrem(Box::new(onprem_config)))),
                    flavor: Flavor::Work(onprem_license.work_context),
                }
            },

            _ => bail!("Configuration constraints should have been handled by clap"),
        };
        Ok(config)
    }
}

/// Minimal configuration options for an identity.
#[derive(Debug, Args)]
pub struct MinimalIdentityConfigOptions {
    /// Common config.
    #[command(flatten)]
    pub common: CommonConfigOptions,

    /// Threema ID.
    #[arg(long)]
    pub threema_id: ThreemaId,

    /// Client key (32 bytes, hex encoded).
    #[arg(long, value_parser = RawClientKey::from_hex)]
    pub client_key: RawClientKey,
}

/// D2X configuration options.
#[derive(Debug, Args)]
#[group(
    required = false,
    requires_all = ["d2x_device_id", "csp_device_id", "device_group_key", "expected_device_slot_state"],
)]
pub struct D2xConfigOptions {
    /// D2X device ID.
    #[arg(long)]
    pub d2x_device_id: Option<D2xDeviceId>,

    /// CSP device ID.
    #[arg(long)]
    pub csp_device_id: Option<CspDeviceId>,

    /// D2X device group key.
    #[arg(long, value_parser = RawDeviceGroupKey::from_hex)]
    pub device_group_key: Option<RawDeviceGroupKey>,

    /// Expected device slot state.
    #[arg(long)]
    pub expected_device_slot_state: Option<protobuf::d2m::DeviceSlotState>,
}

/// Combination of common configuration options, CSP and D2X identity configuration for the CLI.
#[derive(Debug, Args)]
pub struct FullIdentityConfigOptions {
    /// Identity config.
    #[command(flatten)]
    pub minimal: MinimalIdentityConfigOptions,

    /// CSP server group.
    #[arg(long)]
    pub csp_server_group: ChatServerGroup,

    /// Optional CSP device cookie.
    #[arg(long)]
    pub csp_device_cookie: Option<DeviceCookie>,

    /// Optional D2X configuration.
    #[command(flatten)]
    pub d2x_config: D2xConfigOptions,
}

/// Minimal identity configuration for the CLI.
pub struct MinimalIdentityConfig {
    /// Common configuration.
    pub common: CommonConfig,

    /// The user's identity.
    pub user_identity: ThreemaId,

    /// Client key.
    pub client_key: RawClientKey,
}
impl MinimalIdentityConfig {
    /// Create an [`MinimalIdentityConfig`] from [`MinimalIdentityConfigOptions`].
    ///
    /// Note: This is only an asynchronous process for an OnPrem configuration in which case the OPPF file
    /// will be downloaded and verified.
    ///
    /// # Errors
    ///
    /// Returns an error if the options are invalid, or in case of an OnPrem configuration in case the
    /// OPPF file could not be downloaded or verified.
    pub async fn from_options(
        http_client: &reqwest::Client,
        options: MinimalIdentityConfigOptions,
    ) -> anyhow::Result<Self> {
        Ok(Self {
            common: CommonConfig::from_options(http_client, options.common).await?,
            user_identity: options.threema_id,
            client_key: options.client_key,
        })
    }

    /// Generate a [`RemoteSecretSetupContext`] from the config.
    ///
    /// # Errors
    ///
    /// Returns an error if the configuration flavor is not Work (or OnPrem).
    pub fn remote_secret_setup_context(&self) -> anyhow::Result<RemoteSecretSetupContext> {
        let Flavor::Work(work_context) = &self.common.flavor else {
            bail!("Remote secret context only available for 'Work' flavor");
        };
        Ok(RemoteSecretSetupContext {
            client_info: ClientInfo::Libthreema,
            work_server_url: self.common.config.work_server_url.clone(),
            work_context: work_context.clone(),
            user_identity: self.user_identity,
            client_key: ClientKey::from(&self.client_key),
        })
    }

    /// Generate a [`RemoteSecretMonitorContext`] from the config.
    ///
    /// # Errors
    ///
    /// Returns an error if the configuration flavor is not Work (or OnPrem).
    #[must_use]
    pub fn remote_secret_monitor_context(
        &self,
        remote_secret_authentication_token: RemoteSecretAuthenticationToken,
        remote_secret_hash: RemoteSecretHash,
    ) -> RemoteSecretMonitorContext {
        RemoteSecretMonitorContext {
            client_info: ClientInfo::Libthreema,
            work_server_url: self.common.config.work_server_url.clone(),
            remote_secret_authentication_token,
            remote_secret_verifier: RemoteSecretVerifier::RemoteSecretHash(remote_secret_hash),
        }
    }
}

/// D2X configuration for the CLI.
pub struct D2xConfig {
    /// The device's D2X ID.
    pub d2x_device_id: D2xDeviceId,

    /// The device's CSP ID.
    pub csp_device_id: CspDeviceId,

    /// The device group key.
    pub device_group_key: RawDeviceGroupKey,

    /// The expected device slot state.
    pub expected_device_slot_state: protobuf::d2m::DeviceSlotState,
}
impl From<D2xConfigOptions> for Option<D2xConfig> {
    fn from(options: D2xConfigOptions) -> Self {
        if let (
            Some(d2x_device_id),
            Some(csp_device_id),
            Some(device_group_key),
            Some(expected_device_slot_state),
        ) = (
            options.d2x_device_id,
            options.csp_device_id,
            options.device_group_key,
            options.expected_device_slot_state,
        ) {
            Some(D2xConfig {
                d2x_device_id,
                csp_device_id,
                device_group_key,
                expected_device_slot_state,
            })
        } else {
            None
        }
    }
}

/// Common, CSP and D2X identity configuration for the CLI.
pub struct FullIdentityConfig {
    /// Minimal identity configuration.
    pub minimal: MinimalIdentityConfig,

    /// CSP server group.
    pub csp_server_group: ChatServerGroup,

    /// Optional CSP device cookie.
    pub csp_device_cookie: Option<DeviceCookie>,

    /// Optional D2X configuration.
    pub d2x_config: Option<D2xConfig>,
}
impl FullIdentityConfig {
    /// Create an [`FullIdentityConfig`] from [`FullIdentityConfigOptions`].
    ///
    /// Note: This is only an asynchronous process for an OnPrem configuration in which case the OPPF file
    /// will be downloaded and verified.
    ///
    /// # Errors
    ///
    /// Returns an error if the options are invalid, or in case of an OnPrem configuration in case the
    /// OPPF file could not be downloaded or verified.
    pub async fn from_options(
        http_client: &reqwest::Client,
        options: FullIdentityConfigOptions,
    ) -> anyhow::Result<Self> {
        let minimal = MinimalIdentityConfig::from_options(http_client, options.minimal).await?;
        let d2x_config: Option<D2xConfig> = options.d2x_config.into();
        Ok(Self {
            minimal,
            csp_server_group: options.csp_server_group,
            csp_device_cookie: options.csp_device_cookie,
            d2x_config,
        })
    }

    /// Generate a [`CspProtocolContextInit`] from the config.
    ///
    /// # Errors
    ///
    /// Returns an error if the configuration is invalid.
    #[must_use]
    pub fn csp_context_init(&self) -> CspProtocolContextInit {
        CspProtocolContextInit {
            permanent_server_keys: self.minimal.common.config.chat_server_public_keys.clone(),
            identity: self.minimal.user_identity,
            client_key: ClientKey::from(&self.minimal.client_key),
            client_info: ClientInfo::Libthreema,
            device_cookie: self.csp_device_cookie,
            csp_device_id: self
                .d2x_config
                .as_ref()
                .map(|d2x_config| d2x_config.csp_device_id),
        }
    }

    /// Generate a [`CspE2eContextInit`] from the config.
    #[must_use]
    pub fn csp_e2e_context_init(&self, nonce_storage: Box<RefCell<dyn NonceStorage>>) -> CspE2eContextInit {
        CspE2eContextInit {
            user_identity: self.minimal.user_identity,
            client_key: ClientKey::from(&self.minimal.client_key),
            flavor: self.minimal.common.flavor.clone(),
            nonce_storage,
        }
    }

    /// Generate a [`D2mContext`] from the config, if multi-device has been enabled by the config.
    #[must_use]
    pub fn d2m_context(&self) -> Option<D2mContext> {
        let mediator_server_url = self
            .minimal
            .common
            .config
            .multi_device
            .as_ref()
            .map(|config| config.mediator_server_url.clone())?;

        self.d2x_config.as_ref().map(|d2x_config| D2mContext {
            mediator_server_url,
            device_group_key: DeviceGroupKey::from(&d2x_config.device_group_key),
            csp_server_group: self.csp_server_group,
            device_id: d2x_config.d2x_device_id,
            device_slots_exhausted_policy:
                protobuf::d2m::client_hello::DeviceSlotsExhaustedPolicy::DropLeastRecent,
            device_slot_expiration_policy: protobuf::d2m::DeviceSlotExpirationPolicy::Persistent,
            expected_device_slot_state: d2x_config.expected_device_slot_state,
            client_info: ClientInfo::Libthreema,
            device_label: None,
        })
    }

    /// Generate a [`D2xContextInit`] from the config, if multi-device has been enabled by the config.
    #[must_use]
    pub fn d2x_context_init(&self, nonce_storage: Box<RefCell<dyn NonceStorage>>) -> Option<D2xContextInit> {
        self.d2x_config.as_ref().map(|d2x_config| D2xContextInit {
            device_id: d2x_config.d2x_device_id,
            device_group_key: DeviceGroupKey::from(&d2x_config.device_group_key),
            nonce_storage,
        })
    }
}

[Dauer der Verarbeitung: 0.27 Sekunden, vorverarbeitet 2026-04-27]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge