mirror of
https://github.com/aclist/dztui.git
synced 2026-08-30 03:37:00 +02:00
Compare commits
16 Commits
6c56f16633
...
90c7d9f73e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90c7d9f73e | ||
|
|
5923d5d935 | ||
|
|
9f73c545ef | ||
|
|
041438cb7f | ||
|
|
c6e69f9aa0 | ||
|
|
8f1e4ff9d0 | ||
|
|
92792b0b4f | ||
|
|
a7cfd4c346 | ||
|
|
fa5f638697 | ||
|
|
78151b3bb3 | ||
|
|
2418d8be68 | ||
|
|
9fa7172056 | ||
|
|
d75f87768e | ||
|
|
5bb4271d36 | ||
|
|
6c86bff869 | ||
|
|
92ba7c2267 |
69
CONTRIBUTING.md
Normal file
69
CONTRIBUTING.md
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# 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.
|
||||||
@ -24,13 +24,8 @@ 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/)
|
||||||
|
|
||||||
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
|
Executable versions of DZGUI published as release binaries ship with various runtime dependencies and the Python interpreter built in.
|
||||||
of the project root.
|
Users wishing to review the licenses to these components can inspect the `LICENSE` file located in the release tarball.
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import shutil
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
|
||||||
from dzgui.const.constants import APP_NAME, APP_NAME_LOWER
|
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.config.userprefs import UserPrefs
|
from dzgui.config.userprefs import UserPrefs
|
||||||
@ -21,6 +21,7 @@ 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
|
||||||
@ -40,13 +41,19 @@ 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)
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,6 @@ 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
|
||||||
@ -134,9 +133,7 @@ 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:
|
||||||
# NOTE: strips PII and API keys
|
store.append(record)
|
||||||
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:
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
@ -9,7 +10,9 @@ 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
|
from dzgui.util.redact import redact_home
|
||||||
|
|
||||||
|
logger = logging.getLogger(APP_NAME)
|
||||||
|
|
||||||
|
|
||||||
def get_cpu_model() -> str:
|
def get_cpu_model() -> str:
|
||||||
@ -41,12 +44,16 @@ 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()
|
||||||
|
|
||||||
distro = platform.freedesktop_os_release()["ID_LIKE"]
|
try:
|
||||||
|
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)
|
||||||
@ -54,8 +61,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(default)
|
steam_redacted = redact_home(default)
|
||||||
workshop_redacted = redact(str(workshop_path))
|
workshop_redacted = redact_home(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)
|
||||||
@ -65,7 +72,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} ({branch})
|
{APP_NAME} version {version}
|
||||||
Date: {date}
|
Date: {date}
|
||||||
===============================
|
===============================
|
||||||
Distribution: {distro}
|
Distribution: {distro}
|
||||||
|
|||||||
@ -1,20 +1,29 @@
|
|||||||
|
import logging
|
||||||
import re
|
import re
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
def redact(text: str) -> str:
|
api_filter = r"(.*&key=)([^&]*)(.*)"
|
||||||
r = r"(/home/)([^/])*"
|
home_filter = r"(/home/)([^\s'\/]*)(.*)"
|
||||||
cleaned = re.sub(r, r"/home/REDACTED", text)
|
REDACTED = r"\1REDACTED\3"
|
||||||
|
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):
|
||||||
requests library includes Steam API key in URL params
|
def __init__(self, patterns: list[str] | None = None) -> None:
|
||||||
"""
|
super().__init__()
|
||||||
clean = []
|
self._patterns = [re.compile(pat) for pat in (patterns or [])]
|
||||||
for item in record:
|
|
||||||
if "&key=" in item:
|
def filter(self, record: logging.LogRecord) -> Literal[True]:
|
||||||
pat = r"(.*&key=)(\S+)(.*)"
|
for pattern in self._patterns:
|
||||||
scrubbed = re.sub(pat, r"\1REDACTED\3", item)
|
try:
|
||||||
clean.append(scrubbed)
|
record.msg = pattern.sub(REDACTED, record.msg)
|
||||||
else:
|
except TypeError:
|
||||||
clean.append(item)
|
exception_text = f"{type(record.msg).__name__}: {record.msg}"
|
||||||
return clean
|
record.msg = pattern.sub(REDACTED, exception_text)
|
||||||
|
return True
|
||||||
|
|||||||
@ -288,6 +288,7 @@ 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()
|
||||||
|
|
||||||
|
|||||||
@ -110,6 +110,7 @@ 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"
|
||||||
]
|
]
|
||||||
|
|||||||
@ -4,7 +4,6 @@ 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"
|
||||||
@ -27,8 +26,7 @@ def state_files():
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def xdg_paths():
|
def xdg_paths(monkeypatch):
|
||||||
paths = []
|
|
||||||
routes = {
|
routes = {
|
||||||
"XDG_CONFIG_HOME": "",
|
"XDG_CONFIG_HOME": "",
|
||||||
"XDG_STATE_HOME": "",
|
"XDG_STATE_HOME": "",
|
||||||
@ -39,7 +37,7 @@ def xdg_paths():
|
|||||||
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():
|
||||||
os.environ[k] = v
|
monkeypatch.setenv(k, v)
|
||||||
env = get_xdg_paths()
|
env = get_xdg_paths()
|
||||||
return parse_filepaths(env)
|
return parse_filepaths(env)
|
||||||
|
|
||||||
|
|||||||
@ -95,8 +95,3 @@ 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
|
|
||||||
|
|||||||
54
tests/test_log_redaction.py
Normal file
54
tests/test_log_redaction.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
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
|
||||||
@ -20,7 +20,6 @@ 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)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user