use std::collections::HashSet;
use tracing::{debug, warn};
use crate::module::ipc::protocol::RequestPayload;
use crate::module::traits::ModuleError;
pub fn parse_permission_string(perm_str: &str) -> Option<Permission> {
match perm_str {
"read_blockchain" | "ReadBlockchain" => Some(Permission::ReadBlockchain),
"read_utxo" | "ReadUTXO" => Some(Permission::ReadUTXO),
"read_chain_state" | "ReadChainState" => Some(Permission::ReadChainState),
"subscribe_events" | "SubscribeEvents" => Some(Permission::SubscribeEvents),
"send_transactions" | "SendTransactions" => Some(Permission::SendTransactions),
"read_mempool" | "ReadMempool" => Some(Permission::ReadMempool),
"read_network" | "ReadNetwork" => Some(Permission::ReadNetwork),
"network_access" | "NetworkAccess" => Some(Permission::NetworkAccess),
"read_lightning" | "ReadLightning" => Some(Permission::ReadLightning),
"read_payment" | "ReadPayment" => Some(Permission::ReadPayment),
"read_storage" | "ReadStorage" => Some(Permission::ReadStorage),
"write_storage" | "WriteStorage" => Some(Permission::WriteStorage),
"manage_storage" | "ManageStorage" => Some(Permission::ManageStorage),
"read_filesystem" | "ReadFilesystem" => Some(Permission::ReadFilesystem),
"write_filesystem" | "WriteFilesystem" => Some(Permission::WriteFilesystem),
"manage_filesystem" | "ManageFilesystem" => Some(Permission::ManageFilesystem),
"register_rpc_endpoint" | "RegisterRpcEndpoint" => Some(Permission::RegisterRpcEndpoint),
"manage_timers" | "ManageTimers" => Some(Permission::ManageTimers),
"report_metrics" | "ReportMetrics" => Some(Permission::ReportMetrics),
"read_metrics" | "ReadMetrics" => Some(Permission::ReadMetrics),
"discover_modules" | "DiscoverModules" => Some(Permission::DiscoverModules),
"publish_events" | "PublishEvents" => Some(Permission::PublishEvents),
"call_module" | "CallModule" => Some(Permission::CallModule),
"register_module_api" | "RegisterModuleApi" => Some(Permission::RegisterModuleApi),
"submit_block" | "SubmitBlock" => Some(Permission::SubmitBlock),
_ => None,
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Permission {
ReadBlockchain,
ReadUTXO,
SubscribeEvents,
SendTransactions,
ReadChainState,
ReadMempool,
ReadNetwork,
NetworkAccess,
ReadLightning,
ReadPayment,
ReadStorage,
WriteStorage,
ManageStorage,
ReadFilesystem,
WriteFilesystem,
ManageFilesystem,
RegisterRpcEndpoint,
ManageTimers,
ReportMetrics,
ReadMetrics,
DiscoverModules,
PublishEvents,
CallModule,
RegisterModuleApi,
SubmitBlock,
}
#[derive(Debug, Clone, Default)]
pub struct PermissionSet {
permissions: HashSet<Permission>,
}
impl PermissionSet {
pub fn new() -> Self {
Self {
permissions: HashSet::new(),
}
}
pub fn from_vec(permissions: Vec<Permission>) -> Self {
Self {
permissions: permissions.into_iter().collect(),
}
}
pub fn add(&mut self, permission: Permission) {
self.permissions.insert(permission);
}
pub fn has(&self, permission: &Permission) -> bool {
self.permissions.contains(permission)
}
pub fn has_all(&self, required: &[Permission]) -> bool {
required.iter().all(|p| self.permissions.contains(p))
}
pub fn to_vec(&self) -> Vec<Permission> {
self.permissions.iter().cloned().collect()
}
}
pub struct PermissionChecker {
default_permissions: PermissionSet,
module_permissions: std::collections::HashMap<String, PermissionSet>,
#[allow(dead_code)]
payload_to_permission_cache: std::collections::HashMap<std::any::TypeId, Permission>,
}
impl PermissionChecker {
pub fn new() -> Self {
let mut default = PermissionSet::new();
default.add(Permission::ReadBlockchain);
default.add(Permission::ReadUTXO);
default.add(Permission::ReadChainState);
default.add(Permission::SubscribeEvents);
Self {
default_permissions: default,
module_permissions: std::collections::HashMap::new(),
payload_to_permission_cache: std::collections::HashMap::new(),
}
}
pub fn register_module_permissions(&mut self, module_id: String, permissions: PermissionSet) {
debug!(
"Registering permissions for module {}: {:?}",
module_id,
permissions.to_vec()
);
self.module_permissions.insert(module_id, permissions);
}
pub fn unregister_module_permissions(&mut self, module_id: &str) {
if self.module_permissions.remove(module_id).is_some() {
debug!("Unregistered permissions for module {}", module_id);
}
}
#[inline]
pub fn check_permission(&self, module_id: &str, permission: &Permission) -> bool {
if let Some(module_perms) = self.module_permissions.get(module_id) {
if module_perms.has(permission) {
return true;
}
return false;
}
self.default_permissions.has(permission)
}
pub fn get_permissions(&self, module_id: &str) -> PermissionSet {
if let Some(module_perms) = self.module_permissions.get(module_id) {
module_perms.clone()
} else {
self.default_permissions.clone()
}
}
pub fn check_api_call(
&self,
module_id: &str,
payload: &RequestPayload,
) -> Result<(), ModuleError> {
let required_permission = match payload {
RequestPayload::Handshake { .. } => {
return Ok(());
}
RequestPayload::GetBlock { .. } => Permission::ReadBlockchain,
RequestPayload::GetBlockHeader { .. } => Permission::ReadBlockchain,
RequestPayload::GetTransaction { .. } => Permission::ReadBlockchain,
RequestPayload::HasTransaction { .. } => Permission::ReadBlockchain,
RequestPayload::GetChainTip => Permission::ReadChainState,
RequestPayload::GetBlockHeight => Permission::ReadChainState,
RequestPayload::GetUtxo { .. } => Permission::ReadUTXO,
RequestPayload::SubscribeEvents { .. } => Permission::SubscribeEvents,
RequestPayload::GetMempoolTransactions => Permission::ReadMempool,
RequestPayload::GetMempoolTransaction { .. } => Permission::ReadMempool,
RequestPayload::GetMempoolSize => Permission::ReadMempool,
RequestPayload::GetNetworkStats => Permission::ReadNetwork,
RequestPayload::GetNetworkPeers => Permission::ReadNetwork,
RequestPayload::GetChainInfo => Permission::ReadChainState,
RequestPayload::GetBlockByHeight { .. } => Permission::ReadBlockchain,
RequestPayload::GetLightningNodeUrl => Permission::ReadLightning,
RequestPayload::GetLightningInfo => Permission::ReadLightning,
RequestPayload::GetPaymentState { .. } => Permission::ReadPayment,
RequestPayload::CheckTransactionInMempool { .. } => Permission::ReadMempool,
RequestPayload::GetFeeEstimate { .. } => Permission::ReadMempool,
RequestPayload::ReadFile { .. } => Permission::ReadFilesystem,
RequestPayload::WriteFile { .. } => Permission::WriteFilesystem,
RequestPayload::DeleteFile { .. } => Permission::ManageFilesystem,
RequestPayload::ListDirectory { .. } => Permission::ReadFilesystem,
RequestPayload::CreateDirectory { .. } => Permission::ManageFilesystem,
RequestPayload::GetFileMetadata { .. } => Permission::ReadFilesystem,
RequestPayload::RegisterRpcEndpoint { .. } => Permission::RegisterRpcEndpoint,
RequestPayload::UnregisterRpcEndpoint { .. } => Permission::RegisterRpcEndpoint,
RequestPayload::RegisterTimer { .. } => Permission::ManageTimers,
RequestPayload::CancelTimer { .. } => Permission::ManageTimers,
RequestPayload::ScheduleTask { .. } => Permission::ManageTimers,
RequestPayload::ReportMetric { .. } => Permission::ReportMetrics,
RequestPayload::GetModuleMetrics { .. } => Permission::ReadMetrics,
RequestPayload::GetAllMetrics => Permission::ReadMetrics,
RequestPayload::GetModuleHealth { .. } => Permission::ReadMetrics,
RequestPayload::GetAllModuleHealth => Permission::ReadMetrics,
RequestPayload::ReportModuleHealth { .. } => Permission::ReportMetrics,
RequestPayload::SendMeshPacketToPeer { .. } => Permission::NetworkAccess,
RequestPayload::SendStratumV2MessageToPeer { .. } => Permission::NetworkAccess,
RequestPayload::DiscoverModules => Permission::DiscoverModules,
RequestPayload::GetModuleInfo { .. } => Permission::DiscoverModules,
RequestPayload::IsModuleAvailable { .. } => Permission::DiscoverModules,
RequestPayload::PublishEvent { .. } => Permission::PublishEvents,
RequestPayload::CallModule { .. } => Permission::CallModule,
RequestPayload::RegisterModuleApi { .. } => Permission::RegisterModuleApi,
RequestPayload::UnregisterModuleApi => Permission::RegisterModuleApi,
RequestPayload::GetBlockTemplate { .. } => Permission::ReadBlockchain,
RequestPayload::SubmitBlock { .. } => Permission::SubmitBlock,
RequestPayload::MergeBlockServeDenylist { .. } => Permission::NetworkAccess,
RequestPayload::GetBlockServeDenylistSnapshot => Permission::ReadNetwork,
RequestPayload::ClearBlockServeDenylist => Permission::NetworkAccess,
RequestPayload::ReplaceBlockServeDenylist { .. } => Permission::NetworkAccess,
RequestPayload::MergeTxServeDenylist { .. } => Permission::NetworkAccess,
RequestPayload::GetTxServeDenylistSnapshot => Permission::ReadNetwork,
RequestPayload::ClearTxServeDenylist => Permission::NetworkAccess,
RequestPayload::ReplaceTxServeDenylist { .. } => Permission::NetworkAccess,
RequestPayload::GetSyncStatus => Permission::ReadChainState,
RequestPayload::BanPeer { .. } => Permission::NetworkAccess,
RequestPayload::SetBlockServeMaintenanceMode { .. } => Permission::NetworkAccess,
RequestPayload::RegisterCliSpec { .. } => Permission::RegisterRpcEndpoint,
};
if !self.check_permission(module_id, &required_permission) {
warn!(
"Module {} denied access to {:?} (missing permission: {:?})",
module_id, payload, required_permission
);
return Err(ModuleError::OperationError(format!(
"Permission denied: module {module_id} does not have permission {required_permission:?}"
)));
}
debug!("Module {} granted access to {:?}", module_id, payload);
Ok(())
}
}
impl Default for PermissionChecker {
fn default() -> Self {
Self::new()
}
}