Compare commits

..

No commits in common. "90c7d9f73e6aded977c054b522ba6baaecf739fd" and "6c56f16633ce1876072afe94d5e5deed27418850" have entirely different histories.

12 changed files with 44 additions and 176 deletions

View File

@ -1,69 +0,0 @@
# Introduction
Thank you for your interest in DZGUI!
This guide goes over development conventions and best practices for contributors.
If you are a developer, you can skip to the end.
# Requesting help
If you encounter a problem with DZGUI, you can submit tickets on the GitHub
(issue tracker)[https://github.com/aclist/dztui/issues] under the
`troubleshooting` tag.
# How can I help the project?
There are several ways to help this project.
1. Report bugs that you find
2. Request features that you would like
3. Contribute features and fixes to the codebase
4. Contribute documentation to the project
Before making any contribution, please read the `CODE_OF_CONDUCT.md` and act
accordingly.
## Submitting a ticket
Navigate to the GitHub (issue tracker)[https://github.com/aclist/dztui/issues].
From there, follow the onscreen prompts. You will be asked questions such as:
- What version are you using?
- What distribution are you using?
- What is the issue that you found?
- How can we reproduce the issue?
You can also attach screenshots, logs, or other data that can help us.
## Requesting a feature
You can also request features via the same issue tracker. It is good practice to
first search for your idea to see if a similar one has already been posted.
If not, open a ticket where you describe your feature and its possible benefits.
Please note that this is a community project, so it takes time for us to develop
features. Putting in a feature request does not mean that it will be implemented,
but we will do our best to support as many cool ideas as possible.
## Contributing code or documentation
If you would like to assist with an issue on the issue tracker or
contribute a new change, please follow the guidelines below.
Fork this repository and check out the code up to the `dzgui7` (development) branch.
The following naming conventions apply for PRs:
- fix/<your-fix> - patch/hotfix branches
- feat/<your-feature> - feature branches
- doc/<your-doc-branch> - documentation branches
- infra/<your-infra-branch> - infrastructure branches
Implement your changes and test them locally. If they work, you may
open a merge request, and then we review your changes. If everything is OK, it
will be merged to `dzgui7`. After sufficient changes are consolidated into a new release,
this branch will be tagged to a certain point in time and a binary release published.
It is recommended to follow
[Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/), as this
integrates well with tooling and helps the project review your code.
Please see DEVELOPERS.md for further details.

View File

@ -24,8 +24,13 @@ Please refer to the documentation for installation and setup instructions:
Geolocation records from [DB-IP](https://db-ip.com) under [CC 4.0 license](https://creativecommons.org/licenses/by/4.0/) Geolocation records from [DB-IP](https://db-ip.com) under [CC 4.0 license](https://creativecommons.org/licenses/by/4.0/)
Executable versions of DZGUI published as release binaries ship with various runtime dependencies and the Python interpreter built in. This tool uses [python-a2s](https://github.com/Yepoleb/python-a2s) and [dayzquery](https://github.com/Yepoleb/dayzquery) as submodules; licenses for these submodules can be found in the LICENSES file
Users wishing to review the licenses to these components can inspect the `LICENSE` file located in the release tarball. of the project root.
Both the geolocation records and submodules listed above are not shipped with the source code, but are retrieved and assembled at runtime.
Finally, executable versions of DZGUI shipped as release binaries are thin wrappers around the Python interpreter, and also retrieve and assemble the above dependencies at runtime on the end-user's
machine, rather than using pre-compiled source code. Users wishing to review these dependencies can inspect the 'pyproject.toml' manifest in the project root.
## Disclaimer ## Disclaimer

View File

@ -4,7 +4,7 @@ import shutil
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from dzgui.const.constants import APP_NAME from dzgui.const.constants import APP_NAME, APP_NAME_LOWER
from dzgui.const.enum import Preferences from dzgui.const.enum import Preferences
from dzgui.config.query import lookup from dzgui.config.query import lookup
from dzgui.config.userprefs import UserPrefs from dzgui.config.userprefs import UserPrefs
@ -21,7 +21,6 @@ from dzgui.strings import boot
# from dzgui.util.map_count import get_map_count # from dzgui.util.map_count import get_map_count
from dzgui.util.deck import is_steam_deck, is_game_mode from dzgui.util.deck import is_steam_deck, is_game_mode
from dzgui.util.localize import set_locale from dzgui.util.localize import set_locale
from dzgui.util.redact import RedactionFilter, REDACTION_PATTERNS
from dzgui.util.strings import init from dzgui.util.strings import init
from dzgui.views.base import App from dzgui.views.base import App
@ -41,19 +40,13 @@ def make_parents(path: "Path") -> None:
def setup_logger(log_path: "Path") -> None: def setup_logger(log_path: "Path") -> None:
# TODO: put in consts?
_format = ( _format = (
"%(asctime)s%(levelname)s%(filename)s::%(funcName)s::%(lineno)s%(message)s" "%(asctime)s%(levelname)s%(filename)s::%(funcName)s::%(lineno)s%(message)s"
) )
fh = logging.FileHandler(log_path) fh = logging.FileHandler(log_path)
formatter = logging.Formatter(_format) formatter = logging.Formatter(_format)
fh.setFormatter(formatter) fh.setFormatter(formatter)
fh.setLevel(logging.DEBUG) fh.setLevel(logging.DEBUG)
_filter = RedactionFilter(patterns=REDACTION_PATTERNS)
fh.addFilter(_filter)
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
logger.addHandler(fh) logger.addHandler(fh)

View File

@ -2,6 +2,7 @@ from dataclasses import dataclass
from typing import Any, Sequence, TYPE_CHECKING from typing import Any, Sequence, TYPE_CHECKING
from dzgui.const.enum import HELP_MENU_ROWS from dzgui.const.enum import HELP_MENU_ROWS
from dzgui.util.redact import redact_log
from dzgui.util.strings import delimiter from dzgui.util.strings import delimiter
import gi import gi
@ -133,7 +134,9 @@ class ModelFactory:
with open(path, "r") as f: with open(path, "r") as f:
lines = [line.split(delimiter) for line in f.read().splitlines()] lines = [line.split(delimiter) for line in f.read().splitlines()]
for record in lines: for record in lines:
store.append(record) # NOTE: strips PII and API keys
clean = redact_log(record)
store.append(clean)
return store return store
def new_model_from_class(self, cls: type) -> FastInsertListStore: def new_model_from_class(self, cls: type) -> FastInsertListStore:

View File

@ -1,4 +1,3 @@
import logging
import os import os
import platform import platform
@ -10,9 +9,7 @@ from dzgui.const.constants import APP_NAME
from dzgui.const.enum import Preferences from dzgui.const.enum import Preferences
from dzgui.config.query import lookup from dzgui.config.query import lookup
from dzgui.init.prefix import get_version from dzgui.init.prefix import get_version
from dzgui.util.redact import redact_home from dzgui.util.redact import redact
logger = logging.getLogger(APP_NAME)
def get_cpu_model() -> str: def get_cpu_model() -> str:
@ -44,16 +41,12 @@ def write_diagnostic(config: Path, outfile: Path) -> None:
# TODO: test availability on other distros # TODO: test availability on other distros
date = datetime.now().isoformat() date = datetime.now().isoformat()
try: distro = platform.freedesktop_os_release()["ID_LIKE"]
distro = platform.freedesktop_os_release()["ID_LIKE"]
except Exception as e:
logger.warn(e)
distro = "Unknown"
kernel = os.uname().release kernel = os.uname().release
cpu = get_cpu_model() cpu = get_cpu_model()
version = get_version() version = get_version()
branch = lookup(config, Preferences.BRANCH)
debug = lookup(config, Preferences.DEBUG) debug = lookup(config, Preferences.DEBUG)
install = lookup(config, Preferences.INSTALL) install = lookup(config, Preferences.INSTALL)
default = lookup(config, Preferences.DEFAULT) default = lookup(config, Preferences.DEFAULT)
@ -61,8 +54,8 @@ def write_diagnostic(config: Path, outfile: Path) -> None:
steam_path = Path(default) steam_path = Path(default)
workshop_path = get_local_mod_path(steam_path) workshop_path = get_local_mod_path(steam_path)
steam_redacted = redact_home(default) steam_redacted = redact(default)
workshop_redacted = redact_home(str(workshop_path)) workshop_redacted = redact(str(workshop_path))
mods = get_local_mod_ids(steam_path) mods = get_local_mod_ids(steam_path)
mods_pretty = print_mods(mods) mods_pretty = print_mods(mods)
@ -72,7 +65,7 @@ def write_diagnostic(config: Path, outfile: Path) -> None:
# FIXME: extraneous newlines in lists of mods # FIXME: extraneous newlines in lists of mods
template = f"""\ template = f"""\
{APP_NAME} version {version} {APP_NAME} version {version} ({branch})
Date: {date} Date: {date}
=============================== ===============================
Distribution: {distro} Distribution: {distro}

View File

@ -1,29 +1,20 @@
import logging
import re import re
from typing import Literal
api_filter = r"(.*&key=)([^&]*)(.*)" def redact(text: str) -> str:
home_filter = r"(/home/)([^\s'\/]*)(.*)" r = r"(/home/)([^/])*"
REDACTED = r"\1REDACTED\3" cleaned = re.sub(r, r"/home/REDACTED", text)
REDACTION_PATTERNS = [api_filter, home_filter]
def redact_home(text: str) -> str:
pat = re.compile(home_filter)
cleaned = pat.sub(REDACTED, text)
return cleaned return cleaned
def redact_log(record: list) -> list[str]:
class RedactionFilter(logging.Filter): """
def __init__(self, patterns: list[str] | None = None) -> None: requests library includes Steam API key in URL params
super().__init__() """
self._patterns = [re.compile(pat) for pat in (patterns or [])] clean = []
for item in record:
def filter(self, record: logging.LogRecord) -> Literal[True]: if "&key=" in item:
for pattern in self._patterns: pat = r"(.*&key=)(\S+)(.*)"
try: scrubbed = re.sub(pat, r"\1REDACTED\3", item)
record.msg = pattern.sub(REDACTED, record.msg) clean.append(scrubbed)
except TypeError: else:
exception_text = f"{type(record.msg).__name__}: {record.msg}" clean.append(item)
record.msg = pattern.sub(REDACTED, exception_text) return clean
return True

View File

@ -288,7 +288,6 @@ class Options(Gtk.Box):
def _on_api_change_failed(self, emitter: "Emitter") -> None: def _on_api_change_failed(self, emitter: "Emitter") -> None:
self.old_entry.set_text(self.old_text) self.old_entry.set_text(self.old_text)
# TODO: use popover
dialog = ExceptionDialog(self.controller, errors.api_validation_error) dialog = ExceptionDialog(self.controller, errors.api_validation_error)
dialog.run() dialog.run()

View File

@ -110,7 +110,6 @@ markers = [
"mods: tests mod metadata/link creation", "mods: tests mod metadata/link creation",
"pefile: validate PE files", "pefile: validate PE files",
"post_install: requires a completed installation", "post_install: requires a completed installation",
"redact: log redaction mechanisms",
"slow: long-running tests", "slow: long-running tests",
"webtest: checks remote endpoints" "webtest: checks remote endpoints"
] ]

View File

@ -4,6 +4,7 @@ import os
from dzgui.app_init import copy_bare_configs from dzgui.app_init import copy_bare_configs
from dzgui.config.xdg import get_xdg_paths, parse_filepaths from dzgui.config.xdg import get_xdg_paths, parse_filepaths
from pathlib import Path
CONF_STRING = "DZGUI_CONF\n" CONF_STRING = "DZGUI_CONF\n"
@ -26,7 +27,8 @@ def state_files():
@pytest.fixture @pytest.fixture
def xdg_paths(monkeypatch): def xdg_paths():
paths = []
routes = { routes = {
"XDG_CONFIG_HOME": "", "XDG_CONFIG_HOME": "",
"XDG_STATE_HOME": "", "XDG_STATE_HOME": "",
@ -37,7 +39,7 @@ def xdg_paths(monkeypatch):
tmp = tempfile.TemporaryDirectory(delete=False) tmp = tempfile.TemporaryDirectory(delete=False)
routes[route] = tmp.name routes[route] = tmp.name
for k, v in routes.items(): for k, v in routes.items():
monkeypatch.setenv(k, v) os.environ[k] = v
env = get_xdg_paths() env = get_xdg_paths()
return parse_filepaths(env) return parse_filepaths(env)

View File

@ -95,3 +95,8 @@ def test_missing_values(unset_values):
j = convert.rc2json(unset_values) j = convert.rc2json(unset_values)
j = json.loads(j) j = json.loads(j)
assert j.keys() == config_boilerplate.keys() assert j.keys() == config_boilerplate.keys()
# TODO: test that when a config file is created from scratch, it contains all values

View File

@ -1,54 +0,0 @@
import logging
import pytest
from dzgui.util.redact import RedactionFilter, REDACTION_PATTERNS
class RecordsListHandler(logging.Handler):
def __init__(self) -> None:
super().__init__()
self.records_list = []
def emit(self, record: logging.LogRecord) -> None:
self.records_list.append(record)
def pop(self) -> None:
return self.records_list[-1].msg
@pytest.mark.redact
@pytest.mark.parametrize(
"log_error, expect",
[
("/home/SENSITIVE_USERNAME/subdir", "/home/REDACTED/subdir"),
(
"https://url.com/?api&key=SENSITIVE_KEY&results=10",
"https://url.com/?api&key=REDACTED&results=10",
),
(
"https://url.com/?api&key=SENSITIVE_KEY",
"https://url.com/?api&key=REDACTED",
),
(
"Error in directory: '/home/SENSITIVE_USERNAME/'",
"Error in directory: '/home/REDACTED/'",
),
(
"Error in directory: '/home/SENSITIVE_USERNAME'",
"Error in directory: '/home/REDACTED'",
),
],
)
def test_log_redaction(log_error: str, expect: str) -> None:
logger = logging.getLogger("TEST")
handler = RecordsListHandler()
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)
_filter = RedactionFilter(patterns=REDACTION_PATTERNS)
logger.addFilter(_filter)
logger.critical(log_error)
redacted = handler.pop()
assert expect == redacted

View File

@ -20,6 +20,7 @@ def test_steam(config):
key = config["steam_api"] key = config["steam_api"]
assert probe.test_steam_api(key) assert probe.test_steam_api(key)
def test_bm(config): def test_bm(config):
key = config["bm_api"] key = config["bm_api"]
assert probe.test_bm_api(key) assert probe.test_bm_api(key)