mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-08 19:42:05 +00:00
fix(engine): install a directory plugin only with the permissions that were shown
The confirmation showed the directory entry's access and commands, but the downloaded bundle's manifest was registered as is, so a directory could show a read-only plugin and install one that edits the document. A manifest asking for more than the entry showed is refused before it is saved.
This commit is contained in:
1 parent
02c38a6d95
commit
c673996fe7
3 files changed
+45
-2
No files matched your search
@@ -127,6 +127,16 @@ fn install(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
if manifest.id != entry.id || manifest.version != entry.version {
|
||||
return Err(fail("its manifest doesn't match the directory's entry"));
|
||||
}
|
||||
// What was shown and approved is the entry's access and commands: a bundle whose manifest
|
||||
// asks for more is refused, since the manifest is what the plugin then runs under.
|
||||
if manifest.permissions.access == figcraft_plugin::manifest::Access::Write && !entry.access.trim().eq_ignore_ascii_case("write") {
|
||||
return Err(fail("its manifest asks to write the document, but the directory showed it as read-only"));
|
||||
}
|
||||
if !entry.commands.is_empty()
|
||||
&& (manifest.permissions.commands.is_empty() || manifest.permissions.commands.iter().any(|c| !entry.commands.contains(c)))
|
||||
{
|
||||
return Err(fail("its manifest asks for commands the directory didn't show"));
|
||||
}
|
||||
let source = address(&server);
|
||||
let path = save(s, &mut manifest, &bundle).map_err(|e| fail(&e))?;
|
||||
let out = json!({
|
||||
|
||||
@@ -252,3 +252,29 @@ fn publishing_sends_a_checked_folder() {
|
||||
assert_eq!(r["id"], ID);
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_bundle_asking_for_more_than_the_directory_showed_is_not_installed() {
|
||||
let (mut s, d) = session();
|
||||
// Shown as read-only with one command; the bundle's manifest asks to write, any command.
|
||||
let text =
|
||||
json!({"id": ID, "name": "Tiny", "version": "1.0.0", "author": "Ada", "main": "tiny.wasm", "permissions": {"access": "write"}}).to_string();
|
||||
let b = Bundle { manifest: text, wasm: wasm(), assets: vec![] }.encode();
|
||||
let mut e = entry(ID, "1.0.0", &b);
|
||||
e.commands = vec!["node.get".into()];
|
||||
d.lock().unwrap().plugins.push((e, b));
|
||||
s.execute("plugin.directory.list", &json!({"server": "ws://127.0.0.1:9/"})).unwrap();
|
||||
let err = s.execute("plugin.directory.install", &json!({"id": ID, "confirm": true})).unwrap_err().to_string();
|
||||
assert!(err.contains("write"), "{err}");
|
||||
assert!(s.prefs.plugins.is_empty(), "nothing was installed");
|
||||
// Read-only as shown, but more commands than shown.
|
||||
let text = json!({"id": ID, "name": "Tiny", "version": "1.0.1", "author": "Ada", "main": "tiny.wasm", "permissions": {"access": "read", "commands": ["node.get", "node.set"]}}).to_string();
|
||||
let b = Bundle { manifest: text, wasm: wasm(), assets: vec![] }.encode();
|
||||
let mut e = entry(ID, "1.0.1", &b);
|
||||
e.commands = vec!["node.get".into()];
|
||||
d.lock().unwrap().plugins.clear();
|
||||
d.lock().unwrap().plugins.push((e, b));
|
||||
s.execute("plugin.directory.list", &json!({"server": "ws://127.0.0.1:9/"})).unwrap();
|
||||
let err = s.execute("plugin.directory.install", &json!({"id": ID, "confirm": true})).unwrap_err().to_string();
|
||||
assert!(err.contains("commands"), "{err}");
|
||||
}
|
||||
@@ -73,8 +73,15 @@ fn bundle() -> Vec<u8> {
|
||||
|
||||
fn directory(req: PluginReq) -> PluginReply {
|
||||
let bytes = bundle();
|
||||
let entry =
|
||||
PluginEntry { id: ID.into(), name: "Tiny tool".into(), version: "1.0.0".into(), sha256: sha256_hex(&bytes), ..PluginEntry::default() };
|
||||
let entry = PluginEntry {
|
||||
id: ID.into(),
|
||||
name: "Tiny tool".into(),
|
||||
version: "1.0.0".into(),
|
||||
access: "write".into(),
|
||||
commands: vec!["shape.rectangle".into()],
|
||||
sha256: sha256_hex(&bytes),
|
||||
..PluginEntry::default()
|
||||
};
|
||||
match req {
|
||||
PluginReq::List { .. } => PluginReply::List(vec![entry]),
|
||||
PluginReq::Fetch { .. } => PluginReply::Bundle { entry, bundle: bytes },
|
||||
|
||||
Reference in new issue
Block a user