Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle HTTP & HTTPS URLs in dom0 & GUIVMs #226

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,5 @@ ENV/
*.swp

*.~undo-tree~
*.glade~
*.glade#
2 changes: 1 addition & 1 deletion .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ checks:tests:
before_script: &before-script
- "PATH=$PATH:$HOME/.local/bin"
- sudo dnf install -y python3-gobject gtk3 python3-pytest gtksourceview4
python3-coverage xorg-x11-server-Xvfb python3-pip
python3-coverage xorg-x11-server-Xvfb python3-pip xdg-utils
- pip3 install --quiet -r ci/requirements.txt
- git clone https://github.com/QubesOS/qubes-core-admin-client ~/core-admin-client
- git clone https://github.com/QubesOS/qubes-core-qrexec ~/core-qrexec
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ install-autostart:
cp desktop/qubes-policy-editor-gui.desktop $(DESTDIR)/usr/share/applications/
install -d $(DESTDIR)/usr/lib/qubes -m 0755
install -m 0755 qui/devices/qubes-device-agent-autostart $(DESTDIR)/usr/lib/qubes/qubes-device-agent-autostart
cp desktop/qubes-virtual-browser.desktop $(DESTDIR)/usr/share/applications/

install-lang:
mkdir -p $(DESTDIR)/usr/share/gtksourceview-4/language-specs/
Expand Down
1 change: 1 addition & 0 deletions debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Package: qubes-desktop-linux-manager
Architecture: any
Depends:
python3-qui,
xdg-utils,
${misc:Depends}
Description: Qubes UI Applications
A collection of GUI application for enhancing the Qubes UX.
Expand Down
13 changes: 13 additions & 0 deletions desktop/qubes-virtual-browser.desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
### Note: With this installed, typing "xdg-settings set default-web-browser qubes-virtual-browser.desktop" will make it so that in gnome-terminal
### (typing "xdg-settings set default-web-browser firefox.desktop" will put it back to normal)

[Desktop Entry]
Version=1.0
Name=Qubes Virtual Browser
Exec=/usr/bin/qubes-virtual-browser %u
Icon=qubes-manager
Terminal=false
Type=Application
Categories=Network;WebBrowser;
MimeType=text/html;text/xml;application/xhtml+xml;application/vnd.mozilla.xul+xml;text/mml;x-scheme-handler/http;x-scheme-handler/https;
NoDisplay=true
227 changes: 215 additions & 12 deletions qubes_config/global_config.glade

Large diffs are not rendered by default.

157 changes: 144 additions & 13 deletions qubes_config/global_config/global_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
import qubesadmin.vm
from ..widgets.gtk_utils import show_error, show_dialog_with_icon, load_theme
from ..widgets.gtk_widgets import ProgressBarDialog, ViewportHandler
from ..widgets.utils import open_url_in_disposable
from ..widgets.utils import open_url_in_disposable, apply_feature_change
from .page_handler import PageHandler
from .policy_handler import PolicyHandler, VMSubsetPolicyHandler
from .policy_rules import RuleSimple, \
Expand All @@ -49,6 +49,7 @@

gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib, GObject, Gio
from gi.repository.GdkPixbuf import Pixbuf

logger = logging.getLogger('qubes-global-config')

Expand Down Expand Up @@ -189,6 +190,138 @@
return "\n".join(unsaved)


class UrlPageHandler(PageHandler):
"""Handler for URL page. Requires separate handler because it combines
qubes-virtual-browser settings with qubes.OpenURL policies. """
def __init__(self, qapp: qubesadmin.Qubes,
gtk_builder: Gtk.Builder,
policy_manager: PolicyManager):
self.qapp = qapp
self.builder = gtk_builder
self.policy_manager = policy_manager
self.browser_action = qapp.domains[qapp.local_name].features.get( \
"virtual-browser-action", "")

self.virtual_browser_ask: Gtk.RadioButton = \
gtk_builder.get_object('virtual_browser_ask')
self.virtual_browser_dispvm: Gtk.RadioButton = \
gtk_builder.get_object('virtual_browser_dispvm')
self.virtual_browser_clipboard: Gtk.RadioButton = \
gtk_builder.get_object('virtual_browser_clipboard')
self.virtual_browser_discard: Gtk.RadioButton = \
gtk_builder.get_object('virtual_browser_discard')

