Files
k-os/crates/servers/permission-manager/src/policy.rs

137 lines
5.3 KiB
Rust

//! What the permission manager decides, with nothing but its two ports:
//! the owner (`Consent`) and where apps and decisions live (`Store`).
//! No kernel, no IPC, no hardware: tested on the host.
use manifest::{AppId, Capabilities, Capability, Decision, Manifest};
/// Asks the owner. Answers are final: there is no "ask again later".
pub trait Consent {
/// May `app` have everything it cannot work without? No means it is
/// not installed.
fn approve_required(&mut self, app: &Manifest, required: Capabilities) -> bool;
/// May `app` have `cap`, which it can do without?
fn approve_optional(&mut self, app: &Manifest, cap: Capability) -> bool;
}
/// Installed apps, their files and the owner's decisions.
pub trait Store {
/// An app folder offered for installation.
type Source;
/// The manifest in `source`, as text.
fn source_manifest<'a>(&mut self, source: &Self::Source, buf: &'a mut [u8]) -> Option<&'a str>;
fn is_installed(&mut self, id: &AppId) -> bool;
/// Copies `source`'s binary and manifest into the app's own folder,
/// and makes its data folder if it is to have one.
fn install(&mut self, source: &Self::Source, app: &Manifest, data_folder: bool) -> bool;
fn manifest(&mut self, id: &AppId) -> Option<Manifest>;
/// The installed app run as `command`.
fn find(&mut self, command: &str) -> Option<AppId>;
fn decision(&mut self, id: &AppId) -> Option<Decision>;
fn record(&mut self, id: &AppId, decision: &Decision) -> bool;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Refusal {
/// The offered folder holds no manifest it can read.
NoManifest,
BadManifest(manifest::Error),
AlreadyInstalled,
NotInstalled,
/// This device has no server for these.
Unavailable(Capabilities),
/// The owner said no to what it requires.
Declined,
/// It does not hold these, and cannot run without them.
Missing(Capabilities),
/// It never asked for that capability.
NotRequested,
/// Reading or writing the store failed.
Store,
}
/// Longest manifest read.
pub const MAX_MANIFEST: usize = 2048;
/// Checks the app in `source` and asks the owner: required capabilities
/// all together, then each optional one this device has. Installs it and
/// records the decision; returns its manifest.
pub fn install<S: Store>(
store: &mut S,
consent: &mut impl Consent,
source: &S::Source,
available: Capabilities,
) -> Result<Manifest, Refusal> {
let mut buf = [0u8; MAX_MANIFEST];
let text = store.source_manifest(source, &mut buf).ok_or(Refusal::NoManifest)?;
let app = Manifest::parse(text).map_err(Refusal::BadManifest)?;
if store.is_installed(&app.id) {
return Err(Refusal::AlreadyInstalled);
}
let lacking = app.required.minus(available);
if !lacking.is_empty() {
return Err(Refusal::Unavailable(lacking));
}
if !app.required.is_empty() && !consent.approve_required(&app, app.required) {
return Err(Refusal::Declined);
}
let mut granted = app.required;
for cap in app.optional.intersect(available).iter() {
if consent.approve_optional(&app, cap) {
granted = granted.with(cap);
}
}
let decision = Decision { granted };
if !store.install(source, &app, granted.contains(Capability::Storage)) || !store.record(&app.id, &decision) {
return Err(Refusal::Store);
}
Ok(app)
}
/// An app about to run and what it gets.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Launch {
pub app: Manifest,
pub granted: Capabilities,
}
/// What the app run as `command` gets: what it asked for, the owner
/// granted and this device has. Refused if that leaves out anything it
/// requires (a decision changed since install, or a server is gone).
pub fn plan_launch(store: &mut impl Store, command: &str, available: Capabilities) -> Result<Launch, Refusal> {
let id = store.find(command).ok_or(Refusal::NotInstalled)?;
let app = store.manifest(&id).ok_or(Refusal::NotInstalled)?;
let decision = store.decision(&id).ok_or(Refusal::Store)?;
let granted = decision.granted.intersect(app.requested()).intersect(available);
let missing = app.required.minus(granted);
if !missing.is_empty() {
return Err(Refusal::Missing(missing));
}
Ok(Launch { app, granted })
}
/// The owner takes `cap` away from `id`; the new decision.
pub fn revoke(store: &mut impl Store, id: &AppId, cap: Capability) -> Result<Decision, Refusal> {
let decision = store.decision(id).ok_or(Refusal::NotInstalled)?;
let changed = Decision { granted: decision.granted.without(cap) };
if !store.record(id, &changed) {
return Err(Refusal::Store);
}
Ok(changed)
}
/// The owner gives `id` a capability it asked for (one withdrawn, or an
/// optional one declined at install); the new decision.
pub fn allow(store: &mut impl Store, id: &AppId, cap: Capability) -> Result<Decision, Refusal> {
let app = store.manifest(id).ok_or(Refusal::NotInstalled)?;
if !app.requested().contains(cap) {
return Err(Refusal::NotRequested);
}
let decision = store.decision(id).ok_or(Refusal::Store)?;
let changed = Decision { granted: decision.granted.with(cap) };
if !store.record(id, &changed) {
return Err(Refusal::Store);
}
Ok(changed)
}