Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-proton-vpn-api-core for openSUSE:Factory checked in at 2024-05-22 21:33:12 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-proton-vpn-api-core (Old) and /work/SRC/openSUSE:Factory/.python-proton-vpn-api-core.new.1880 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-proton-vpn-api-core" Wed May 22 21:33:12 2024 rev:4 rq:1175848 version:0.24.4 Changes: -------- --- /work/SRC/openSUSE:Factory/python-proton-vpn-api-core/python-proton-vpn-api-core.changes 2024-05-08 11:42:33.274945909 +0200 +++ /work/SRC/openSUSE:Factory/.python-proton-vpn-api-core.new.1880/python-proton-vpn-api-core.changes 2024-05-22 21:33:37.436496114 +0200 @@ -1,0 +2,11 @@ +Wed May 22 11:28:37 UTC 2024 - Alexandre Vicenzi <alexandre.vice...@suse.com> + +- Update to 0.24.4 + * Filter OSError not just FileNotFound error in sentry. + * Set the sentry user id based on a hash of /etc/machine-id + * Fix deprecation warning when calculatin WireGuard certificate validity period. + * Fix error saving cache file when parent directory does not exist + * Only initialize sentry on first enable. + * Forward SSL_CERT_FILE environment variable to sentry. + +------------------------------------------------------------------- Old: ---- v0.23.1.tar.gz New: ---- v0.24.4.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-proton-vpn-api-core.spec ++++++ --- /var/tmp/diff_new_pack.9e7YbH/_old 2024-05-22 21:33:39.024554198 +0200 +++ /var/tmp/diff_new_pack.9e7YbH/_new 2024-05-22 21:33:39.024554198 +0200 @@ -18,20 +18,21 @@ %{?sle15_python_module_pythons} Name: python-proton-vpn-api-core -Version: 0.23.1 +Version: 0.24.4 Release: 0 Summary: Proton VPN API library License: GPL-3.0-or-later Group: Development/Languages/Python URL: https://github.com/ProtonVPN/python-proton-vpn-api-core Source: https://github.com/ProtonVPN/python-proton-vpn-api-core/archive/refs/tags/v%{version}.tar.gz +BuildRequires: %{python_module PyNaCl} +BuildRequires: %{python_module cryptography} BuildRequires: %{python_module distro} BuildRequires: %{python_module pip} BuildRequires: %{python_module proton-core} BuildRequires: %{python_module proton-vpn-connection} BuildRequires: %{python_module proton-vpn-killswitch} BuildRequires: %{python_module proton-vpn-logger} -BuildRequires: %{python_module proton-vpn-session} BuildRequires: %{python_module pytest-asyncio} BuildRequires: %{python_module pytest-cov} BuildRequires: %{python_module pytest} @@ -40,12 +41,13 @@ BuildRequires: fdupes BuildRequires: pkgconfig BuildRequires: python-rpm-macros +Requires: python-PyNaCl +Requires: python-cryptography Requires: python-distro Requires: python-proton-core Requires: python-proton-vpn-connection Requires: python-proton-vpn-killswitch Requires: python-proton-vpn-logger -Requires: python-proton-vpn-session Requires: python-sentry-sdk BuildArch: noarch %python_subpackages ++++++ v0.23.1.tar.gz -> v0.24.4.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-vpn-api-core-0.23.1/debian/changelog new/python-proton-vpn-api-core-0.24.4/debian/changelog --- old/python-proton-vpn-api-core-0.23.1/debian/changelog 2024-04-23 17:03:26.000000000 +0200 +++ new/python-proton-vpn-api-core-0.24.4/debian/changelog 2024-05-07 10:00:23.000000000 +0200 @@ -1,3 +1,34 @@ +proton-vpn-api-core (0.24.4) unstable; urgency=medium + + * Filter OSError not just FileNotFound error in sentry. + + -- Luke Titley <luke.tit...@proton.ch> Tue, 07 May 2024 11:00:00 +0100 + +proton-vpn-api-core (0.24.3) unstable; urgency=medium + + * Set the sentry user id based on a hash of /etc/machine-id + + -- Luke Titley <luke.tit...@proton.ch> Fri, 03 May 2024 11:00:00 +0100 + +proton-vpn-api-core (0.24.2) unstable; urgency=medium + + * Fix deprecation warning when calculatin WireGuard certificate validity period. + + -- Alexandru Cheltuitor <alexandru.cheltui...@proton.ch> Thu, 02 May 2024 16:00:00 +0100 + +proton-vpn-api-core (0.24.1) unstable; urgency=medium + + * Fix error saving cache file when parent directory does not exist + + -- Josep Llaneras <josep.llane...@proton.ch> Tue, 30 Apr 2024 17:58:40 +0200 + +proton-vpn-api-core (0.24.0) unstable; urgency=medium + + * Only initialize sentry on first enable. + * Forward SSL_CERT_FILE environment variable to sentry. + + -- Luke Titley <luke.tit...@proton.ch> Tue, 30 Apr 2024 17:00:00 +0100 + proton-vpn-api-core (0.23.1) unstable; urgency=medium * Added missing pip dependencies. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-vpn-api-core-0.23.1/debian/control new/python-proton-vpn-api-core-0.24.4/debian/control --- old/python-proton-vpn-api-core-0.23.1/debian/control 2024-04-23 17:03:26.000000000 +0200 +++ new/python-proton-vpn-api-core-0.24.4/debian/control 2024-05-07 10:00:23.000000000 +0200 @@ -14,6 +14,6 @@ python3-proton-vpn-connection, python3-distro, python3-proton-vpn-logger, python3-proton-vpn-killswitch, python3-sentry-sdk, python3-proton-core, python3-nacl -Breaks: proton-vpn-gtk-app (<< 4.3.1~rc1) +Breaks: proton-vpn-gtk-app (<< 4.3.2~rc2) Replaces: python3-proton-vpn-session Description: Python3 ProtonVPN Core API diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-vpn-api-core-0.23.1/proton/vpn/core/api.py new/python-proton-vpn-api-core-0.24.4/proton/vpn/core/api.py --- old/python-proton-vpn-api-core-0.23.1/proton/vpn/core/api.py 2024-04-23 17:03:26.000000000 +0200 +++ new/python-proton-vpn-api-core-0.24.4/proton/vpn/core/api.py 2024-05-07 10:00:23.000000000 +0200 @@ -30,7 +30,7 @@ from proton.vpn.core.session import ClientConfig, LoginResult, BugReportForm from proton.vpn.core.session.account import VPNAccount -from proton.vpn.core.usage import UsageReporting, usage_reporting +from proton.vpn.core.usage import UsageReporting class ProtonVPNAPI: # pylint: disable=too-many-public-methods @@ -41,6 +41,8 @@ ) self._settings_persistence = SettingsPersistence() self._vpn_connector = None + self._usage_reporting = UsageReporting( + client_type_metadata=client_type_metadata) async def get_vpn_connector(self): """Returns an object that wraps around the raw VPN connection object. @@ -69,7 +71,7 @@ settings = await loop.run_in_executor( None, self._settings_persistence.get, user_tier ) - self.usage_reporting.enabled = settings.anonymous_crash_reports + self._usage_reporting.enabled = settings.anonymous_crash_reports return settings async def save_settings(self, settings: Settings): @@ -82,7 +84,7 @@ loop = asyncio.get_running_loop() await loop.run_in_executor(None, self._settings_persistence.save, settings) await self._vpn_connector.apply_settings(settings) - self.usage_reporting.enabled = settings.anonymous_crash_reports + self._usage_reporting.enabled = settings.anonymous_crash_reports async def login(self, username: str, password: str) -> LoginResult: """ @@ -209,7 +211,7 @@ @property def usage_reporting(self) -> UsageReporting: """Returns the usage reporting instance to send anonymous crash reports.""" - return usage_reporting + return self._usage_reporting def _get_features(self): settings = self._settings_persistence.get(user_tier=0, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-vpn-api-core-0.23.1/proton/vpn/core/cache_handler.py new/python-proton-vpn-api-core-0.24.4/proton/vpn/core/cache_handler.py --- old/python-proton-vpn-api-core-0.23.1/proton/vpn/core/cache_handler.py 2024-04-23 17:03:26.000000000 +0200 +++ new/python-proton-vpn-api-core-0.24.4/proton/vpn/core/cache_handler.py 2024-05-07 10:00:23.000000000 +0200 @@ -22,21 +22,28 @@ import json import os +from pathlib import Path class CacheHandler: """Used to save, load, and remove cache files.""" def __init__(self, filepath: str): - self._fp = filepath + self._fp = Path(filepath) + + @property + def exists(self): + """True if the cache file exists and False otherwise.""" + return self._fp.is_file() def save(self, newdata: dict): """Save data to cache file.""" + self._fp.parent.mkdir(parents=True, exist_ok=True) with open(self._fp, "w") as f: # pylint: disable=W1514, C0103 json.dump(newdata, f, indent=4) # pylint: disable=C0103 def load(self): """Load data from cache file, if it exists.""" - if not os.path.isfile(self._fp): + if not self.exists: return None with open(self._fp, "r") as f: # pylint: disable=W1514, C0103 @@ -44,5 +51,5 @@ def remove(self): """ Remove cache from disk.""" - if os.path.exists(self._fp): + if self.exists: os.remove(self._fp) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-vpn-api-core-0.23.1/proton/vpn/core/session/cache.py new/python-proton-vpn-api-core-0.24.4/proton/vpn/core/session/cache.py --- old/python-proton-vpn-api-core-0.23.1/proton/vpn/core/session/cache.py 2024-04-23 17:03:26.000000000 +0200 +++ new/python-proton-vpn-api-core-0.24.4/proton/vpn/core/session/cache.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,74 +0,0 @@ -""" -Copyright (c) 2023 Proton AG - -This file is part of Proton VPN. - -Proton VPN is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Proton VPN is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with ProtonVPN. If not, see <https://www.gnu.org/licenses/>. -""" - -import json -from pathlib import Path - - -class CacheFile: - """ - Persists/loads a python dictionary to disk. - """ - def __init__(self, file_path: Path): - """ - :param file_path: Path from/to which load/persist the cache data. - """ - self.file_path = file_path - - @property - def exists(self): - """True if the cache file exists and False otherwise.""" - return self.file_path.is_file() - - def save(self, data: dict) -> None: - """Persists the dictionary to the current file path.""" - CacheFile.to_path(data, self.file_path) - - @staticmethod - def to_path(data: dict, file_path: Path) -> None: - """Persists the dictionary to the given path.""" - with open(file_path, "w", encoding="utf-8") as file: - json.dump(obj=data, fp=file) - - def load(self) -> dict: - """ - Loads the dictionary from the current file path. - - :returns: the loaded dictionary. - :raises ValueError: if the file content is invalid. - :raises FileNotFoundError: if the file was not found. - """ - return CacheFile.from_path(self.file_path) - - @staticmethod - def from_path(file_path: Path) -> dict: - """ - Loads a dictionary from a given file path. - - :param: file_path: path to the file to load as dictionary. - :returns: the loaded dictionary. - :raises ValueError: if the file content is invalid. - :raises FileNotFoundError: if the file was not found. - """ - with open(file_path, "r", encoding="utf-8") as file: - return json.load(file) - - def remove(self) -> None: - """Removes the current persistence file, if exists.""" - self.file_path.unlink(missing_ok=True) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-vpn-api-core-0.23.1/proton/vpn/core/session/certificates.py new/python-proton-vpn-api-core-0.24.4/proton/vpn/core/session/certificates.py --- old/python-proton-vpn-api-core-0.23.1/proton/vpn/core/session/certificates.py 2024-04-23 17:03:26.000000000 +0200 +++ new/python-proton-vpn-api-core-0.24.4/proton/vpn/core/session/certificates.py 2024-05-07 10:00:23.000000000 +0200 @@ -278,7 +278,20 @@ """ remaining time the certificate is valid, in seconds. < 0 : certificate is not valid anymore. """ - return self._cert.not_valid_after.timestamp() - datetime.datetime.utcnow().timestamp() + # cryptography >= v42.0.0 added `not_valid_after_utc` and deprecated `not_valid_after`. + if hasattr(self._cert, "not_valid_after_utc"): + not_valid_after_timestamp = self._cert.not_valid_after_utc.timestamp() + else: + # Because `not_valid_after` returns a naive utc + # datetime object (without time zone info), we add it manually + # to then be able to compare it to the current time. + not_valid_after_timestamp = self._cert.not_valid_after.replace( + tzinfo=datetime.timezone.utc + ).timestamp() + + now_timestamp = datetime.datetime.now(datetime.timezone.utc).timestamp() + + return not_valid_after_timestamp - now_timestamp @property def validity_date(self) -> datetime.datetime: # pylint: disable=missing-function-docstring diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-vpn-api-core-0.23.1/proton/vpn/core/session/client_config.py new/python-proton-vpn-api-core-0.24.4/proton/vpn/core/session/client_config.py --- old/python-proton-vpn-api-core-0.23.1/proton/vpn/core/session/client_config.py 2024-04-23 17:03:26.000000000 +0200 +++ new/python-proton-vpn-api-core-0.24.4/proton/vpn/core/session/client_config.py 2024-05-07 10:00:23.000000000 +0200 @@ -25,7 +25,7 @@ from proton.utils.environment import VPNExecutionEnvironment -from proton.vpn.core.session.cache import CacheFile +from proton.vpn.core.cache_handler import CacheHandler from proton.vpn.core.session.exceptions import ClientConfigDecodeError from proton.vpn.core.session.utils import rest_api_request @@ -237,7 +237,7 @@ """ self._session = session self._client_config = None - self._cache_file = CacheFile(self.CACHE_PATH) + self._cache_file = CacheHandler(self.CACHE_PATH) def clear_cache(self): """Discards the cache, if existing.""" @@ -265,6 +265,6 @@ was found then the default client configuration is returned. """ - cache = self._cache_file.load() if self._cache_file.exists else None + cache = self._cache_file.load() self._client_config = ClientConfig.from_dict(cache) if cache else ClientConfig.default() return self._client_config diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-vpn-api-core-0.23.1/proton/vpn/core/session/servers/fetcher.py new/python-proton-vpn-api-core-0.24.4/proton/vpn/core/session/servers/fetcher.py --- old/python-proton-vpn-api-core-0.23.1/proton/vpn/core/session/servers/fetcher.py 2024-04-23 17:03:26.000000000 +0200 +++ new/python-proton-vpn-api-core-0.24.4/proton/vpn/core/session/servers/fetcher.py 2024-05-07 10:00:23.000000000 +0200 @@ -22,7 +22,7 @@ from proton.utils.environment import VPNExecutionEnvironment -from proton.vpn.core.session.cache import CacheFile +from proton.vpn.core.cache_handler import CacheHandler from proton.vpn.core.session.exceptions import ServerListDecodeError from proton.vpn.core.session.servers.types import ServerLoad from proton.vpn.core.session.servers.logicals import ServerList, PersistenceKeys @@ -44,11 +44,11 @@ self, session: "VPNSession", server_list: Optional[ServerList] = None, - cache_file: Optional[CacheFile] = None + cache_file: Optional[CacheHandler] = None ): self._session = session self._server_list = server_list - self._cache_file = cache_file or CacheFile(self.CACHE_PATH) + self._cache_file = cache_file or CacheHandler(self.CACHE_PATH) def clear_cache(self): """Discards the cache, if existing.""" @@ -103,10 +103,10 @@ :raises ServerListDecodeError: if the cache is not found or if the data stored in the cache is not valid. """ - try: - cache = self._cache_file.load() - except FileNotFoundError as error: - raise ServerListDecodeError("Cached server list was not found") from error + cache = self._cache_file.load() + + if not cache: + raise ServerListDecodeError("Cached server list was not found") self._server_list = ServerList.from_dict(cache) return self._server_list diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-vpn-api-core-0.23.1/proton/vpn/core/session/session.py new/python-proton-vpn-api-core-0.24.4/proton/vpn/core/session/session.py --- old/python-proton-vpn-api-core-0.23.1/proton/vpn/core/session/session.py 2024-04-23 17:03:26.000000000 +0200 +++ new/python-proton-vpn-api-core-0.24.4/proton/vpn/core/session/session.py 2024-05-07 10:00:23.000000000 +0200 @@ -89,7 +89,7 @@ self._server_list = self._fetcher.load_server_list_from_cache() self._client_config = self._fetcher.load_client_config_from_cache() except ValueError: - logger.exception("Error deserializing VPN session.") + logger.warning("VPN session could not be deserialized.", exc_info=True) super().__setstate__(data) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-vpn-api-core-0.23.1/proton/vpn/core/usage.py new/python-proton-vpn-api-core-0.24.4/proton/vpn/core/usage.py --- old/python-proton-vpn-api-core-0.23.1/proton/vpn/core/usage.py 2024-04-23 17:03:26.000000000 +0200 +++ new/python-proton-vpn-api-core-0.24.4/proton/vpn/core/usage.py 2024-05-07 10:00:23.000000000 +0200 @@ -16,27 +16,159 @@ You should have received a copy of the GNU General Public License along with ProtonVPN. If not, see <https://www.gnu.org/licenses/>. """ -import sentry_sdk -from sentry_sdk.integrations.dedupe import DedupeIntegration -from sentry_sdk.integrations.stdlib import StdlibIntegration -from sentry_sdk.integrations.modules import ModulesIntegration +import logging +import os +import hashlib +import getpass from proton.vpn.core.session_holder import ClientTypeMetadata, DISTRIBUTION_VERSION, DISTRIBUTION_ID from proton.vpn.core.session.dataclasses import get_desktop_environment DSN = "https://9a5ea555a4dc48dbbb4cfa72bdbd0...@vpn-api.proton.me/core/v4/reports/sentry/25" +SSL_CERT_FILE = "SSL_CERT_FILE" +MACHINE_ID = "/etc/machine-id" +PROTON_VPN = "protonvpn" + +log = logging.getLogger(__name__) class UsageReporting: """Sends anonymous usage reports to Proton.""" - def __init__(self): - self.enabled = False + def __init__(self, client_type_metadata: ClientTypeMetadata): + self._enabled = False self._capture_exception = None + self._client_type_metadata = client_type_metadata + self._user_id = None + self._desktop_environment = get_desktop_environment() + + @property + def enabled(self): + """Returns whether anonymous usage reporting is enabled.""" + return self._enabled + + @enabled.setter + def enabled(self, value: bool): + """ + Sets whether usage reporting is enabled/disabled. + + On unsupported platforms, this may fail, in which case UsageReporting + will be disabled and an exception will be logged. + """ + + try: + self._enabled = value and self._start_sentry() + except Exception: # pylint: disable=broad-except + self._enabled = False + log.exception("Failed to enabled usage reporting") + + def report_error(self, error): + """ + Send an error to sentry if anonymous usage reporting is enabled. - def init(self, client_type_metadata: ClientTypeMetadata, enabled: bool): - """This method should be called before reporting, otherwise reports will be ignored.""" + On unsupported platforms, this may fail, in which case the error will + will not be reported and an exception will be logged. + """ + + try: + if self._enabled: + self._add_scope_metadata() + sanitized_error = self._sanitize_error(error) + self._capture_exception(sanitized_error) + + except Exception: # pylint: disable=broad-except + log.exception("Failed to report error '%s'", str(error)) + + @staticmethod + def _get_user_id(machine_id_filepath=MACHINE_ID, user_name=None): + """ + Returns a unique identifier for the user. + + :param machine_id_filepath: The path to the machine id file, + defaults to /etc/machine-id. This can be overrided for testing. + + :param user_name: The username to include in the hash, if None is + provided, the current user is obtained from the environment. + """ + + if not os.path.exists(machine_id_filepath): + return None + + # We include the username in the hash to avoid collisions on machines + # with multiple users. + if not user_name: + user_name = getpass.getuser() + + # We use the machine id to uniquely identify the machine, we combine it + # with the application name and the username. All three are hashed to + # avoid leaking any personal information. + with open(machine_id_filepath, "r", encoding="utf-8") as machine_id_file: + machine_id = machine_id_file.read().strip() + + combined = hashlib.sha256(machine_id.encode('utf-8')) + combined.update(hashlib.sha256(PROTON_VPN.encode('utf-8')).digest()) + combined.update(hashlib.sha256(user_name.encode('utf-8')).digest()) + + return str(combined.hexdigest()) + + @staticmethod + def _sanitize_error(error_info): + """ + This is where we remove personal information from the error before + sending it to sentry. + """ + + def _sanitize(error): + if isinstance(error, OSError): + # We dont have a lot of files we need to read, so it's safer to + # not include the full file path in the report. + _, filename = os.path.split(error.filename) + error.filename = filename + return error + + if isinstance(error_info, tuple): + return (error_info[0], _sanitize(error_info[1]), error_info[2]) + + return _sanitize(error_info) + + def _add_scope_metadata(self): + """ + Unfortunately, we cannot set the user and tags on the isolation scope + on startup because this is lost by the time we report an error. + So we have to set the user and tags on the current scope just before + reporting an error. + """ + import sentry_sdk # pylint: disable=import-outside-toplevel + # Using configure_scope to set a tag works with older versions of + # sentry (0.12.2) and so works on ubuntu 20. + with sentry_sdk.configure_scope() as scope: + scope.set_tag("distro_name", DISTRIBUTION_ID) + scope.set_tag("distro_version", DISTRIBUTION_VERSION) + scope.set_tag("desktop_environment", self._desktop_environment) + if self._user_id and hasattr(scope, "set_user"): + scope.set_user({"id": self._user_id}) + + def _start_sentry(self): + """Starts the sentry SDK with the appropriate configuration.""" + + if self._capture_exception: + return True + + if not self._client_type_metadata: + raise ValueError("Client type metadata is not set, " + "UsageReporting.init() must be called first.") + + import sentry_sdk # pylint: disable=import-outside-toplevel + + from sentry_sdk.integrations.dedupe import DedupeIntegration # pylint: disable=import-outside-toplevel + from sentry_sdk.integrations.stdlib import StdlibIntegration # pylint: disable=import-outside-toplevel + from sentry_sdk.integrations.modules import ModulesIntegration # pylint: disable=import-outside-toplevel + + # Read from SSL_CERT_FILE from environment variable, this allows us to + # use an http proxy if we want to. + ca_certs = os.environ.get(SSL_CERT_FILE, None) + client_type_metadata = self._client_type_metadata sentry_sdk.init( dsn=DSN, release=f"{client_type_metadata.type}-{client_type_metadata.version}", @@ -46,34 +178,14 @@ DedupeIntegration(), # Yes we want to avoid event duplication StdlibIntegration(), # Yes we want info from the standard lib objects ModulesIntegration() # Yes we want to know what python modules are installed - ] + ], + ca_certs=ca_certs ) - # Using configure_scope to set a tag works with older versions of - # sentry (0.12.2) and so works on ubuntu 20. - with sentry_sdk.configure_scope() as scope: - scope.set_tag("distro_name", DISTRIBUTION_ID) - scope.set_tag("distro_version", DISTRIBUTION_VERSION) - scope.set_tag("desktop_environment", get_desktop_environment()) - - self.enabled = enabled + # Store the user id so we don't have to calculate it again. + self._user_id = self._get_user_id() # Store _capture_exception as a member, so it's easier to test. self._capture_exception = sentry_sdk.capture_exception - def disable(self): - """Disables anonymous usage reporting. """ - self.enabled = False - - def enable(self): - """Enables anonymous usage reporting.""" - self.enabled = True - - def report_error(self, error): - """Reports an error if anonymous usage reporting is enabled.""" - - if self.enabled: - self._capture_exception(error) - - -usage_reporting = UsageReporting() + return True diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-vpn-api-core-0.23.1/rpmbuild/SPECS/package.spec new/python-proton-vpn-api-core-0.24.4/rpmbuild/SPECS/package.spec --- old/python-proton-vpn-api-core-0.23.1/rpmbuild/SPECS/package.spec 2024-04-23 17:03:26.000000000 +0200 +++ new/python-proton-vpn-api-core-0.24.4/rpmbuild/SPECS/package.spec 2024-05-07 10:00:23.000000000 +0200 @@ -1,5 +1,5 @@ %define unmangled_name proton-vpn-api-core -%define version 0.23.1 +%define version 0.24.4 %define release 1 Prefix: %{_prefix} @@ -34,7 +34,7 @@ Requires: python3-sentry-sdk Requires: python3-pynacl -Conflicts: proton-vpn-gtk-app < 4.3.1~rc1 +Conflicts: proton-vpn-gtk-app < 4.3.2~rc2 Obsoletes: python3-proton-vpn-session %{?python_disable_dependency_generator} @@ -59,6 +59,22 @@ %defattr(-,root,root) %changelog +* Fri May 03 2024 Luke Titley <luke.tit...@proton.ch> 0.24.4 +- Filter OSError not just FileNotFound error in sentry. + +* Fri May 03 2024 Luke Titley <luke.tit...@proton.ch> 0.24.3 +- Set the sentry user id based on a hash of /etc/machine-id + +* Thu May 02 2024 Alexandru Cheltuitor <alexandru.cheltui...@proton.ch> 0.24.2 +- Fix deprecation warning when calculatin WireGuard certificate validity period. + +* Tue Apr 30 2024 Josep Llaneras <josep.llane...@proton.ch> 0.24.1 +- Fix error saving cache file when parent directory does not exist + +* Tue Apr 30 2024 Luke Titley <luke.tit...@proton.ch> 0.24.0 +- Only initialize sentry on first enable. +- Forward SSL_CERT_FILE environment variable to sentry. + * Mon Apr 23 2024 Luke Titley <luke.tit...@proton.ch> 0.23.1 - Added missing pip dependencies. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-vpn-api-core-0.23.1/setup.py new/python-proton-vpn-api-core-0.24.4/setup.py --- old/python-proton-vpn-api-core-0.23.1/setup.py 2024-04-23 17:03:26.000000000 +0200 +++ new/python-proton-vpn-api-core-0.24.4/setup.py 2024-05-07 10:00:23.000000000 +0200 @@ -4,7 +4,7 @@ setup( name="proton-vpn-api-core", - version="0.23.1", + version="0.24.4", description="Proton AG VPN Core API", author="Proton AG", author_email="cont...@protonmail.com", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-proton-vpn-api-core-0.23.1/tests/test_usage.py new/python-proton-vpn-api-core-0.24.4/tests/test_usage.py --- old/python-proton-vpn-api-core-0.23.1/tests/test_usage.py 2024-04-23 17:03:26.000000000 +0200 +++ new/python-proton-vpn-api-core-0.24.4/tests/test_usage.py 2024-05-07 10:00:23.000000000 +0200 @@ -16,16 +16,26 @@ You should have received a copy of the GNU General Public License along with ProtonVPN. If not, see <https://www.gnu.org/licenses/>. """ +import os import pytest from types import SimpleNamespace +import tempfile -from proton.vpn.core.usage import usage_reporting +from proton.vpn.core.session_holder import ClientTypeMetadata +from proton.vpn.core.usage import UsageReporting +SECRET_FILE = "secret.txt" +SECRET_PATH = os.path.join("/home/wozniak/5nkfiudfmk/.cache", SECRET_FILE) +MACHINE_ID = "bg77t2rmpjhgt9zim5gkz4t78jfur39f" +SENTRY_USER_ID = "70cf75689cecae78ec588316320d76477c71031f7fd172dd5577ac95934d4499" +USERNAME = "tester" @pytest.mark.parametrize("enabled", [True, False]) def test_usage_report_enabled(enabled): report_error = SimpleNamespace(invoked=False) + usage_reporting = UsageReporting(ClientTypeMetadata("test_usage.py", "none")) + def capture_exception(error): report_error.invoked = True @@ -36,3 +46,33 @@ usage_reporting.report_error(EMPTY_ERROR) assert report_error.invoked == enabled, "UsageReporting enable state does not match the error reporting" + + +@pytest.mark.parametrize("enabled", [True, False]) +def test_sanitize_simple_error(enabled): + + error = FileNotFoundError("File not found") + error.filename = SECRET_PATH + + assert UsageReporting._sanitize_error(error).filename == SECRET_FILE, "Error sanitization failed" + + +@pytest.mark.parametrize("enabled", [True, False]) +def test_sanitize_traceback_error(enabled): + + try: + open(SECRET_PATH, "r") + except FileNotFoundError as exception: + error = (type(exception), exception, exception.__traceback__) + + assert UsageReporting._sanitize_error(error)[1].filename == SECRET_FILE, "Error sanitization failed" + + +def test_userid_calaculation(): + with tempfile.NamedTemporaryFile() as file: + file.write(MACHINE_ID.encode('utf-8')) + file.seek(0) + + assert UsageReporting._get_user_id( + machine_id_filepath=file.name, + user_name=USERNAME) == SENTRY_USER_ID, "Error hashing does not match the expected value"