self.virtual_browser_dispvms: Gtk.ComboBox = \
gtk_builder.get_object('virtual_browser_dispvms')
self.disposables = Gtk.ListStore(object, Pixbuf, str)
self.virtual_browser_dispvms.set_model(self.disposables)
self.renderer_icon = Gtk.CellRendererPixbuf()
self.renderer_vmname = Gtk.CellRendererText()
self.virtual_browser_dispvms.pack_start(self.renderer_icon, True)
self.virtual_browser_dispvms.pack_start(self.renderer_vmname, True)
self.virtual_browser_dispvms.add_attribute( \
self.renderer_icon, "pixbuf", 1)
self.virtual_browser_dispvms.add_attribute( \
self.renderer_vmname, "text", 2)

default_dispvm = getattr(self.qapp, "default_dispvm", None)
for domain in qapp.domains:
if getattr(domain, "template_for_dispvms", False):
# pylint: disable=no-member
try:
icon = Gtk.IconTheme.get_default().load_icon(
getattr(domain, "icon", "qubes-manager"), 32, 0)
except gi.repository.GLib.GError:
# Use Adwaita's Qube like icon if original icon is missing
icon = Gtk.IconTheme.get_default().load_icon(
"insert-object-symbolic", 32, 0)
row = self.disposables.append([domain, icon, domain.name])
if domain.name == default_dispvm:
self.disposables[row][2] += " (Default DispVM Template)"

Check warning on line 240 in qubes_config/global_config/global_config.py

View check run for this annotation

Codecov / codecov/patch

qubes_config/global_config/global_config.py#L240

Added line #L240 was not covered by tests

self._reset_virtual_browser_choice()
self.virtual_browser_dispvms.connect("changed", self._dispvm_selected)

# Allocating a list of handlers in case we want to add more in future
self.handlers: List[Union[DispvmExceptionHandler, FeatureHandler]] = [
DispvmExceptionHandler(
gtk_builder=self.builder,
qapp=self.qapp,
service_name="qubes.OpenURL",
policy_file_name="50-config-openurl",
prefix="url",
policy_manager=self.policy_manager,
)
]

def _dispvm_selected(self, combo):
# pylint: disable=unused-argument
self.virtual_browser_dispvm.set_active(True)

Check warning on line 259 in qubes_config/global_config/global_config.py

View check run for this annotation

Codecov / codecov/patch

qubes_config/global_config/global_config.py#L259

Added line #L259 was not covered by tests

def _reset_virtual_browser_choice(self):
if self.browser_action is not None \
and self.browser_action.startswith('disposable:') \
and self.browser_action[11:] in self.qapp.domains:
self.virtual_browser_dispvm.set_active(True)

Check warning on line 265 in qubes_config/global_config/global_config.py

View check run for this annotation

Codecov / codecov/patch

qubes_config/global_config/global_config.py#L265

Added line #L265 was not covered by tests
elif self.browser_action == 'clipboard':
self.virtual_browser_clipboard.set_active(True)

Check warning on line 267 in qubes_config/global_config/global_config.py

View check run for this annotation

Codecov / codecov/patch

qubes_config/global_config/global_config.py#L267

Added line #L267 was not covered by tests
elif self.browser_action == 'discard':
self.virtual_browser_discard.set_active(True)

Check warning on line 269 in qubes_config/global_config/global_config.py

View check run for this annotation

Codecov / codecov/patch

qubes_config/global_config/global_config.py#L269

Added line #L269 was not covered by tests
else:
self.virtual_browser_ask.set_active(True)

default_dispvm = getattr(self.qapp, "default_dispvm", None)
for index, disposable in enumerate(self.disposables):
if self.virtual_browser_dispvm.get_active():
if disposable[0].name == self.browser_action[11:]:
self.virtual_browser_dispvms.set_active(index)

Check warning on line 277 in qubes_config/global_config/global_config.py

View check run for this annotation

Codecov / codecov/patch

