KeyringAttribute.py 4.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. # Copyright (c) 2021 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. from typing import Type, TYPE_CHECKING, Optional, List
  4. from io import BlockingIOError
  5. import keyring
  6. from keyring.backend import KeyringBackend
  7. from keyring.errors import NoKeyringError, PasswordSetError, KeyringLocked, KeyringError
  8. from UM.Logger import Logger
  9. if TYPE_CHECKING:
  10. from cura.OAuth2.Models import BaseModel
  11. # Need to do some extra workarounds on windows:
  12. import sys
  13. from UM.Platform import Platform
  14. if Platform.isWindows():
  15. if hasattr(sys, "frozen"):
  16. import win32timezone
  17. from keyring.backends.Windows import WinVaultKeyring
  18. keyring.set_keyring(WinVaultKeyring())
  19. if Platform.isOSX():
  20. from keyring.backends.macOS import Keyring
  21. keyring.set_keyring(Keyring())
  22. if Platform.isLinux():
  23. # We do not support the keyring on Linux, so make sure no Keyring backend is loaded, even if there is a system one.
  24. from keyring.backends.fail import Keyring as NoKeyringBackend
  25. keyring.set_keyring(NoKeyringBackend())
  26. # Even if errors happen, we don't want this stored locally:
  27. DONT_EVER_STORE_LOCALLY: List[str] = ["refresh_token"]
  28. class KeyringAttribute:
  29. """
  30. Descriptor for attributes that need to be stored in the keyring. With Fallback behaviour to the preference cfg file
  31. """
  32. def __get__(self, instance: "BaseModel", owner: type) -> Optional[str]:
  33. if self._store_secure: # type: ignore
  34. try:
  35. value = keyring.get_password("cura", self._keyring_name)
  36. return value if value != "" else None
  37. except NoKeyringError:
  38. self._store_secure = False
  39. Logger.logException("w", "No keyring backend present")
  40. return getattr(instance, self._name)
  41. except (KeyringLocked, BlockingIOError):
  42. self._store_secure = False
  43. Logger.log("i", "Access to the keyring was denied.")
  44. return getattr(instance, self._name)
  45. except UnicodeDecodeError:
  46. self._store_secure = False
  47. Logger.log("w", "The password retrieved from the keyring cannot be used because it contains characters that cannot be decoded.")
  48. return getattr(instance, self._name)
  49. except KeyringError:
  50. self._store_secure = False
  51. Logger.logException("w", "Unknown keyring error.")
  52. return getattr(instance, self._name)
  53. else:
  54. return getattr(instance, self._name)
  55. def __set__(self, instance: "BaseModel", value: Optional[str]):
  56. if self._store_secure:
  57. setattr(instance, self._name, None)
  58. if value is not None:
  59. try:
  60. keyring.set_password("cura", self._keyring_name, value)
  61. except (PasswordSetError, KeyringLocked):
  62. self._store_secure = False
  63. if self._name not in DONT_EVER_STORE_LOCALLY:
  64. setattr(instance, self._name, value)
  65. Logger.logException("w", "Keyring access denied")
  66. except NoKeyringError:
  67. self._store_secure = False
  68. if self._name not in DONT_EVER_STORE_LOCALLY:
  69. setattr(instance, self._name, value)
  70. Logger.logException("w", "No keyring backend present")
  71. except BaseException as e:
  72. # A BaseException can occur in Windows when the keyring attempts to write a token longer than 1024
  73. # characters in the Windows Credentials Manager.
  74. self._store_secure = False
  75. if self._name not in DONT_EVER_STORE_LOCALLY:
  76. setattr(instance, self._name, value)
  77. Logger.log("w", "Keyring failed: {}".format(e))
  78. else:
  79. setattr(instance, self._name, value)
  80. def __set_name__(self, owner: type, name: str):
  81. self._name = "_{}".format(name)
  82. self._keyring_name = name
  83. self._store_secure = False
  84. try:
  85. self._store_secure = KeyringBackend.viable
  86. except NoKeyringError:
  87. Logger.logException("w", "Could not use keyring")
  88. setattr(owner, self._name, None)