wasm: dedupe capability resolution

This commit is contained in:
Marco Allegretti 2026-02-02 11:33:24 +01:00
parent 91d2f79740
commit 3aba16a8e8

View file

@ -23,33 +23,14 @@ pub struct WasmPlugin {
limits: ExecutionLimits, limits: ExecutionLimits,
} }
impl WasmPlugin { async fn capabilities_for_manifest(
/// Creates a new WASM plugin from a manifest and compiled module. pool: &PgPool,
pub fn new( community_id: Option<Uuid>,
package_id: Uuid, manifest_capabilities: &[String],
manifest: PluginManifest, ) -> Result<Vec<Capability>, PluginError> {
compiled: Arc<CompiledPlugin>,
) -> Self {
Self {
package_id,
manifest,
compiled,
limits: ExecutionLimits::default(),
}
}
/// Sets custom execution limits for this plugin.
#[allow(dead_code)] // API for future use
pub fn with_limits(mut self, limits: ExecutionLimits) -> Self {
self.limits = limits;
self
}
async fn capabilities_for(&self, pool: &PgPool, ctx: &HookContext) -> Result<Vec<Capability>, PluginError> {
let mut out: Vec<Capability> = Vec::new(); let mut out: Vec<Capability> = Vec::new();
// Community policy influences outbound HTTP. let (allow_http, allowlist) = if let Some(cid) = community_id {
let (allow_http, allowlist) = if let Some(cid) = ctx.community_id {
let row = sqlx::query!( let row = sqlx::query!(
r#"SELECT settings as "settings!: serde_json::Value" FROM communities WHERE id = $1"#, r#"SELECT settings as "settings!: serde_json::Value" FROM communities WHERE id = $1"#,
cid cid
@ -83,7 +64,7 @@ impl WasmPlugin {
(false, Vec::new()) (false, Vec::new())
}; };
for cap in &self.manifest.capabilities { for cap in manifest_capabilities {
match cap.as_str() { match cap.as_str() {
CAP_OUTBOUND_HTTP => { CAP_OUTBOUND_HTTP => {
let allowed = allow_http && !allowlist.is_empty(); let allowed = allow_http && !allowlist.is_empty();
@ -101,7 +82,6 @@ impl WasmPlugin {
}); });
} }
_ => { _ => {
// Unknown capability is denied by default.
out.push(Capability { out.push(Capability {
name: cap.clone(), name: cap.clone(),
allowed: false, allowed: false,
@ -112,6 +92,32 @@ impl WasmPlugin {
} }
Ok(out) Ok(out)
}
impl WasmPlugin {
/// Creates a new WASM plugin from a manifest and compiled module.
pub fn new(
package_id: Uuid,
manifest: PluginManifest,
compiled: Arc<CompiledPlugin>,
) -> Self {
Self {
package_id,
manifest,
compiled,
limits: ExecutionLimits::default(),
}
}
/// Sets custom execution limits for this plugin.
#[allow(dead_code)] // API for future use
pub fn with_limits(mut self, limits: ExecutionLimits) -> Self {
self.limits = limits;
self
}
async fn capabilities_for(&self, pool: &PgPool, ctx: &HookContext) -> Result<Vec<Capability>, PluginError> {
capabilities_for_manifest(pool, ctx.community_id, &self.manifest.capabilities).await
} }
async fn create_instance(&self, ctx: &HookContext) -> Result<PluginInstance, PluginError> { async fn create_instance(&self, ctx: &HookContext) -> Result<PluginInstance, PluginError> {
@ -173,66 +179,13 @@ impl Plugin for WasmPlugin {
let lim = limits_clone.clone(); let lim = limits_clone.clone();
Box::pin(async move { Box::pin(async move {
let (allow_http, allowlist) = if let Some(cid) = ctx.community_id { let capabilities = capabilities_for_manifest(
let row = sqlx::query!( &ctx.pool,
r#"SELECT settings as "settings!: serde_json::Value" FROM communities WHERE id = $1"#, ctx.community_id,
cid &manifest_capabilities,
) )
.fetch_optional(&ctx.pool)
.await?; .await?;
if let Some(row) = row {
let allow_http = row
.settings
.get("plugin_allow_outbound_http")
.and_then(|v: &serde_json::Value| v.as_bool())
.unwrap_or(false);
let allowlist: Vec<String> = row
.settings
.get("plugin_http_egress_allowlist")
.and_then(|v: &serde_json::Value| v.as_array())
.map(|arr: &Vec<serde_json::Value>| {
arr.iter()
.filter_map(|v: &serde_json::Value| v.as_str().map(|s: &str| s.to_string()))
.collect()
})
.unwrap_or_default();
(allow_http, allowlist)
} else {
(false, Vec::new())
}
} else {
(false, Vec::new())
};
let mut capabilities: Vec<Capability> = Vec::new();
for cap in &manifest_capabilities {
match cap.as_str() {
CAP_OUTBOUND_HTTP => {
let allowed = allow_http && !allowlist.is_empty();
capabilities.push(Capability {
name: cap.clone(),
allowed,
config: serde_json::json!({"allowlist": allowlist.clone()}),
});
}
CAP_SETTINGS | CAP_KV_STORE | CAP_EMIT_EVENTS => {
capabilities.push(Capability {
name: cap.clone(),
allowed: true,
config: serde_json::json!({}),
});
}
_ => capabilities.push(Capability {
name: cap.clone(),
allowed: false,
config: serde_json::json!({}),
}),
}
}
let host_state = HostState::new( let host_state = HostState::new(
plugin.clone(), plugin.clone(),
ctx.community_id, ctx.community_id,