diff --git a/dangerzone/docker_installer.py b/dangerzone/docker_installer.py index b1f043a..db07a23 100644 --- a/dangerzone/docker_installer.py +++ b/dangerzone/docker_installer.py @@ -11,6 +11,10 @@ from PyQt5 import QtCore, QtGui, QtWidgets from .container import container_runtime +class AuthorizationFailed(Exception): + pass + + def is_docker_installed(global_common): if platform.system() == "Darwin": # Does the docker binary exist? @@ -29,15 +33,18 @@ def is_docker_installed(global_common): def is_docker_ready(global_common): # Run `docker image ls` without an error - try: - print(global_common.get_dangerzone_container_args()) - subprocess.run( - global_common.get_dangerzone_container_args() + ["image-ls"], - startupinfo=global_common.get_subprocess_startupinfo(), - ) - return True - except subprocess.CalledProcessError: - return False + with global_common.exec_dangerzone_container(["image-ls"]) as p: + p.communicate() + + # The user canceled, or permission denied + if p.returncode == 126 or p.returncode == 127: + raise AuthorizationFailed + + # Return true if it succeeds + if p.returncode == 0: + return True + else: + return False def launch_docker_windows(global_common): diff --git a/dangerzone/global_common.py b/dangerzone/global_common.py index 95e6934..26dd061 100644 --- a/dangerzone/global_common.py +++ b/dangerzone/global_common.py @@ -6,6 +6,7 @@ import appdirs import platform import subprocess import shlex +import pipes from PyQt5 import QtCore, QtGui, QtWidgets if platform.system() == "Darwin": @@ -284,14 +285,25 @@ class GlobalCommon(object): else: return "/usr/bin/dangerzone-container" - def get_dangerzone_container_args(self): + def exec_dangerzone_container(self, args): + # Prefix the args with the retainer runtime, and in the case linux when the user isn't in the docker group, pkexec if platform.system() == "Linux": if self.settings.get("linux_prefers_typing_password"): - return ["/usr/bin/pkexec", self.dz_container_path] + args = ["/usr/bin/pkexec", self.dz_container_path] + args else: - return [self.dz_container_path] + args = [self.dz_container_path] + args else: - return [self.dz_container_path] + args = [self.dz_container_path] + args + + # Execute dangerzone-container + args_str = " ".join(pipes.quote(s) for s in args) + print(f"Executing: {args_str}") + return subprocess.Popen( + args, + startupinfo=self.get_subprocess_startupinfo(), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) def get_window_icon(self): if platform.system() == "Windows": diff --git a/dangerzone/gui.py b/dangerzone/gui.py index 7aaf6dc..9d2d7db 100644 --- a/dangerzone/gui.py +++ b/dangerzone/gui.py @@ -15,6 +15,7 @@ from .docker_installer import ( is_docker_ready, launch_docker_windows, DockerInstaller, + AuthorizationFailed, ) from .container import container_runtime @@ -51,14 +52,23 @@ def gui_main(custom_container, filename): if custom_container: # Do we have this container? - output = subprocess.check_output( - global_common.get_dangerzone_container_args() - + ["image-ls", custom_container], - startupinfo=global_common.get_subprocess_startupinfo(), - ) - if custom_container.encode() not in output: - click.echo(f"Container '{container}' not found") - return + with global_common.exec_dangerzone_container( + ["image-ls", "--container-name", custom_container] + ) as p: + stdout_data, stderr_data = p.communicate() + + # The user canceled, or permission denied + if p.returncode == 126 or p.returncode == 127: + click.echo("Authorization failed") + return + elif p.returncode != 0: + click.echo("Container error") + return + + # Check the output + if custom_container.encode() not in stdout_data: + click.echo(f"Container '{container}' not found") + return global_common.custom_container = custom_container @@ -69,8 +79,12 @@ def gui_main(custom_container, filename): if platform.system() == "Linux" and container_runtime == "/usr/bin/docker": if not global_common.ensure_docker_group_preference(): return - if not global_common.ensure_docker_service_is_started(): - click.echo("Failed to start docker service") + try: + if not global_common.ensure_docker_service_is_started(): + click.echo("Failed to start docker service") + return + except AuthorizationFailed: + click.echo("Authorization failed") return # See if we need to install Docker... diff --git a/dangerzone/main_window.py b/dangerzone/main_window.py index dff53d0..38789d9 100644 --- a/dangerzone/main_window.py +++ b/dangerzone/main_window.py @@ -52,7 +52,11 @@ class MainWindow(QtWidgets.QMainWindow): self.settings_widget.document_selected ) self.settings_widget.start_clicked.connect(self.start_clicked) + self.settings_widget.close_window.connect(self.close) self.settings_widget.hide() + QtCore.QTimer.singleShot( + 1, self.settings_widget.check_update_container_default_state + ) # Tasks self.tasks_widget = TasksWidget(self.global_common, self.common) diff --git a/dangerzone/settings_widget.py b/dangerzone/settings_widget.py index eecdbbc..ec7305c 100644 --- a/dangerzone/settings_widget.py +++ b/dangerzone/settings_widget.py @@ -6,6 +6,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class SettingsWidget(QtWidgets.QWidget): start_clicked = QtCore.pyqtSignal() + close_window = QtCore.pyqtSignal() def __init__(self, global_common, common): super(SettingsWidget, self).__init__() @@ -134,24 +135,31 @@ class SettingsWidget(QtWidgets.QWidget): else: self.update_checkbox.setCheckState(QtCore.Qt.Unchecked) + def check_update_container_default_state(self): # Is update containers required? if self.global_common.custom_container: self.update_checkbox.setCheckState(QtCore.Qt.Unchecked) self.update_checkbox.setEnabled(False) self.update_checkbox.hide() else: - output = subprocess.check_output( - self.global_common.get_dangerzone_container_args() - + [ + with self.global_common.exec_dangerzone_container( + [ "image-ls", "--container-name", self.global_common.get_container_name(), - ], - startupinfo=self.global_common.get_subprocess_startupinfo(), - ) - if b"dangerzone" not in output: - self.update_checkbox.setCheckState(QtCore.Qt.Checked) - self.update_checkbox.setEnabled(False) + ] + ) as p: + stdout_data, stderror_data = p.communicate() + + # The user canceled, or permission denied + if p.returncode == 126 or p.returncode == 127: + self.close_window.emit() + return + + # Check the output + if b"dangerzone" not in stdout_data: + self.update_checkbox.setCheckState(QtCore.Qt.Checked) + self.update_checkbox.setEnabled(False) def update_ui(self): if platform.system() == "Windows": diff --git a/dangerzone/tasks.py b/dangerzone/tasks.py index fae33ae..1398203 100644 --- a/dangerzone/tasks.py +++ b/dangerzone/tasks.py @@ -16,29 +16,25 @@ class TaskBase(QtCore.QThread): super(TaskBase, self).__init__() def exec_container(self, args): - args = self.global_common.get_dangerzone_container_args() + args output = "" self.update_details.emit(output) - with subprocess.Popen( - args, - stdin=None, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - bufsize=1, - universal_newlines=True, - startupinfo=self.global_common.get_subprocess_startupinfo(), - ) as p: + with self.global_common.exec_dangerzone_container(args) as p: for line in p.stdout: - output += line - print(line, end="") + output += line.decode() + print(line.decode(), end="") self.update_details.emit(output) stderr = p.stderr.read() - output += stderr - print(stderr) + output += stderr.decode() + print(stderr.decode()) self.update_details.emit(output) + if p.returncode == 126 or p.returncode == 127: + self.task_failed.emit(f"Authorization failed") + elif p.returncode == 0: + self.task_failed.emit(f"Return code: {p.returncode}") + print("") return p.returncode, output @@ -58,7 +54,6 @@ class PullImageTask(TaskBase): returncode, _ = self.exec_container(args) if returncode != 0: - self.task_failed.emit(f"Return code: {returncode}") return self.task_finished.emit() @@ -88,7 +83,6 @@ class ConvertToPixels(TaskBase): returncode, output = self.exec_container(args) if returncode != 0: - self.task_failed.emit(f"Return code: {returncode}") return # Did we hit an error? @@ -194,7 +188,6 @@ class ConvertToPDF(TaskBase): returncode, output = self.exec_container(args) if returncode != 0: - self.task_failed.emit(f"Return code: {returncode}") return self.task_finished.emit()