From e5c077bd786722225ba06a93fd9bf483febe91fa Mon Sep 17 00:00:00 2001 From: Sonny Date: Thu, 3 Oct 2024 23:15:00 +0200 Subject: [PATCH] Remove unsafe permissions and add dialog (#985) Fixes https://github.com/workbenchdev/Workbench/issues/915 --- build-aux/re.sonny.Workbench.Devel.json | 4 +- build-aux/re.sonny.Workbench.json | 4 +- build-aux/wip/run.js | 8 + src/Library/Library.blp | 7 +- src/Library/Library.js | 14 +- src/Permissions/Permissions.blp | 155 +++++ src/Permissions/Permissions.js | 62 ++ src/Permissions/permissions.svg | 568 ++++++++++++++++++ src/application.js | 5 + ...sonny.Workbench-external-link-symbolic.svg | 2 + .../re.sonny.Workbench-gamepad-symbolic.svg | 2 + ...ny.Workbench-network-wireless-symbolic.svg | 4 + .../re.sonny.Workbench-person-symbolic.svg | 2 + .../re.sonny.Workbench-speakers-symbolic.svg | 2 + src/window.blp | 5 + src/window.js | 2 + 16 files changed, 838 insertions(+), 8 deletions(-) create mode 100644 src/Permissions/Permissions.blp create mode 100644 src/Permissions/Permissions.js create mode 100644 src/Permissions/permissions.svg create mode 100644 src/icons/re.sonny.Workbench-external-link-symbolic.svg create mode 100644 src/icons/re.sonny.Workbench-gamepad-symbolic.svg create mode 100644 src/icons/re.sonny.Workbench-network-wireless-symbolic.svg create mode 100644 src/icons/re.sonny.Workbench-person-symbolic.svg create mode 100644 src/icons/re.sonny.Workbench-speakers-symbolic.svg diff --git a/build-aux/re.sonny.Workbench.Devel.json b/build-aux/re.sonny.Workbench.Devel.json index 6b04fb2b3..0bbc6c099 100644 --- a/build-aux/re.sonny.Workbench.Devel.json +++ b/build-aux/re.sonny.Workbench.Devel.json @@ -20,9 +20,7 @@ "--share=ipc", "--socket=fallback-x11", "--socket=wayland", - "--device=dri", - "--share=network", - "--socket=pulseaudio" + "--device=dri" ], "cleanup": [ "#/include", diff --git a/build-aux/re.sonny.Workbench.json b/build-aux/re.sonny.Workbench.json index 6ffa94ec9..13ed0dac4 100644 --- a/build-aux/re.sonny.Workbench.json +++ b/build-aux/re.sonny.Workbench.json @@ -20,9 +20,7 @@ "--share=ipc", "--socket=fallback-x11", "--socket=wayland", - "--device=dri", - "--share=network", - "--socket=pulseaudio" + "--device=dri" ], "cleanup": [ "#/include", diff --git a/build-aux/wip/run.js b/build-aux/wip/run.js index 57a7f8faf..0c4b58dd6 100755 --- a/build-aux/wip/run.js +++ b/build-aux/wip/run.js @@ -164,6 +164,14 @@ async function runCommand(argv) { "--allow=devel", `--bind-mount=/run/user/1000/doc=/run/user/1000/doc/by-app/${flatpak_id}`, ...manifest["finish-args"], + + // Non default permissions + // see Permissions.js + // consider getting installed overrides instead + "--share=network", + "--socket=pulseaudio", + "--device=input", + "--talk-name=org.freedesktop.portal.*", "--talk-name=org.a11y.Bus", "--bind-mount=/run/flatpak/at-spi-bus=/run/user/1000/at-spi/bus", diff --git a/src/Library/Library.blp b/src/Library/Library.blp index c577ac58f..7d23823af 100644 --- a/src/Library/Library.blp +++ b/src/Library/Library.blp @@ -63,11 +63,13 @@ Adw.Window window { DropDown dropdown_language { valign: end; + model: Gtk.StringList {}; } DropDown dropdown_category { valign: end; + model: Gtk.StringList {}; } } @@ -105,7 +107,10 @@ Adw.Window window { Button button_reset { label: _("Reset filters"); halign: center; - styles ["pill"] + + styles [ + "pill" + ] } } diff --git a/src/Library/Library.js b/src/Library/Library.js index 4a596b247..ce6a5c6b3 100644 --- a/src/Library/Library.js +++ b/src/Library/Library.js @@ -17,6 +17,10 @@ import illustration from "./library.svg"; import { gettext as _ } from "gettext"; import { build } from "../../troll/src/builder.js"; +import { + needsAdditionalPermissions, + showPermissionsDialog, +} from "../Permissions/Permissions.js"; export default function Library({ application }) { const objects = build(resource); @@ -204,9 +208,17 @@ async function openDemo({ application, demo_name, language }) { ); } - const { load, runCode } = Window({ application, session }); + const { load, runCode, window } = Window({ + application, + session, + }); await load(); + if (needsAdditionalPermissions({ demo })) { + showPermissionsDialog({ window }); + return; + } + const code_language = session.getCodeLanguage(); const run = autorun && code_language.id === "javascript"; if (run) { diff --git a/src/Permissions/Permissions.blp b/src/Permissions/Permissions.blp new file mode 100644 index 000000000..86743400d --- /dev/null +++ b/src/Permissions/Permissions.blp @@ -0,0 +1,155 @@ +using Gtk 4.0; +using Adw 1; + +Adw.Dialog dialog { + content-height: 750; + content-width: 600; + + Adw.ToolbarView { + [top] + Adw.HeaderBar {} + + content: ScrolledWindow { + hscrollbar-policy: never; + + Adw.Clamp { + maximum-size: 520; + tightening-threshold: 400; + margin-start: 12; + margin-end: 12; + margin-bottom: 24; + + Box { + orientation: vertical; + halign: fill; + spacing: 24; + // Gtk.Picture needs to be wrapped in a box to behave properly + Box { + halign: center; + + Picture picture_illustration { + can-shrink: false; + margin-bottom: 24; + } + } + + Label { + label: _("Permissions Needed"); + + styles [ + "title-1" + ] + } + + Label { + label: _("Workbench needs additional permissions. Please run the following command in a terminal and restart the app."); + wrap: true; + justify: center; + } + + Label label_command { + use-markup: true; + wrap: true; + wrap-mode: word_char; + selectable: true; + xalign: 0; + + styles [ + "command_snippet" + ] + } + + Box { + orientation: vertical; + + Box { + margin-bottom: 6; + + Label { + label: _("What it does"); + halign: start; + hexpand: true; + + styles [ + "heading" + ] + } + + Button button_info { + icon-name: "re.sonny.Workbench-external-link-symbolic"; + + styles [ + "flat" + ] + } + } + + ListBox { + selection-mode: none; + + styles [ + "boxed-list" + ] + + Adw.ActionRow { + [prefix] + Image { + icon-name: "re.sonny.Workbench-person-symbolic"; + } + + title: _("--user"); + subtitle: _("Grant for your account only"); + + styles [ + "property" + ] + } + + Adw.ActionRow { + [prefix] + Image { + icon-name: "re.sonny.Workbench-network-wireless-symbolic"; + } + + title: _("--share-network"); + subtitle: _("Network access"); + + styles [ + "property" + ] + } + + Adw.ActionRow { + [prefix] + Image { + icon-name: "re.sonny.Workbench-speakers-symbolic"; + } + + title: _("--socket=pulseaudio"); + subtitle: _("Record and play audio"); + + styles [ + "property" + ] + } + + Adw.ActionRow { + [prefix] + Image { + icon-name: "re.sonny.Workbench-gamepad-symbolic"; + } + + title: _("--device=input"); + subtitle: _("Access to input device such as gamepads"); + + styles [ + "property" + ] + } + } + } + } + } + }; + } +} diff --git a/src/Permissions/Permissions.js b/src/Permissions/Permissions.js new file mode 100644 index 000000000..c0561b579 --- /dev/null +++ b/src/Permissions/Permissions.js @@ -0,0 +1,62 @@ +import Gio from "gi://Gio"; +import GLib from "gi://GLib"; +import Gtk from "gi://Gtk"; + +import { build } from "../../troll/src/main.js"; + +import Interface from "./Permissions.blp" with { type: "uri" }; + +import illustration from "./permissions.svg"; + +import { getFlatpakInfo } from "../util.js"; + +const action_permissions = new Gio.SimpleAction({ + name: "permissions", + parameter_type: null, +}); + +export function Permissions({ window }) { + const { dialog, picture_illustration, label_command, button_info } = + build(Interface); + + picture_illustration.set_resource(illustration); + label_command.label = `flatpak override --user --share=network --socket=pulseaudio --device=input ${GLib.getenv( + "FLATPAK_ID", + )}`; + + button_info.connect("clicked", () => { + new Gtk.UriLauncher({ + uri: "https://docs.flatpak.org/en/latest/sandbox-permissions.html", + }) + .launch(window, null) + .catch(console.error); + }); + + action_permissions.connect("activate", () => { + dialog.present(window); + }); + + window.add_action(action_permissions); +} + +const missing_permissions = (() => { + const flatpak_info = getFlatpakInfo(); + const shared = flatpak_info.get_string_list("Context", "shared"); + const sockets = flatpak_info.get_string_list("Context", "sockets"); + const devices = flatpak_info.get_string_list("Context", "devices"); + + return ( + !shared.includes("network") || + !sockets.includes("pulseaudio") || + !devices.includes("all") + ); +})(); + +export function needsAdditionalPermissions({ demo }) { + if (!demo["flatpak-finish-args"]) return false; + return missing_permissions; +} + +export function showPermissionsDialog({ window }) { + window.activate_action("permissions", null); +} diff --git a/src/Permissions/permissions.svg b/src/Permissions/permissions.svg new file mode 100644 index 000000000..c1eb540de --- /dev/null +++ b/src/Permissions/permissions.svg @@ -0,0 +1,568 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/application.js b/src/application.js index 25acd8d74..a5d0c7713 100644 --- a/src/application.js +++ b/src/application.js @@ -30,6 +30,11 @@ import "./icons/re.sonny.Workbench-terminal-symbolic.svg" with { type: "icon" }; import "./icons/re.sonny.Workbench-test-pass-symbolic.svg" with { type: "icon" }; import "./icons/re.sonny.Workbench-up-symbolic.svg" with { type: "icon" }; import "./icons/re.sonny.Workbench-library-symbolic.svg" with { type: "icon" }; +import "./icons/re.sonny.Workbench-gamepad-symbolic.svg" with { type: "icon" }; +import "./icons/re.sonny.Workbench-network-wireless-symbolic.svg" with { type: "icon" }; +import "./icons/re.sonny.Workbench-person-symbolic.svg" with { type: "icon" }; +import "./icons/re.sonny.Workbench-speakers-symbolic.svg" with { type: "icon" }; +import "./icons/re.sonny.Workbench-external-link-symbolic.svg" with { type: "icon" }; const icon_theme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default()); icon_theme.resource_path = [ diff --git a/src/icons/re.sonny.Workbench-external-link-symbolic.svg b/src/icons/re.sonny.Workbench-external-link-symbolic.svg new file mode 100644 index 000000000..e88a887a0 --- /dev/null +++ b/src/icons/re.sonny.Workbench-external-link-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/src/icons/re.sonny.Workbench-gamepad-symbolic.svg b/src/icons/re.sonny.Workbench-gamepad-symbolic.svg new file mode 100644 index 000000000..ff2aa1e8c --- /dev/null +++ b/src/icons/re.sonny.Workbench-gamepad-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/src/icons/re.sonny.Workbench-network-wireless-symbolic.svg b/src/icons/re.sonny.Workbench-network-wireless-symbolic.svg new file mode 100644 index 000000000..f33a9104e --- /dev/null +++ b/src/icons/re.sonny.Workbench-network-wireless-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/icons/re.sonny.Workbench-person-symbolic.svg b/src/icons/re.sonny.Workbench-person-symbolic.svg new file mode 100644 index 000000000..4936089f3 --- /dev/null +++ b/src/icons/re.sonny.Workbench-person-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/src/icons/re.sonny.Workbench-speakers-symbolic.svg b/src/icons/re.sonny.Workbench-speakers-symbolic.svg new file mode 100644 index 000000000..bf59b480b --- /dev/null +++ b/src/icons/re.sonny.Workbench-speakers-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/src/window.blp b/src/window.blp index ab3ac0de6..3e7579db4 100644 --- a/src/window.blp +++ b/src/window.blp @@ -700,6 +700,11 @@ menu menu_app { action: "win.extensions"; } + item { + label: _("Permissions"); + action: "win.permissions"; + } + section { item { label: _("Reveal in Files"); diff --git a/src/window.js b/src/window.js index 00116f465..915426f89 100644 --- a/src/window.js +++ b/src/window.js @@ -33,6 +33,7 @@ import { isTypeScriptAvailable, isValaAvailable, } from "./Extensions/Extensions.js"; +import { Permissions } from "./Permissions/Permissions.js"; import { JavaScriptDocument } from "./langs/javascript/JavaScriptDocument.js"; import { BlueprintDocument } from "./langs/blueprint/BlueprintDocument.js"; import { CssDocument } from "./langs/css/CssDocument.js"; @@ -67,6 +68,7 @@ export default function Window({ application, session }) { Extensions({ window, }); + Permissions({ window }); // Popover menu theme switcher const button_menu = builder.get_object("button_menu");