Track plugin registry audits
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use ely_domain::{DomainError, DownloadId, ProfileId, SpaceId, TabId};
|
||||
use ely_domain::{DomainError, DownloadId, PluginId, ProfileId, SpaceId, TabId};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Clone, Debug, Error, Eq, PartialEq)]
|
||||
@@ -21,6 +21,15 @@ pub enum CoreError {
|
||||
#[error("download target path is unavailable: {id}")]
|
||||
DownloadTargetPathUnavailable { id: DownloadId },
|
||||
|
||||
#[error("plugin already installed: {id}")]
|
||||
PluginAlreadyInstalled { id: PluginId },
|
||||
|
||||
#[error("plugin requires high-risk permission confirmation: {id}")]
|
||||
PluginHighRiskConfirmationRequired { id: PluginId },
|
||||
|
||||
#[error("plugin not found: {id}")]
|
||||
PluginNotFound { id: PluginId },
|
||||
|
||||
#[error("favorite limit reached: {limit}")]
|
||||
FavoriteLimitReached { limit: usize },
|
||||
|
||||
|
||||
@@ -3,4 +3,7 @@ mod navigation;
|
||||
mod state;
|
||||
|
||||
pub use error::CoreError;
|
||||
pub use state::{BrowserCore, BrowserSnapshot, InitialBrowserConfig};
|
||||
pub use state::{
|
||||
BrowserCore, BrowserSnapshot, InitialBrowserConfig, InstalledPlugin, PluginAuditAction,
|
||||
PluginAuditEvent,
|
||||
};
|
||||
|
||||
@@ -10,9 +10,12 @@ use crate::CoreError;
|
||||
mod commands;
|
||||
mod downloads;
|
||||
mod history;
|
||||
mod plugins;
|
||||
mod profiles;
|
||||
mod tabs;
|
||||
|
||||
pub use plugins::{InstalledPlugin, PluginAuditAction, PluginAuditEvent};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct InitialBrowserConfig {
|
||||
pub space_name: String,
|
||||
@@ -40,6 +43,8 @@ pub struct BrowserSnapshot {
|
||||
pub archived_tabs: Vec<ArchivedTab>,
|
||||
pub download_entries: Vec<DownloadEntry>,
|
||||
pub history_entries: Vec<HistoryEntry>,
|
||||
pub installed_plugins: Vec<InstalledPlugin>,
|
||||
pub plugin_audit_events: Vec<PluginAuditEvent>,
|
||||
pub spaces: Vec<Space>,
|
||||
pub active_tab_id: TabId,
|
||||
pub active_space_id: SpaceId,
|
||||
@@ -57,6 +62,8 @@ pub struct BrowserCore {
|
||||
archived_tabs: Vec<ArchivedTab>,
|
||||
download_entries: Vec<DownloadEntry>,
|
||||
history_entries: Vec<HistoryEntry>,
|
||||
installed_plugins: Vec<InstalledPlugin>,
|
||||
plugin_audit_events: Vec<PluginAuditEvent>,
|
||||
active_space_id: SpaceId,
|
||||
active_profile_id: ProfileId,
|
||||
active_tab_id: TabId,
|
||||
@@ -99,6 +106,8 @@ impl BrowserCore {
|
||||
archived_tabs: Vec::new(),
|
||||
download_entries: Vec::new(),
|
||||
history_entries: Vec::new(),
|
||||
installed_plugins: Vec::new(),
|
||||
plugin_audit_events: Vec::new(),
|
||||
command_query: String::new(),
|
||||
new_tab_url,
|
||||
})
|
||||
@@ -186,6 +195,8 @@ impl BrowserCore {
|
||||
archived_tabs: self.archived_tabs.clone(),
|
||||
download_entries: self.visible_downloads(),
|
||||
history_entries: self.visible_history(),
|
||||
installed_plugins: self.installed_plugins.clone(),
|
||||
plugin_audit_events: self.plugin_audit_events.clone(),
|
||||
spaces: self.spaces.clone(),
|
||||
tabs: self.visible_tabs(),
|
||||
active_tab_id: self.active_tab_id.clone(),
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
use std::time::SystemTime;
|
||||
|
||||
use ely_domain::{PluginId, PluginManifest};
|
||||
|
||||
use crate::CoreError;
|
||||
|
||||
use super::BrowserCore;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct InstalledPlugin {
|
||||
manifest: PluginManifest,
|
||||
enabled: bool,
|
||||
high_risk_confirmed: bool,
|
||||
installed_at: SystemTime,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum PluginAuditAction {
|
||||
Installed,
|
||||
Enabled,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct PluginAuditEvent {
|
||||
plugin_id: PluginId,
|
||||
action: PluginAuditAction,
|
||||
created_at: SystemTime,
|
||||
}
|
||||
|
||||
impl InstalledPlugin {
|
||||
fn new(manifest: PluginManifest, high_risk_confirmed: bool, installed_at: SystemTime) -> Self {
|
||||
Self { manifest, enabled: true, high_risk_confirmed, installed_at }
|
||||
}
|
||||
|
||||
fn set_enabled(&mut self, enabled: bool) {
|
||||
self.enabled = enabled;
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn manifest(&self) -> &PluginManifest {
|
||||
&self.manifest
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn id(&self) -> &PluginId {
|
||||
self.manifest.id()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn enabled(&self) -> bool {
|
||||
self.enabled
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn high_risk_confirmed(&self) -> bool {
|
||||
self.high_risk_confirmed
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn installed_at(&self) -> SystemTime {
|
||||
self.installed_at
|
||||
}
|
||||
}
|
||||
|
||||
impl PluginAuditEvent {
|
||||
fn record(plugin_id: PluginId, action: PluginAuditAction, created_at: SystemTime) -> Self {
|
||||
Self { plugin_id, action, created_at }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn plugin_id(&self) -> &PluginId {
|
||||
&self.plugin_id
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn action(&self) -> &PluginAuditAction {
|
||||
&self.action
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn created_at(&self) -> SystemTime {
|
||||
self.created_at
|
||||
}
|
||||
}
|
||||
|
||||
impl BrowserCore {
|
||||
pub fn install_plugin(
|
||||
&mut self,
|
||||
manifest: PluginManifest,
|
||||
high_risk_confirmed: bool,
|
||||
) -> Result<PluginId, CoreError> {
|
||||
let plugin_id = manifest.id().clone();
|
||||
if self.plugin_installed(&plugin_id) {
|
||||
return Err(CoreError::PluginAlreadyInstalled { id: plugin_id });
|
||||
}
|
||||
if manifest.high_risk_permissions().next().is_some() && !high_risk_confirmed {
|
||||
return Err(CoreError::PluginHighRiskConfirmationRequired { id: plugin_id });
|
||||
}
|
||||
|
||||
self.installed_plugins.push(InstalledPlugin::new(
|
||||
manifest,
|
||||
high_risk_confirmed,
|
||||
SystemTime::now(),
|
||||
));
|
||||
self.record_plugin_audit_event(plugin_id.clone(), PluginAuditAction::Installed);
|
||||
Ok(plugin_id)
|
||||
}
|
||||
|
||||
pub fn enable_plugin(&mut self, plugin_id: &PluginId) -> Result<(), CoreError> {
|
||||
self.plugin_mut(plugin_id)?.set_enabled(true);
|
||||
self.record_plugin_audit_event(plugin_id.clone(), PluginAuditAction::Enabled);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn disable_plugin(&mut self, plugin_id: &PluginId) -> Result<(), CoreError> {
|
||||
self.plugin_mut(plugin_id)?.set_enabled(false);
|
||||
self.record_plugin_audit_event(plugin_id.clone(), PluginAuditAction::Disabled);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn record_plugin_audit_event(&mut self, plugin_id: PluginId, action: PluginAuditAction) {
|
||||
self.plugin_audit_events.push(PluginAuditEvent::record(
|
||||
plugin_id,
|
||||
action,
|
||||
SystemTime::now(),
|
||||
));
|
||||
}
|
||||
|
||||
fn plugin_installed(&self, plugin_id: &PluginId) -> bool {
|
||||
self.installed_plugins.iter().any(|plugin| plugin.id() == plugin_id)
|
||||
}
|
||||
|
||||
fn plugin_mut(&mut self, plugin_id: &PluginId) -> Result<&mut InstalledPlugin, CoreError> {
|
||||
self.installed_plugins
|
||||
.iter_mut()
|
||||
.find(|plugin| plugin.id() == plugin_id)
|
||||
.ok_or_else(|| CoreError::PluginNotFound { id: plugin_id.clone() })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user