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"

Reply via email to