mirror of
https://github.com/freedomofpress/dangerzone.git
synced 2025-05-04 20:51:49 +02:00
Compare commits
29 commits
e06c0100c8
...
630083bdea
Author | SHA1 | Date | |
---|---|---|---|
![]() |
630083bdea | ||
![]() |
504a9e1df2 | ||
![]() |
a54a8f2057 | ||
![]() |
35abd14f5f | ||
![]() |
1bd18a175b | ||
![]() |
96aa56a6dc | ||
![]() |
91932046f5 | ||
![]() |
c8411de433 | ||
![]() |
95150bcfc1 | ||
![]() |
bae109717c | ||
![]() |
00480551ca | ||
![]() |
32deea10c4 | ||
![]() |
f540a67d06 | ||
![]() |
68f8338d20 | ||
![]() |
d561878e03 | ||
![]() |
59e1666c28 | ||
![]() |
95d7d8a4d9 | ||
![]() |
ed2791bbbc | ||
![]() |
c1cf16a705 | ||
![]() |
281432fcaa | ||
![]() |
71cc4b37e5 | ||
![]() |
5ed4a048a0 | ||
![]() |
50627d375c | ||
![]() |
8172195f95 | ||
![]() |
f5242078a9 | ||
![]() |
e68a43bbbf | ||
![]() |
10fb631b8e | ||
![]() |
796ca79289 | ||
![]() |
a95b612e78 |
33 changed files with 919 additions and 499 deletions
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
|
@ -1,6 +1,10 @@
|
|||
name: Build dev environments
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- "test/**"
|
||||
schedule:
|
||||
- cron: "0 0 * * *" # Run every day at 00:00 UTC.
|
||||
|
||||
|
@ -33,8 +37,6 @@ jobs:
|
|||
version: "20.04"
|
||||
- distro: ubuntu
|
||||
version: "22.04"
|
||||
- distro: ubuntu
|
||||
version: "23.10"
|
||||
- distro: ubuntu
|
||||
version: "24.04"
|
||||
- distro: ubuntu
|
||||
|
|
2
.github/workflows/check_push.yml
vendored
2
.github/workflows/check_push.yml
vendored
|
@ -1,6 +1,6 @@
|
|||
name: Check branch conformity
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
prevent-fixup-commits:
|
||||
|
|
2
.github/workflows/check_repos.yml
vendored
2
.github/workflows/check_repos.yml
vendored
|
@ -23,8 +23,6 @@ jobs:
|
|||
version: "24.10" # oracular
|
||||
- distro: ubuntu
|
||||
version: "24.04" # noble
|
||||
- distro: ubuntu
|
||||
version: "23.10" # mantic
|
||||
- distro: ubuntu
|
||||
version: "22.04" # jammy
|
||||
- distro: ubuntu
|
||||
|
|
29
.github/workflows/ci.yml
vendored
29
.github/workflows/ci.yml
vendored
|
@ -1,8 +1,10 @@
|
|||
name: Tests
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- "test/**"
|
||||
schedule:
|
||||
- cron: "2 0 * * *" # Run every day at 02:00 UTC.
|
||||
workflow_dispatch:
|
||||
|
@ -78,7 +80,7 @@ jobs:
|
|||
path: share/tessdata/
|
||||
key: v1-tessdata-${{ hashFiles('./install/common/download-tessdata.py') }}
|
||||
enableCrossOsArchive: true
|
||||
- uses: actions/setup-python@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Download Tessdata
|
||||
|
@ -91,7 +93,8 @@ jobs:
|
|||
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
needs: download-tessdata
|
||||
needs:
|
||||
- download-tessdata
|
||||
env:
|
||||
DUMMY_CONVERSION: 1
|
||||
steps:
|
||||
|
@ -121,7 +124,8 @@ jobs:
|
|||
macOS:
|
||||
name: "macOS (${{ matrix.arch }})"
|
||||
runs-on: ${{ matrix.runner }}
|
||||
needs: download-tessdata
|
||||
needs:
|
||||
- download-tessdata
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
|
@ -149,9 +153,10 @@ jobs:
|
|||
run: poetry run make test
|
||||
|
||||
build-deb:
|
||||
needs:
|
||||
- build-container-image
|
||||
name: "build-deb (${{ matrix.distro }} ${{ matrix.version }})"
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-container-image
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
|
@ -159,8 +164,6 @@ jobs:
|
|||
version: "20.04"
|
||||
- distro: ubuntu
|
||||
version: "22.04"
|
||||
- distro: ubuntu
|
||||
version: "23.10"
|
||||
- distro: ubuntu
|
||||
version: "24.04"
|
||||
- distro: ubuntu
|
||||
|
@ -221,7 +224,8 @@ jobs:
|
|||
install-deb:
|
||||
name: "install-deb (${{ matrix.distro }} ${{ matrix.version }})"
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-deb
|
||||
needs:
|
||||
- build-deb
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
|
@ -229,8 +233,6 @@ jobs:
|
|||
version: "20.04"
|
||||
- distro: ubuntu
|
||||
version: "22.04"
|
||||
- distro: ubuntu
|
||||
version: "23.10"
|
||||
- distro: ubuntu
|
||||
version: "24.04"
|
||||
- distro: ubuntu
|
||||
|
@ -277,7 +279,8 @@ jobs:
|
|||
build-install-rpm:
|
||||
name: "build-install-rpm (${{ matrix.distro }} ${{matrix.version}})"
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-container-image
|
||||
needs:
|
||||
- build-container-image
|
||||
strategy:
|
||||
matrix:
|
||||
distro: ["fedora"]
|
||||
|
@ -350,8 +353,6 @@ jobs:
|
|||
version: "20.04"
|
||||
- distro: ubuntu
|
||||
version: "22.04"
|
||||
- distro: ubuntu
|
||||
version: "23.10"
|
||||
- distro: ubuntu
|
||||
version: "24.04"
|
||||
- distro: ubuntu
|
||||
|
|
22
.github/workflows/close-issues.yml
vendored
Normal file
22
.github/workflows/close-issues.yml
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
name: Close inactive issues
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 1 * * *"
|
||||
|
||||
jobs:
|
||||
close-issues:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
days-before-issue-stale: 30
|
||||
days-before-issue-close: 14
|
||||
stale-issue-label: "stale"
|
||||
stale-issue-message: "Marking this issue as stale because it has been open for 30 days with no activity. It will be closed in 14 days if there's no activity, or if the `stale` label is not removed. Does anyone want to add something?"
|
||||
close-issue-message: "Closing this issue now. Don't hesitate to reopen if you have anything to add :-)"
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
any-of-labels: needs info
|
3
.github/workflows/scan.yml
vendored
3
.github/workflows/scan.yml
vendored
|
@ -1,8 +1,9 @@
|
|||
name: Scan latest app and container
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
schedule:
|
||||
- cron: '0 0 * * *' # Run every day at 00:00 UTC.
|
||||
workflow_dispatch:
|
||||
|
|
8
.github/workflows/scan_released.yml
vendored
8
.github/workflows/scan_released.yml
vendored
|
@ -10,12 +10,12 @@ jobs:
|
|||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Download container image for the latest release
|
||||
- name: Download container image for the latest release and load it
|
||||
run: |
|
||||
VERSION=$(curl https://api.github.com/repos/freedomofpress/dangerzone/releases/latest | jq -r '.tag_name')
|
||||
wget https://github.com/freedomofpress/dangerzone/releases/download/${VERSION}/container.tar.gz
|
||||
- name: Load container image
|
||||
run: docker load -i container.tar.gz
|
||||
CONTAINER_FILENAME=container-${VERSION:1}-i686.tar.gz
|
||||
wget https://github.com/freedomofpress/dangerzone/releases/download/${VERSION}/${CONTAINER_FILENAME} -O ${CONTAINER_FILENAME}
|
||||
docker load -i ${CONTAINER_FILENAME}
|
||||
# NOTE: Scan first without failing, else we won't be able to read the scan
|
||||
# report.
|
||||
- name: Scan container image (no fail)
|
||||
|
|
41
CHANGELOG.md
41
CHANGELOG.md
|
@ -5,16 +5,51 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||
since 0.4.1, and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased](https://github.com/freedomofpress/dangerzone/compare/v0.7.1...HEAD)
|
||||
## [Unreleased](https://github.com/freedomofpress/dangerzone/compare/v0.8.0...HEAD)
|
||||
|
||||
## [0.8.0](https://github.com/freedomofpress/dangerzone/compare/v0.8.0...0.7.1)
|
||||
|
||||
### Added
|
||||
|
||||
- Point to the installation instructions that the Tails team maintains for Dangerzone ([announcement](https://tails.net/news/dangerzone/index.en.html))
|
||||
- Platform support: Ubuntu 24.10 and Fedora 41 ([issue #947](https://github.com/freedomofpress/dangerzone/issues/947))
|
||||
- Installation and execution errors are now caught and displayed in the interface ([#193](https://github.com/freedomofpress/dangerzone/issues/193))
|
||||
- Prevent users from using illegal characters in output filename ([#362](https://github.com/freedomofpress/dangerzone/issues/362)). Thanks [@bnewc](https://github.com/bnewc) for the contribution!
|
||||
- Add support for Fedora 41 ([#947](https://github.com/freedomofpress/dangerzone/issues/947))
|
||||
- Add support for Ubuntu Oracular (24.10) ([#954](https://github.com/freedomofpress/dangerzone/pull/954))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Update our macOS entitlements, removing now unneeded privileges ([#638](https://github.com/freedomofpress/dangerzone/issues/638))
|
||||
- Make Dangerzone work on Linux systems with SELinux in enforcing mode ([#880](https://github.com/freedomofpress/dangerzone/issues/880))
|
||||
- Process documents with embedded multimedia files without crashing ([#877](https://github.com/freedomofpress/dangerzone/issues/877))
|
||||
- Search for applications that can read PDF files in a more reliable way on Linux ([#899](https://github.com/freedomofpress/dangerzone/issues/899))
|
||||
- Handle and report some stray conversion errors ([#776](https://github.com/freedomofpress/dangerzone/issues/776)). Thanks [@amnak613](https://github.com/amnak613) for the contribution!
|
||||
- Replace occurrences of the word "Docker" in Podman-related error messages in Linux ([#212](https://github.com/freedomofpress/dangerzone/issues/212))
|
||||
|
||||
### Changed
|
||||
|
||||
- The second phase of the conversion (pixels to PDF) now happens on the host. Instead of first grabbing all of the pixel data from the first container, storing them on disk, and then reconstructing the PDF on a second container, Dangerzone now immediately reconstructs the PDF **on the host**, while the doc to pixels conversion is still running on the first container. The sanitation is no less safe, since the boundaries between the sandbox and the host are still respected ([#625](https://github.com/freedomofpress/dangerzone/issues/625))
|
||||
- PyMuPDF is now vendorized for Debian packages. This is done because the PyMuPDF package from the Debian repos lacks OCR support ([#940](https://github.com/freedomofpress/dangerzone/pull/940))
|
||||
- Always use our own seccomp policy as a default ([#908](https://github.com/freedomofpress/dangerzone/issues/908))
|
||||
- Debian packages are now amd64 only, which removes some warnings in Linux distros with 32-bit repos enabled ([#394](https://github.com/freedomofpress/dangerzone/issues/394))
|
||||
- Allow choosing installation directory on Windows platforms ([#148](https://github.com/freedomofpress/dangerzone/issues/148)). Thanks [@jkarasti](https://github.com/jkarasti) for the contribution!
|
||||
- Bumped H2ORestart LibreOffice extension to version 0.6.6 ([#943](https://github.com/freedomofpress/dangerzone/issues/943))
|
||||
- Platform support: Ubuntu Focal (20.04) is now deprecated, and support will be dropped with the next release ([#965](https://github.com/freedomofpress/dangerzone/issues/965))
|
||||
|
||||
### Removed
|
||||
|
||||
- Platform support: Drop Ubuntu Mantic (23.10), since it's end-of-life ([#977](https://github.com/freedomofpress/dangerzone/pull/977))
|
||||
|
||||
### Development changes
|
||||
|
||||
- Build Debian packages with pybuild ([#773](https://github.com/freedomofpress/dangerzone/issues/773))
|
||||
- Test Dangerzone on Intel macOS machines as well ([#932](https://github.com/freedomofpress/dangerzone/issues/932))
|
||||
- Switch from CircleCI runners to Github actions ([#674](https://github.com/freedomofpress/dangerzone/issues/674))
|
||||
- Sign Windows executables and installer with SHA256 rather than SHA1 ([#931](https://github.com/freedomofpress/dangerzone/pull/931)). Thanks [@jkarasti](https://github.com/jkarasti) for the contribution!
|
||||
|
||||
## [0.7.1](https://github.com/freedomofpress/dangerzone/compare/v0.7.1...v0.7.0)
|
||||
|
||||
### Fixed
|
||||
### Fixed
|
||||
|
||||
- Fix an `image-id.txt` mismatch happening on Docker Desktop >= 4.30.0 ([#933](https://github.com/freedomofpress/dangerzone/issues/933))
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ an isolated environment. It will be installed automatically when installing Dang
|
|||
Dangerzone is available for:
|
||||
- Ubuntu 24.10 (oracular)
|
||||
- Ubuntu 24.04 (noble)
|
||||
- Ubuntu 23.10 (mantic)
|
||||
- Ubuntu 22.04 (jammy)
|
||||
- Ubuntu 20.04 (focal)
|
||||
- Debian 13 (trixie)
|
||||
|
@ -290,7 +289,7 @@ Our [GitHub Releases page](https://github.com/freedomofpress/dangerzone/releases
|
|||
hosts the following files:
|
||||
* Windows installer (`Dangerzone-<version>.msi`)
|
||||
* macOS archives (`Dangerzone-<version>-<arch>.dmg`)
|
||||
* Container image (`container.tar.gz`)
|
||||
* Container images (`container-<version>-<arch>.tar.gz`)
|
||||
* Source package (`dangerzone-<version>.tar.gz`)
|
||||
|
||||
All these files are accompanied by signatures (as `.asc` files). We'll explain
|
||||
|
@ -315,10 +314,10 @@ gpg --verify Dangerzone-0.6.1-arm64.dmg.asc Dangerzone-0.6.1-arm64.dmg
|
|||
gpg --verify Dangerzone-0.6.1-i686.dmg.asc Dangerzone-0.6.1-i686.dmg
|
||||
```
|
||||
|
||||
For the container image:
|
||||
For the container images:
|
||||
|
||||
```
|
||||
gpg --verify container.tar.gz.asc container.tar.gz
|
||||
gpg --verify container-0.6.1-i686.tar.gz.asc container-0.6.1-i686.tar.gz
|
||||
```
|
||||
|
||||
For the source package:
|
||||
|
|
|
@ -13,8 +13,8 @@ _Read more about Dangerzone in the [official site](https://dangerzone.rocks/abou
|
|||
## Getting started
|
||||
|
||||
### MacOS
|
||||
- Download [Dangerzone 0.7.1 for Mac (Apple Silicon CPU)](https://github.com/freedomofpress/dangerzone/releases/download/v0.7.1/Dangerzone-0.7.1-arm64.dmg)
|
||||
- Download [Dangerzone 0.7.1 for Mac (Intel CPU)](https://github.com/freedomofpress/dangerzone/releases/download/v0.7.1/Dangerzone-0.7.1-i686.dmg)
|
||||
- Download [Dangerzone 0.8.0 for Mac (Apple Silicon CPU)](https://github.com/freedomofpress/dangerzone/releases/download/v0.8.0/Dangerzone-0.8.0-arm64.dmg)
|
||||
- Download [Dangerzone 0.8.0 for Mac (Intel CPU)](https://github.com/freedomofpress/dangerzone/releases/download/v0.8.0/Dangerzone-0.8.0-i686.dmg)
|
||||
|
||||
You can also install Dangerzone for Mac using [Homebrew](https://brew.sh/): `brew install --cask dangerzone`
|
||||
|
||||
|
@ -24,7 +24,7 @@ You can also install Dangerzone for Mac using [Homebrew](https://brew.sh/): `bre
|
|||
|
||||
### Windows
|
||||
|
||||
- Download [Dangerzone 0.7.1 for Windows](https://github.com/freedomofpress/dangerzone/releases/download/v0.7.1/Dangerzone-0.7.1.msi)
|
||||
- Download [Dangerzone 0.8.0 for Windows](https://github.com/freedomofpress/dangerzone/releases/download/v0.8.0/Dangerzone-0.8.0.msi)
|
||||
|
||||
> **Note**: you will also need to install [Docker Desktop](https://www.docker.com/products/docker-desktop/).
|
||||
> This program needs to run alongside Dangerzone at all times, since it is what allows Dangerzone to
|
||||
|
|
11
RELEASE.md
11
RELEASE.md
|
@ -285,6 +285,11 @@ Once we are confident that the release will be out shortly, and doesn't need any
|
|||
* You can verify the correct Python version is used with `poetry debug info`
|
||||
- [ ] Verify and checkout the git tag for this release
|
||||
- [ ] Run `poetry install --sync`
|
||||
- [ ] On the silicon mac, build the container image:
|
||||
```
|
||||
python3 ./install/common/build-image.py
|
||||
```
|
||||
Then copy the `share/container.tar.gz` to the assets folder on `dangerzone-$VERSION-arm64.tar.gz`, along with the `share/image-id.txt` file.
|
||||
- [ ] Run `poetry run ./install/macos/build-app.py`; this will make `dist/Dangerzone.app`
|
||||
- [ ] Make sure that the build application works with the containerd graph
|
||||
driver (see [#933](https://github.com/freedomofpress/dangerzone/issues/933))
|
||||
|
@ -403,6 +408,8 @@ Build the latest container:
|
|||
python3 ./install/common/build-image.py
|
||||
```
|
||||
|
||||
Copy the container image to the assets folder on `dangerzone-$VERSION-i686.tar.gz`.
|
||||
|
||||
Create a .rpm:
|
||||
|
||||
```sh
|
||||
|
@ -449,9 +456,9 @@ To publish the release:
|
|||
* Copy the release notes text from the template at [`docs/templates/release-notes`](https://github.com/freedomofpress/dangerzone/tree/main/docs/templates/)
|
||||
* You can use `./dev_scripts/upload-asset.py`, if you want to upload an asset
|
||||
using an access token.
|
||||
- [ ] Upload the `container.tar.gz` i686 image that was created in the previous step
|
||||
- [ ] Upload the `container-$VERSION-i686.tar.gz` and `container-$VERSION-arm64.tar.gz` images that were created in the previous step
|
||||
|
||||
**Important:** Make sure that it's the same container image as the ones that
|
||||
**Important:** Make sure that it's the same container images as the ones that
|
||||
are shipped in other platforms (see our [Pre-release](#Pre-release) section)
|
||||
|
||||
- [ ] Upload the detached signatures (.asc) and checksum file.
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import logging
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
import tempfile
|
||||
import typing
|
||||
from multiprocessing.pool import ThreadPool
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
# FIXME: See https://github.com/freedomofpress/dangerzone/issues/320 for more details.
|
||||
|
@ -20,15 +20,19 @@ else:
|
|||
from PySide6.QtWidgets import QTextEdit
|
||||
except ImportError:
|
||||
from PySide2 import QtCore, QtGui, QtSvg, QtWidgets
|
||||
from PySide2.QtWidgets import QAction, QTextEdit
|
||||
from PySide2.QtCore import Qt
|
||||
from PySide2.QtWidgets import QAction, QTextEdit
|
||||
|
||||
from .. import errors
|
||||
from ..document import SAFE_EXTENSION, Document
|
||||
from ..isolation_provider.container import Container, NoContainerTechException
|
||||
from ..isolation_provider.container import (
|
||||
Container,
|
||||
NoContainerTechException,
|
||||
NotAvailableContainerTechException,
|
||||
)
|
||||
from ..isolation_provider.dummy import Dummy
|
||||
from ..isolation_provider.qubes import Qubes, is_qubes_native_conversion
|
||||
from ..util import get_resource_path, get_subprocess_startupinfo, get_version
|
||||
from ..util import format_exception, get_resource_path, get_version
|
||||
from .logic import Alert, CollapsibleBox, DangerzoneGui, UpdateDialog
|
||||
from .updater import UpdateReport
|
||||
|
||||
|
@ -57,6 +61,13 @@ about updates.</p>
|
|||
HAMBURGER_MENU_SIZE = 30
|
||||
|
||||
|
||||
WARNING_MESSAGE = """\
|
||||
<p><b>Warning:</b> Ubuntu Focal systems and their derivatives will
|
||||
stop being supported in subsequent Dangerzone releases. We encourage you to upgrade to a
|
||||
more recent version of your operating system in order to get security updates.</p>
|
||||
"""
|
||||
|
||||
|
||||
def load_svg_image(filename: str, width: int, height: int) -> QtGui.QPixmap:
|
||||
"""Load an SVG image from a filename.
|
||||
|
||||
|
@ -388,15 +399,24 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||
|
||||
|
||||
class InstallContainerThread(QtCore.QThread):
|
||||
finished = QtCore.Signal()
|
||||
finished = QtCore.Signal(str)
|
||||
|
||||
def __init__(self, dangerzone: DangerzoneGui) -> None:
|
||||
super(InstallContainerThread, self).__init__()
|
||||
self.dangerzone = dangerzone
|
||||
|
||||
def run(self) -> None:
|
||||
self.dangerzone.isolation_provider.install()
|
||||
self.finished.emit()
|
||||
error = None
|
||||
try:
|
||||
installed = self.dangerzone.isolation_provider.install()
|
||||
except Exception as e:
|
||||
log.error("Container installation problem")
|
||||
error = format_exception(e)
|
||||
else:
|
||||
if not installed:
|
||||
error = "The image cannot be found. This can be caused by a faulty container image."
|
||||
finally:
|
||||
self.finished.emit(error)
|
||||
|
||||
|
||||
class WaitingWidget(QtWidgets.QWidget):
|
||||
|
@ -423,9 +443,10 @@ class TracebackWidget(QTextEdit):
|
|||
# Enable copying
|
||||
self.setTextInteractionFlags(Qt.TextSelectableByMouse)
|
||||
|
||||
def set_content(self, error: str) -> None:
|
||||
self.setPlainText(error)
|
||||
self.setVisible(True)
|
||||
def set_content(self, error: Optional[str] = None) -> None:
|
||||
if error:
|
||||
self.setPlainText(error)
|
||||
self.setVisible(True)
|
||||
|
||||
|
||||
class WaitingWidgetContainer(WaitingWidget):
|
||||
|
@ -438,7 +459,6 @@ class WaitingWidgetContainer(WaitingWidget):
|
|||
#
|
||||
# Linux states
|
||||
# - "install_container"
|
||||
finished = QtCore.Signal()
|
||||
|
||||
def __init__(self, dangerzone: DangerzoneGui) -> None:
|
||||
super(WaitingWidgetContainer, self).__init__()
|
||||
|
@ -480,49 +500,61 @@ class WaitingWidgetContainer(WaitingWidget):
|
|||
error: Optional[str] = None
|
||||
|
||||
try:
|
||||
if isinstance( # Sanity check
|
||||
self.dangerzone.isolation_provider, Container
|
||||
):
|
||||
container_runtime = self.dangerzone.isolation_provider.get_runtime()
|
||||
runtime_name = self.dangerzone.isolation_provider.get_runtime_name()
|
||||
self.dangerzone.isolation_provider.is_runtime_available()
|
||||
except NoContainerTechException as e:
|
||||
log.error(str(e))
|
||||
state = "not_installed"
|
||||
|
||||
except NotAvailableContainerTechException as e:
|
||||
log.error(str(e))
|
||||
state = "not_running"
|
||||
error = e.error
|
||||
except Exception as e:
|
||||
log.error(str(e))
|
||||
state = "not_running"
|
||||
error = format_exception(e)
|
||||
else:
|
||||
# Can we run `docker/podman image ls` without an error
|
||||
with subprocess.Popen(
|
||||
[container_runtime, "image", "ls"],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.PIPE,
|
||||
startupinfo=get_subprocess_startupinfo(),
|
||||
) as p:
|
||||
_, stderr = p.communicate()
|
||||
if p.returncode != 0:
|
||||
log.error(f"{runtime_name} is not running")
|
||||
state = "not_running"
|
||||
error = stderr.decode()
|
||||
else:
|
||||
# Always try installing the container
|
||||
state = "install_container"
|
||||
state = "install_container"
|
||||
|
||||
# Update the state
|
||||
self.state_change(state, error)
|
||||
|
||||
def show_error(self, msg: str, details: Optional[str] = None) -> None:
|
||||
self.label.setText(msg)
|
||||
show_traceback = details is not None
|
||||
if show_traceback:
|
||||
self.traceback.set_content(details)
|
||||
self.traceback.setVisible(show_traceback)
|
||||
self.buttons.show()
|
||||
|
||||
def show_message(self, msg: str) -> None:
|
||||
self.label.setText(msg)
|
||||
self.traceback.setVisible(False)
|
||||
self.buttons.hide()
|
||||
|
||||
def installation_finished(self, error: Optional[str] = None) -> None:
|
||||
if error:
|
||||
msg = (
|
||||
"During installation of the dangerzone image, <br>"
|
||||
"the following error occured:"
|
||||
)
|
||||
self.show_error(msg, error)
|
||||
else:
|
||||
self.finished.emit()
|
||||
|
||||
def state_change(self, state: str, error: Optional[str] = None) -> None:
|
||||
if state == "not_installed":
|
||||
if platform.system() == "Linux":
|
||||
self.label.setText(
|
||||
self.show_error(
|
||||
"<strong>Dangerzone requires Podman</strong><br><br>"
|
||||
"Install it and retry."
|
||||
)
|
||||
else:
|
||||
self.label.setText(
|
||||
self.show_error(
|
||||
"<strong>Dangerzone requires Docker Desktop</strong><br><br>"
|
||||
"<a href='https://www.docker.com/products/docker-desktop'>Download Docker Desktop</a>"
|
||||
", install it, and open it."
|
||||
)
|
||||
self.buttons.show()
|
||||
|
||||
elif state == "not_running":
|
||||
if platform.system() == "Linux":
|
||||
# "not_running" here means that the `podman image ls` command failed.
|
||||
|
@ -530,27 +562,20 @@ class WaitingWidgetContainer(WaitingWidget):
|
|||
"<strong>Dangerzone requires Podman</strong><br><br>"
|
||||
"Podman is installed but cannot run properly. See errors below"
|
||||
)
|
||||
if error:
|
||||
self.traceback.set_content(error)
|
||||
|
||||
self.label.setText(message)
|
||||
|
||||
else:
|
||||
self.label.setText(
|
||||
message = (
|
||||
"<strong>Dangerzone requires Docker Desktop</strong><br><br>"
|
||||
"Docker is installed but isn't running.<br><br>"
|
||||
"Open Docker and make sure it's running in the background."
|
||||
)
|
||||
self.buttons.show()
|
||||
self.show_error(message, error)
|
||||
else:
|
||||
self.label.setText(
|
||||
self.show_message(
|
||||
"Installing the Dangerzone container image.<br><br>"
|
||||
"This might take a few minutes..."
|
||||
)
|
||||
self.buttons.hide()
|
||||
self.traceback.setVisible(False)
|
||||
self.install_container_t = InstallContainerThread(self.dangerzone)
|
||||
self.install_container_t.finished.connect(self.finished)
|
||||
self.install_container_t.finished.connect(self.installation_finished)
|
||||
self.install_container_t.start()
|
||||
|
||||
|
||||
|
@ -562,6 +587,17 @@ class ContentWidget(QtWidgets.QWidget):
|
|||
self.dangerzone = dangerzone
|
||||
self.conversion_started = False
|
||||
|
||||
self.warning_label = None
|
||||
if platform.system() == "Linux":
|
||||
# Add the warning message only for ubuntu focal
|
||||
os_release_path = Path("/etc/os-release")
|
||||
if os_release_path.exists():
|
||||
os_release = os_release_path.read_text()
|
||||
if "Ubuntu 20.04" in os_release or "focal" in os_release:
|
||||
self.warning_label = QtWidgets.QLabel(WARNING_MESSAGE)
|
||||
self.warning_label.setWordWrap(True)
|
||||
self.warning_label.setProperty("style", "warning")
|
||||
|
||||
# Doc selection widget
|
||||
self.doc_selection_widget = DocSelectionWidget(self.dangerzone)
|
||||
self.doc_selection_widget.documents_selected.connect(self.documents_selected)
|
||||
|
@ -587,6 +623,8 @@ class ContentWidget(QtWidgets.QWidget):
|
|||
|
||||
# Layout
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
if self.warning_label:
|
||||
layout.addWidget(self.warning_label) # Add warning at the top
|
||||
layout.addWidget(self.settings_widget, stretch=1)
|
||||
layout.addWidget(self.documents_list, stretch=1)
|
||||
layout.addWidget(self.doc_selection_wrapper, stretch=1)
|
||||
|
|
|
@ -142,9 +142,6 @@ runsc_argv = [
|
|||
"--rootless=true",
|
||||
"--network=none",
|
||||
"--root=/home/dangerzone/.containers",
|
||||
# Disable DirectFS for to make the seccomp filter even stricter,
|
||||
# at some performance cost.
|
||||
"--directfs=false",
|
||||
]
|
||||
if os.environ.get("RUNSC_DEBUG"):
|
||||
runsc_argv += ["--debug=true", "--alsologtostderr=true"]
|
||||
|
|
|
@ -93,6 +93,10 @@ class IsolationProvider(ABC):
|
|||
else:
|
||||
self.proc_stderr = subprocess.DEVNULL
|
||||
|
||||
@staticmethod
|
||||
def is_runtime_available() -> bool:
|
||||
return True
|
||||
|
||||
@abstractmethod
|
||||
def install(self) -> bool:
|
||||
pass
|
||||
|
|
|
@ -30,6 +30,21 @@ class NoContainerTechException(Exception):
|
|||
super().__init__(f"{container_tech} is not installed")
|
||||
|
||||
|
||||
class NotAvailableContainerTechException(Exception):
|
||||
def __init__(self, container_tech: str, error: str) -> None:
|
||||
self.error = error
|
||||
self.container_tech = container_tech
|
||||
super().__init__(f"{container_tech} is not available")
|
||||
|
||||
|
||||
class ImageNotPresentException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ImageInstallationException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Container(IsolationProvider):
|
||||
# Name of the dangerzone container
|
||||
CONTAINER_NAME = "dangerzone.rocks/dangerzone"
|
||||
|
@ -156,7 +171,7 @@ class Container(IsolationProvider):
|
|||
startupinfo=get_subprocess_startupinfo(),
|
||||
)
|
||||
|
||||
chunk_size = 10240
|
||||
chunk_size = 4 << 20
|
||||
compressed_container_path = get_resource_path("container.tar.gz")
|
||||
with gzip.open(compressed_container_path) as f:
|
||||
while True:
|
||||
|
@ -166,19 +181,42 @@ class Container(IsolationProvider):
|
|||
p.stdin.write(chunk)
|
||||
else:
|
||||
break
|
||||
p.communicate()
|
||||
_, err = p.communicate()
|
||||
if p.returncode < 0:
|
||||
if err:
|
||||
error = err.decode()
|
||||
else:
|
||||
error = "No output"
|
||||
raise ImageInstallationException(
|
||||
f"Could not install container image: {error}"
|
||||
)
|
||||
|
||||
if not Container.is_container_installed():
|
||||
log.error("Failed to install the container image")
|
||||
if not Container.is_container_installed(raise_on_error=True):
|
||||
return False
|
||||
|
||||
log.info("Container image installed")
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def is_container_installed() -> bool:
|
||||
def is_runtime_available() -> bool:
|
||||
container_runtime = Container.get_runtime()
|
||||
runtime_name = Container.get_runtime_name()
|
||||
# Can we run `docker/podman image ls` without an error
|
||||
with subprocess.Popen(
|
||||
[container_runtime, "image", "ls"],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.PIPE,
|
||||
startupinfo=get_subprocess_startupinfo(),
|
||||
) as p:
|
||||
_, stderr = p.communicate()
|
||||
if p.returncode != 0:
|
||||
raise NotAvailableContainerTechException(runtime_name, stderr.decode())
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def is_container_installed(raise_on_error: bool = False) -> bool:
|
||||
"""
|
||||
See if the podman container is installed. Linux only.
|
||||
See if the container is installed.
|
||||
"""
|
||||
# Get the image id
|
||||
with open(get_resource_path("image-id.txt")) as f:
|
||||
|
@ -203,8 +241,18 @@ class Container(IsolationProvider):
|
|||
if found_image_id in expected_image_ids:
|
||||
installed = True
|
||||
elif found_image_id == "":
|
||||
pass
|
||||
if raise_on_error:
|
||||
raise ImageNotPresentException(
|
||||
"Image is not listed after installation. Bailing out."
|
||||
)
|
||||
else:
|
||||
msg = (
|
||||
f"{Container.CONTAINER_NAME} images found, but IDs do not match."
|
||||
f" Found: {found_image_id}, Expected: {','.join(expected_image_ids)}"
|
||||
)
|
||||
if raise_on_error:
|
||||
raise ImageNotPresentException(msg)
|
||||
log.info(msg)
|
||||
log.info("Deleting old dangerzone container image")
|
||||
|
||||
try:
|
||||
|
|
|
@ -70,14 +70,18 @@ class Qubes(IsolationProvider):
|
|||
standard streams explicitly, so that we can afterwards use `Popen.wait()` to
|
||||
learn if the qube terminated.
|
||||
|
||||
Note that we don't close the stderr stream because we want to read debug logs
|
||||
from it. In the rare case where a qube cannot terminate because it's stuck
|
||||
writing at stderr (this is not the expected behavior), we expect that the
|
||||
process will still be forcefully killed after the soft termination timeout
|
||||
expires.
|
||||
|
||||
[1]: https://github.com/freedomofpress/dangerzone/issues/563#issuecomment-2034803232
|
||||
"""
|
||||
if p.stdin:
|
||||
p.stdin.close()
|
||||
if p.stdout:
|
||||
p.stdout.close()
|
||||
if p.stderr:
|
||||
p.stderr.close()
|
||||
|
||||
def teleport_dz_module(self, wpipe: IO[bytes]) -> None:
|
||||
"""Send the dangerzone module to another qube, as a zipfile."""
|
||||
|
|
|
@ -2,6 +2,7 @@ import pathlib
|
|||
import platform
|
||||
import subprocess
|
||||
import sys
|
||||
import traceback
|
||||
import unicodedata
|
||||
|
||||
import appdirs
|
||||
|
@ -117,3 +118,13 @@ def replace_control_chars(untrusted_str: str, keep_newlines: bool = False) -> st
|
|||
else:
|
||||
sanitized_str += "<EFBFBD>"
|
||||
return sanitized_str
|
||||
|
||||
|
||||
def format_exception(e: Exception) -> str:
|
||||
# The signature of traceback.format_exception has changed in python 3.10
|
||||
if sys.version_info < (3, 10):
|
||||
output = traceback.format_exception(*sys.exc_info())
|
||||
else:
|
||||
output = traceback.format_exception(e)
|
||||
|
||||
return "".join(output)
|
||||
|
|
6
debian/changelog
vendored
6
debian/changelog
vendored
|
@ -1,3 +1,9 @@
|
|||
dangerzone (0.8.0) unstable; urgency=low
|
||||
|
||||
* Released Dangerzone 0.8.0
|
||||
|
||||
-- Freedom of the Press Foundation <info@freedom.press> Tue, 30 Oct 2024 01:56:28 +0300
|
||||
|
||||
dangerzone (0.7.1) unstable; urgency=low
|
||||
|
||||
* Released Dangerzone 0.7.1
|
||||
|
|
|
@ -696,8 +696,6 @@ class Env:
|
|||
DOCKERFILE_CONMON_UPDATE + DOCKERFILE_BUILD_DEV_DEBIAN_DEPS
|
||||
)
|
||||
elif self.distro == "ubuntu" and self.version in (
|
||||
"23.10",
|
||||
"mantic",
|
||||
"24.04",
|
||||
"noble",
|
||||
"24.10",
|
||||
|
@ -784,8 +782,6 @@ class Env:
|
|||
# package (see https://github.com/freedomofpress/dangerzone/issues/685)
|
||||
install_deps = DOCKERFILE_CONMON_UPDATE + DOCKERFILE_BUILD_DEBIAN_DEPS
|
||||
elif self.distro == "ubuntu" and self.version in (
|
||||
"23.10",
|
||||
"mantic",
|
||||
"24.04",
|
||||
"noble",
|
||||
"24.10",
|
||||
|
|
|
@ -978,11 +978,6 @@ class QAUbuntu2204(QADebianBased):
|
|||
VERSION = "22.04"
|
||||
|
||||
|
||||
class QAUbuntu2310(QADebianBased):
|
||||
DISTRO = "ubuntu"
|
||||
VERSION = "23.10"
|
||||
|
||||
|
||||
class QAUbuntu2404(QADebianBased):
|
||||
DISTRO = "ubuntu"
|
||||
VERSION = "24.04"
|
||||
|
|
|
@ -11,7 +11,8 @@ log = logging.getLogger(__name__)
|
|||
|
||||
|
||||
DZ_ASSETS = [
|
||||
"container.tar.gz",
|
||||
"container-{version}-i686.tar.gz",
|
||||
"container-{version}-arm64.tar.gz",
|
||||
"Dangerzone-{version}.msi",
|
||||
"Dangerzone-{version}-arm64.dmg",
|
||||
"Dangerzone-{version}-i686.dmg",
|
||||
|
|
|
@ -32,7 +32,7 @@ Name: dangerzone-qubes
|
|||
Name: dangerzone
|
||||
%endif
|
||||
|
||||
Version: 0.7.1
|
||||
Version: 0.8.0
|
||||
Release: 1%{?dist}
|
||||
Summary: Take potentially dangerous PDFs, office documents, or images and convert them to safe PDFs
|
||||
|
||||
|
|
|
@ -10,11 +10,5 @@
|
|||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
<key>com.apple.security.hypervisor</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
</plist>
|
||||
|
|
799
poetry.lock
generated
799
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "dangerzone"
|
||||
version = "0.7.1"
|
||||
version = "0.8.0"
|
||||
description = "Take potentially dangerous PDFs, office documents, or images and convert them to safe PDFs"
|
||||
authors = ["Freedom of the Press Foundation <info@freedom.press>", "Micah Lee <micah.lee@theintercept.com>"]
|
||||
license = "AGPL-3.0"
|
||||
|
@ -31,7 +31,7 @@ dangerzone-cli = 'dangerzone:main'
|
|||
# Dependencies required for packaging the code on various platforms.
|
||||
[tool.poetry.group.package.dependencies]
|
||||
setuptools = "*"
|
||||
cx_freeze = {version = "^7.1.1", platform = "win32"}
|
||||
cx_freeze = {version = "^7.2.5", platform = "win32"}
|
||||
pywin32 = {version = "*", platform = "win32"}
|
||||
pyinstaller = {version = "*", platform = "darwin"}
|
||||
|
||||
|
@ -51,9 +51,11 @@ pytest-mock = "^3.10.0"
|
|||
pytest-qt = "^4.2.0"
|
||||
pytest-cov = "^5.0.0"
|
||||
strip-ansi = "*"
|
||||
pytest-subprocess = "^1.5.2"
|
||||
pytest-rerunfailures = "^14.0"
|
||||
|
||||
[tool.poetry.group.container.dependencies]
|
||||
pymupdf = "^1.24.10"
|
||||
pymupdf = "1.24.11" # Last version to support python 3.8 (needed for Ubuntu Focal support)
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
|
|
|
@ -4,7 +4,6 @@ from cx_Freeze import Executable, setup
|
|||
with open("share/version.txt") as f:
|
||||
version = f.read().strip()
|
||||
|
||||
packages = ["dangerzone", "dangerzone.gui"]
|
||||
|
||||
setup(
|
||||
name="dangerzone",
|
||||
|
@ -12,10 +11,9 @@ setup(
|
|||
# On Windows description will show as the app's name in the "Open With" menu. See:
|
||||
# https://github.com/freedomofpress/dangerzone/issues/283#issuecomment-1365148805
|
||||
description="Dangerzone",
|
||||
packages=packages,
|
||||
options={
|
||||
"build_exe": {
|
||||
"packages": packages,
|
||||
"packages": ["dangerzone", "dangerzone.gui"],
|
||||
"excludes": ["test", "tkinter"],
|
||||
"include_files": [("share", "share"), ("LICENSE", "LICENSE")],
|
||||
"include_msvcr": True,
|
||||
|
|
|
@ -55,4 +55,20 @@ QTextEdit[style="traceback"] {
|
|||
background-color: #ffffff;
|
||||
color: #000000;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
QLabel[style="warning"] {
|
||||
background-color: #FFF3CD;
|
||||
color: #856404;
|
||||
border: 1px solid #FFEEBA;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
|
||||
MainWindow[OSColorMode="dark"] QLabel[style="warning"] {
|
||||
background-color: #332D00;
|
||||
color: #FFD970;
|
||||
border-color: #665A00;
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.7.1
|
||||
0.8.0
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import os
|
||||
import pathlib
|
||||
import platform
|
||||
import shutil
|
||||
import time
|
||||
from typing import List
|
||||
|
||||
from pytest import MonkeyPatch, fixture
|
||||
from pytest_mock import MockerFixture
|
||||
from pytest_subprocess import FakeProcess
|
||||
from pytestqt.qtbot import QtBot
|
||||
|
||||
from dangerzone.document import Document
|
||||
|
@ -13,12 +15,21 @@ from dangerzone.gui import MainWindow
|
|||
from dangerzone.gui import main_window as main_window_module
|
||||
from dangerzone.gui import updater as updater_module
|
||||
from dangerzone.gui.logic import DangerzoneGui
|
||||
from dangerzone.gui.main_window import ( # import Pyside related objects from here to avoid duplicating import logic.
|
||||
|
||||
# import Pyside related objects from here to avoid duplicating import logic.
|
||||
from dangerzone.gui.main_window import (
|
||||
ContentWidget,
|
||||
InstallContainerThread,
|
||||
QtCore,
|
||||
QtGui,
|
||||
WaitingWidgetContainer,
|
||||
)
|
||||
from dangerzone.gui.updater import UpdateReport, UpdaterThread
|
||||
from dangerzone.isolation_provider.container import (
|
||||
Container,
|
||||
NoContainerTechException,
|
||||
NotAvailableContainerTechException,
|
||||
)
|
||||
|
||||
from .test_updater import assert_report_equal, default_updater_settings
|
||||
|
||||
|
@ -492,3 +503,90 @@ def test_drop_1_invalid_2_valid_documents(
|
|||
content_widget.doc_selection_wrapper.dropEvent(
|
||||
drag_1_invalid_and_2_valid_files_event
|
||||
)
|
||||
|
||||
|
||||
def test_not_available_container_tech_exception(
|
||||
qtbot: QtBot, mocker: MockerFixture
|
||||
) -> None:
|
||||
# Setup
|
||||
mock_app = mocker.MagicMock()
|
||||
dummy = mocker.MagicMock()
|
||||
|
||||
dummy.is_runtime_available.side_effect = NotAvailableContainerTechException(
|
||||
"podman", "podman image ls logs"
|
||||
)
|
||||
|
||||
dz = DangerzoneGui(mock_app, dummy)
|
||||
widget = WaitingWidgetContainer(dz)
|
||||
qtbot.addWidget(widget)
|
||||
|
||||
# Assert that the error is displayed in the GUI
|
||||
if platform.system() in ["Darwin", "Windows"]:
|
||||
assert "Dangerzone requires Docker Desktop" in widget.label.text()
|
||||
else:
|
||||
assert "Podman is installed but cannot run properly" in widget.label.text()
|
||||
|
||||
assert "podman image ls logs" in widget.traceback.toPlainText()
|
||||
|
||||
|
||||
def test_no_container_tech_exception(qtbot: QtBot, mocker: MockerFixture) -> None:
|
||||
# Setup
|
||||
mock_app = mocker.MagicMock()
|
||||
dummy = mocker.MagicMock()
|
||||
|
||||
# Raise
|
||||
dummy.is_runtime_available.side_effect = NoContainerTechException("podman")
|
||||
|
||||
dz = DangerzoneGui(mock_app, dummy)
|
||||
widget = WaitingWidgetContainer(dz)
|
||||
qtbot.addWidget(widget)
|
||||
|
||||
# Assert that the error is displayed in the GUI
|
||||
if platform.system() in ["Darwin", "Windows"]:
|
||||
assert "Dangerzone requires Docker Desktop" in widget.label.text()
|
||||
else:
|
||||
assert "Dangerzone requires Podman" in widget.label.text()
|
||||
|
||||
|
||||
def test_installation_failure_exception(qtbot: QtBot, mocker: MockerFixture) -> None:
|
||||
"""Ensures that if an exception is raised during image installation,
|
||||
it is shown in the GUI.
|
||||
"""
|
||||
# Setup install to raise an exception
|
||||
mock_app = mocker.MagicMock()
|
||||
dummy = mocker.MagicMock(spec=Container)
|
||||
dummy.install.side_effect = RuntimeError("Error during install")
|
||||
|
||||
dz = DangerzoneGui(mock_app, dummy)
|
||||
|
||||
# Mock the InstallContainerThread to call the original run method instead of
|
||||
# starting a new thread
|
||||
mocker.patch.object(InstallContainerThread, "start", InstallContainerThread.run)
|
||||
widget = WaitingWidgetContainer(dz)
|
||||
qtbot.addWidget(widget)
|
||||
|
||||
assert dummy.install.call_count == 1
|
||||
|
||||
assert "Error during install" in widget.traceback.toPlainText()
|
||||
assert "RuntimeError" in widget.traceback.toPlainText()
|
||||
|
||||
|
||||
def test_installation_failure_return_false(qtbot: QtBot, mocker: MockerFixture) -> None:
|
||||
"""Ensures that if the installation returns False, the error is shown in the GUI."""
|
||||
# Setup install to return False
|
||||
mock_app = mocker.MagicMock()
|
||||
dummy = mocker.MagicMock(spec=Container)
|
||||
dummy.install.return_value = False
|
||||
|
||||
dz = DangerzoneGui(mock_app, dummy)
|
||||
|
||||
# Mock the InstallContainerThread to call the original run method instead of
|
||||
# starting a new thread
|
||||
mocker.patch.object(InstallContainerThread, "start", InstallContainerThread.run)
|
||||
widget = WaitingWidgetContainer(dz)
|
||||
qtbot.addWidget(widget)
|
||||
|
||||
assert dummy.install.call_count == 1
|
||||
|
||||
assert "the following error occured" in widget.label.text()
|
||||
assert "The image cannot be found" in widget.traceback.toPlainText()
|
||||
|
|
|
@ -7,7 +7,6 @@ from pytest_mock import MockerFixture
|
|||
from dangerzone.conversion import errors
|
||||
from dangerzone.document import Document
|
||||
from dangerzone.isolation_provider import base
|
||||
from dangerzone.isolation_provider.qubes import running_on_qubes
|
||||
|
||||
TIMEOUT_STARTUP = 60 # Timeout in seconds until the conversion sandbox starts.
|
||||
|
||||
|
@ -165,6 +164,7 @@ class IsolationProviderTermination:
|
|||
terminate_proc_mock = mocker.patch.object(
|
||||
provider, "terminate_doc_to_pixels_proc", return_value=None
|
||||
)
|
||||
kill_pg_orig = base.kill_process_group
|
||||
kill_pg_mock = mocker.patch(
|
||||
"dangerzone.isolation_provider.base.kill_process_group", return_value=None
|
||||
)
|
||||
|
@ -179,6 +179,7 @@ class IsolationProviderTermination:
|
|||
|
||||
# Reset the function to the original state.
|
||||
provider.terminate_doc_to_pixels_proc = terminate_proc_orig # type: ignore [method-assign]
|
||||
base.kill_process_group = kill_pg_orig
|
||||
|
||||
# Really kill the spawned process, so that it doesn't linger after the tests
|
||||
# complete.
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
import os
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
from pytest_subprocess import FakeProcess
|
||||
|
||||
from dangerzone.isolation_provider.container import Container
|
||||
from dangerzone.isolation_provider.container import (
|
||||
Container,
|
||||
ImageInstallationException,
|
||||
ImageNotPresentException,
|
||||
NotAvailableContainerTechException,
|
||||
)
|
||||
from dangerzone.isolation_provider.qubes import is_qubes_native_conversion
|
||||
|
||||
from .base import IsolationProviderTermination, IsolationProviderTest
|
||||
|
@ -22,7 +27,94 @@ def provider() -> Container:
|
|||
|
||||
|
||||
class TestContainer(IsolationProviderTest):
|
||||
pass
|
||||
def test_is_runtime_available_raises(
|
||||
self, provider: Container, fp: FakeProcess
|
||||
) -> None:
|
||||
"""
|
||||
NotAvailableContainerTechException should be raised when
|
||||
the "podman image ls" command fails.
|
||||
"""
|
||||
fp.register_subprocess(
|
||||
[provider.get_runtime(), "image", "ls"],
|
||||
returncode=-1,
|
||||
stderr="podman image ls logs",
|
||||
)
|
||||
with pytest.raises(NotAvailableContainerTechException):
|
||||
provider.is_runtime_available()
|
||||
|
||||
def test_is_runtime_available_works(
|
||||
self, provider: Container, fp: FakeProcess
|
||||
) -> None:
|
||||
"""
|
||||
No exception should be raised when the "podman image ls" can return properly.
|
||||
"""
|
||||
fp.register_subprocess(
|
||||
[provider.get_runtime(), "image", "ls"],
|
||||
)
|
||||
provider.is_runtime_available()
|
||||
|
||||
def test_install_raise_if_image_cant_be_installed(
|
||||
self, mocker: MockerFixture, provider: Container, fp: FakeProcess
|
||||
) -> None:
|
||||
"""When an image installation fails, an exception should be raised"""
|
||||
|
||||
fp.register_subprocess(
|
||||
[provider.get_runtime(), "image", "ls"],
|
||||
)
|
||||
|
||||
# First check should return nothing.
|
||||
fp.register_subprocess(
|
||||
[
|
||||
provider.get_runtime(),
|
||||
"image",
|
||||
"list",
|
||||
"--format",
|
||||
"{{.ID}}",
|
||||
"dangerzone.rocks/dangerzone",
|
||||
],
|
||||
occurrences=2,
|
||||
)
|
||||
|
||||
# Make podman load fail
|
||||
mocker.patch("gzip.open", mocker.mock_open(read_data=""))
|
||||
|
||||
fp.register_subprocess(
|
||||
[provider.get_runtime(), "load"],
|
||||
returncode=-1,
|
||||
)
|
||||
|
||||
with pytest.raises(ImageInstallationException):
|
||||
provider.install()
|
||||
|
||||
def test_install_raises_if_still_not_installed(
|
||||
self, mocker: MockerFixture, provider: Container, fp: FakeProcess
|
||||
) -> None:
|
||||
"""When an image keep being not installed, it should return False"""
|
||||
|
||||
fp.register_subprocess(
|
||||
[provider.get_runtime(), "image", "ls"],
|
||||
)
|
||||
|
||||
# First check should return nothing.
|
||||
fp.register_subprocess(
|
||||
[
|
||||
provider.get_runtime(),
|
||||
"image",
|
||||
"list",
|
||||
"--format",
|
||||
"{{.ID}}",
|
||||
"dangerzone.rocks/dangerzone",
|
||||
],
|
||||
occurrences=2,
|
||||
)
|
||||
|
||||
# Patch gzip.open and podman load so that it works
|
||||
mocker.patch("gzip.open", mocker.mock_open(read_data=""))
|
||||
fp.register_subprocess(
|
||||
[provider.get_runtime(), "load"],
|
||||
)
|
||||
with pytest.raises(ImageNotPresentException):
|
||||
provider.install()
|
||||
|
||||
|
||||
class TestContainerTermination(IsolationProviderTermination):
|
||||
|
|
|
@ -335,6 +335,7 @@ class TestCliConversion(TestCliBasic):
|
|||
|
||||
class TestExtraFormats(TestCli):
|
||||
@for_each_external_doc("*hwp*")
|
||||
@pytest.mark.flaky(reruns=2)
|
||||
def test_hancom_office(self, doc: str) -> None:
|
||||
if is_qubes_native_conversion():
|
||||
pytest.skip("HWP / HWPX formats are not supported on this platform")
|
||||
|
|
BIN
tests/test_docs/sample-odt-mp4.odt
Normal file
BIN
tests/test_docs/sample-odt-mp4.odt
Normal file
Binary file not shown.
Loading…
Reference in a new issue