qubes_config/global_config/global_config.py#L276-L277

Added lines #L276 - L277 were not covered by tests
elif disposable[0].name == default_dispvm:
self.virtual_browser_dispvms.set_active(index)

Check warning on line 279 in qubes_config/global_config/global_config.py

View check run for this annotation

Codecov / codecov/patch

qubes_config/global_config/global_config.py#L279

Added line #L279 was not covered by tests

def reset(self):
for handler in self.handlers:
handler.reset()
self._reset_virtual_browser_choice()

Check warning on line 284 in qubes_config/global_config/global_config.py

View check run for this annotation

Codecov / codecov/patch

qubes_config/global_config/global_config.py#L282-L284

Added lines #L282 - L284 were not covered by tests

def save(self):
for handler in self.handlers:
handler.save()
if self.virtual_browser_ask.get_active():
self.browser_action = None
elif self.virtual_browser_dispvm.get_active():
tree_iter = self.virtual_browser_dispvms.get_active_iter()
self.browser_action = "disposable:" + \

Check warning on line 293 in qubes_config/global_config/global_config.py

View check run for this annotation

Codecov / codecov/patch

qubes_config/global_config/global_config.py#L287-L293

Added lines #L287 - L293 were not covered by tests
self.disposables[tree_iter][0].name
elif self.virtual_browser_clipboard.get_active():
self.browser_action = "clipboard"
elif self.virtual_browser_discard.get_active():
self.browser_action = "discard"
apply_feature_change(self.qapp.domains[self.qapp.local_name], \

Check warning on line 299 in qubes_config/global_config/global_config.py

View check run for this annotation

Codecov / codecov/patch

qubes_config/global_config/global_config.py#L295-L299

Added lines #L295 - L299 were not covered by tests
"virtual-browser-action", self.browser_action)

def _virtual_browser_choice(self) -> str:
if self.virtual_browser_ask.get_active():
return ""
if self.virtual_browser_dispvm.get_active():
tree_iter = self.virtual_browser_dispvms.get_active_iter()
return "disposable:" + self.disposables[tree_iter][0].name
if self.virtual_browser_clipboard.get_active():
return "clipboard"
if self.virtual_browser_discard.get_active():
return "discard"
return ""

Check warning on line 312 in qubes_config/global_config/global_config.py

View check run for this annotation

Codecov / codecov/patch

qubes_config/global_config/global_config.py#L305-L312

Added lines #L305 - L312 were not covered by tests

def get_unsaved(self) -> str:
unsaved = []
for handler in self.handlers:
unsaved_changes = handler.get_unsaved()
if unsaved_changes:
unsaved.append(unsaved_changes)

Check warning on line 319 in qubes_config/global_config/global_config.py

View check run for this annotation

Codecov / codecov/patch

qubes_config/global_config/global_config.py#L319

Added line #L319 was not covered by tests
if self.browser_action != self._virtual_browser_choice():
unsaved.append(_("Qubes Virtual Browser default action"))

Check warning on line 321 in qubes_config/global_config/global_config.py

View check run for this annotation

Codecov / codecov/patch

qubes_config/global_config/global_config.py#L321

Added line #L321 was not covered by tests
return "\n".join(unsaved)


