use std::path::Path;
use nichlink_run_method::{
PluginArtifact, PluginAssurance, PluginCatalog, PluginChannel, PluginDecision, PluginPolicy,
PluginSignatureVerifier, PluginSource, PluginTrustPolicy, VerifiedPluginArtifact,
};
use crate::HostError;
pub const PLUGIN_LOCK_DIRECTORY: &str = ".nichlink/plugins";
pub const OFFICIAL_LOCK: &str = "official.lock";
pub const USER_LOCK: &str = "user.lock";
pub fn plugin_catalog(package_root: &Path) -> Result<PluginCatalog, HostError> {
let directory = package_root.join(PLUGIN_LOCK_DIRECTORY);
let mut lock = String::new();
for name in [OFFICIAL_LOCK, USER_LOCK] {
let path = directory.join(name);
match std::fs::read_to_string(&path) {
Ok(text) => {
lock.push_str(&text);
lock.push('\n');
}
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
Err(error) => {
return Err(HostError::Policy(format!(
"cannot read {}: {error}",
path.display()
)));
}
}
}
PluginCatalog::parse(&lock).map_err(|error| {
HostError::Policy(format!(
"invalid plugin lock under {}: {error}",
directory.display()
))
})
}
pub fn lane_for(artifact: &VerifiedPluginArtifact) -> PluginChannel {
match artifact.assurance() {
PluginAssurance::Signature => PluginChannel::Official,
PluginAssurance::Digest => PluginChannel::Community,
}
}
pub struct PluginAdmission<V> {
policy: PluginPolicy,
trust: PluginTrustPolicy,
catalog: PluginCatalog,
verifier: V,
}
impl<V: PluginSignatureVerifier> PluginAdmission<V> {
pub fn new(
policy: PluginPolicy,
trust: PluginTrustPolicy,
catalog: PluginCatalog,
verifier: V,
) -> Self {
Self {
policy,
trust,
catalog,
verifier,
}
}
pub fn from_package_root(
package_root: &Path,
policy: PluginPolicy,
trust: PluginTrustPolicy,
verifier: V,
) -> Result<Self, HostError> {
Ok(Self::new(
policy,
trust,
plugin_catalog(package_root)?,
verifier,
))
}
pub fn catalog(&self) -> &PluginCatalog {
&self.catalog
}
pub fn admit(&self, artifact: PluginArtifact) -> Result<VerifiedPluginArtifact, HostError> {
let manifest = artifact
.registration
.plugin
.ok_or_else(|| HostError::Policy("plugin artifact has no manifest".to_owned()))?;
match self.policy.decision(manifest, Some(&self.catalog)) {
PluginDecision::Accepted => {}
PluginDecision::Rejected(reason) => {
return Err(HostError::Policy(format!(
"plugin rejected by the selection policy: {reason}"
)));
}
}
let verified = match manifest.source {
PluginSource::Official => artifact.verify_signed(self.trust, &self.verifier),
PluginSource::User => artifact.verify_artifact(self.trust),
};
verified.map_err(|error| {
HostError::Policy(format!("plugin refused by the trust policy: {error}"))
})
}
}
#[cfg(feature = "wasm")]
impl<V: PluginSignatureVerifier> PluginAdmission<V> {
pub fn install(
&self,
table: &crate::WasmPluginTable,
slot: &str,
artifact: PluginArtifact,
) -> Result<u64, HostError> {
let verified = self.admit(artifact)?;
let channel = lane_for(&verified);
table.install(slot, channel, verified)
}
}
#[cfg(feature = "process-tools")]
impl<V: PluginSignatureVerifier> PluginAdmission<V> {
pub fn load_process(
&self,
backend: &crate::ProcessBackend,
program: crate::ProcessProgram,
artifact: PluginArtifact,
) -> Result<crate::ProcessInstance, HostError> {
let verified = self.admit(artifact)?;
backend.load(verified, program)
}
}