1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
|
"""
Module for all linux keyring backends.
The public interface and the functionality that's common to all supported
VPN connection backends is defined in this module.
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
import logging
import keyring
from proton.keyring._base import Keyring
from proton.keyring.exceptions import KeyringLocked, KeyringError
logger = logging.getLogger(__name__)
class KeyringBackendLinux(Keyring): # pylint: disable=too-few-public-methods
"""Keyring linux backend.
All backend implementations should derive from this class, so methods
can be reused, unless there are backend specific approaches.
"""
KEYRING_SERVICE = "Proton"
def __init__(self, keyring_backend):
super().__init__()
self.__keyring_backend = keyring_backend
# pylint: disable=duplicate-code
def _get_item(self, key):
try:
stored_data = self.__keyring_backend.get_password( # pylint: disable=duplicate-code
self.KEYRING_SERVICE,
key
)
except keyring.errors.KeyringLocked as excp:
logging.info("Keyring locked while getting")
raise KeyringLocked("Keyring is locked") from excp
except keyring.errors.KeyringError as excp:
logging.exception("Keyring error while getting")
raise KeyringError(excp) from excp
# Since we're borrowing the dict interface,
# be consistent and throw a KeyError if it doesn't exist
if stored_data is None:
raise KeyError(key)
try:
return json.loads(stored_data)
except json.JSONDecodeError as excp:
# Delete data (it's invalid anyway)
self._del_item(key)
raise KeyError(key) from excp
def _del_item(self, key):
try:
self.__keyring_backend.delete_password(self.KEYRING_SERVICE, key)
except keyring.errors.PasswordDeleteError as excp:
logging.exception("Unable to delete entry from keyring")
raise KeyError(key) from excp
except keyring.errors.KeyringError as excp:
logging.exception("Keyring error while deleting")
raise KeyringError(excp) from excp
def _set_item(self, key, value):
json_data = json.dumps(value)
try:
self.__keyring_backend.set_password(
self.KEYRING_SERVICE,
key,
json_data
)
except keyring.errors.PasswordSetError as excp:
logging.info("Unable to set value to keyring")
raise KeyError(excp) from excp
except keyring.errors.KeyringError as excp:
logging.exception("Keyring error while setting")
raise KeyringError(excp) from excp
@classmethod
def _is_backend_working(cls, keyring_backend):
"""Check that a backend is working properly.
It can happen so that a backend is installed but it might be
misconfigured. But adding this test, we can asses if the backend
is working correctly or not. If not then another backend should be tried instead.
keyring.errors.InitError will be thrown if the backend system can not be initialized,
indicating that possibly it might be misconfigured.
"""
try:
keyring_backend.get_password(
"ProtonVPN",
"TestingThatBackendIsWorking"
)
return True
except (
keyring.errors.InitError, keyring.errors.KeyringLocked,
keyring.errors.NoKeyringError
):
logger.exception("Keyring %s error", keyring_backend)
return False
|