class GlobalConfig(Gtk.Application):
"""
Main Gtk.Application for new qube widget.
Expand Down Expand Up @@ -403,14 +536,12 @@
policy_manager=self.policy_manager
)
self.progress_bar_dialog.update_progress(page_progress)
self.handlers['url'] = DispvmExceptionHandler(
gtk_builder=self.builder,
qapp=self.qapp,
service_name="qubes.OpenURL",
policy_file_name="50-config-openurl",
prefix="url",
policy_manager=self.policy_manager,
)

self.handlers['url'] = UrlPageHandler(
qapp=self.qapp,
gtk_builder=self.builder,
policy_manager=self.policy_manager
)
self.progress_bar_dialog.update_progress(page_progress)

self.handlers['thisdevice'] = ThisDeviceHandler(self.qapp,
Expand Down Expand Up @@ -452,7 +583,7 @@
label.connect("activate-link", self._activate_link)

def _activate_link(self, _widget, url):
open_url_in_disposable(url, self.qapp)
open_url_in_disposable(url)

Check warning on line 586 in qubes_config/global_config/global_config.py

View check run for this annotation

Codecov / codecov/patch

qubes_config/global_config/global_config.py#L586

Added line #L586 was not covered by tests
return True

def get_current_page(self) -> Optional[PageHandler]:
Expand Down Expand Up @@ -520,9 +651,9 @@
label_3 = Gtk.Label()
label_3.set_text(_("Do you want to save changes?"))
label_3.set_xalign(0)
box.pack_start(label_1, False, False, 10)
box.pack_start(label_2, False, False, 10)
box.pack_start(label_3, False, False, 10)
box.pack_start(label_1, False, False, 10) # pylint: disable=no-member
box.pack_start(label_2, False, False, 10) # pylint: disable=no-member
box.pack_start(label_3, False, False, 10) # pylint: disable=no-member

response = show_dialog_with_icon(
parent=self.main_window, title=_("Unsaved changes"), text=box,
Expand Down
4 changes: 1 addition & 3 deletions qubes_config/policy_editor/policy_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import gi
import importlib.resources

import qubesadmin
from qrexec.policy.admin_client import PolicyClient
from qrexec.policy.parser import StringPolicy
from qrexec.exc import PolicySyntaxError
Expand Down Expand Up @@ -246,8 +245,7 @@

@staticmethod
def _open_docs(_widget, url):
qapp = qubesadmin.Qubes()
open_url_in_disposable(url, qapp)
open_url_in_disposable(url)

Check warning on line 248 in qubes_config/policy_editor/policy_editor.py

View check run for this annotation

Codecov / codecov/patch

qubes_config/policy_editor/policy_editor.py#L248

Added line #L248 was not covered by tests
return True

def setup_actions(self):
Expand Down
9 changes: 6 additions & 3 deletions qubes_config/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,8 @@ def test_qapp_impl():
'config.default.qubes-update-check': None,
'config-usbvm-name': None,
'gui-default-secure-copy-sequence': None,
'gui-default-secure-paste-sequence': None
'gui-default-secure-paste-sequence': None,
'virtual-browser-action': None
}, [])
add_expected_vm(qapp, 'sys-net', 'AppVM',
{'provides_network': ('bool', False, 'True')},
Expand Down Expand Up @@ -373,7 +374,8 @@ def test_qapp_simple(): # pylint: disable=redefined-outer-name
'config.default.qubes-update-check': None,
'config-usbvm-name': None,
'gui-default-secure-copy-sequence': None,
'gui-default-secure-paste-sequence': None
'gui-default-secure-paste-sequence': None,
'virtual-browser-action': None
}, [])
add_expected_vm(qapp, 'sys-net', 'AppVM',
{'provides_network': ('bool', False, 'True')},
Expand Down Expand Up @@ -427,7 +429,8 @@ def test_qapp_broken(): # pylint: disable=redefined-outer-name
'config.default.qubes-update-check': None,
'config-usbvm-name': None,
'gui-default-secure-copy-sequence': None,
'gui-default-secure-paste-sequence': None
'gui-default-secure-paste-sequence': None,
'virtual-browser-action': None
}, [])

#
Expand Down
12 changes: 5 additions & 7 deletions qubes_config/widgets/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,17 +108,15 @@ def compare_rule_lists(rule_list_1: List[Rule],
return False
return True

def _open_url_in_dvm(url, default_dvm: qubesadmin.vm.QubesVM):
def _open_url(url):
subprocess.run(
['qvm-run', '-p', '--service', f'--dispvm={default_dvm}',
'qubes.OpenURL'], input=url.encode(), check=False,
['qubes-virtual-browser', url.encode()], input=None, check=False,
stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)

def open_url_in_disposable(url: str, qapp: qubesadmin.Qubes):
def open_url_in_disposable(url: str):
"""Open provided url in disposable qube based on default disposable
template"""
default_dvm = qapp.default_dispvm
open_thread = threading.Thread(group=None,
target=_open_url_in_dvm,
args=[url, default_dvm])
target=_open_url,
args=[url])
open_thread.start()
Loading