//! 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; /// The installed app run as `command`. fn find(&mut self, command: &str) -> Option; fn decision(&mut self, id: &AppId) -> Option; 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( store: &mut S, consent: &mut impl Consent, source: &S::Source, available: Capabilities, ) -> Result { 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 { 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 { 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 { 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) }