KeyringAttribute.py 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  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. import keyring
  5. from keyring.backend import KeyringBackend
  6. from keyring.errors import NoKeyringError, PasswordSetError
  7. from UM.Logger import Logger
  8. if TYPE_CHECKING:
  9. from cura.OAuth2.Models import BaseModel
  10. # Need to do some extra workarounds on windows:
  11. import sys
  12. from UM.Platform import Platform
  13. if Platform.isWindows() and hasattr(sys, "frozen"):
  14. import win32timezone
  15. from keyring.backends.Windows import WinVaultKeyring
  16. keyring.set_keyring(WinVaultKeyring())
  17. # Even if errors happen, we don't want this stored locally:
  18. DONT_EVER_STORE_LOCALLY: List[str] = ["refresh_token"]
  19. class KeyringAttribute:
  20. """
  21. Descriptor for attributes that need to be stored in the keyring. With Fallback behaviour to the preference cfg file
  22. """
  23. def __get__(self, instance: "BaseModel", owner: type) -> Optional[str]:
  24. if self._store_secure: # type: ignore
  25. try:
  26. value = keyring.get_password("cura", self._keyring_name)
  27. return value if value != "" else None
  28. except NoKeyringError:
  29. self._store_secure = False
  30. Logger.logException("w", "No keyring backend present")
  31. return getattr(instance, self._name)
  32. else:
  33. return getattr(instance, self._name)
  34. def __set__(self, instance: "BaseModel", value: Optional[str]):
  35. if self._store_secure:
  36. setattr(instance, self._name, None)
  37. try:
  38. keyring.set_password("cura", self._keyring_name, value if value is not None else "")
  39. except PasswordSetError:
  40. self._store_secure = False
  41. if self._name not in DONT_EVER_STORE_LOCALLY:
  42. setattr(instance, self._name, value)
  43. Logger.logException("w", "Keyring access denied")
  44. except NoKeyringError:
  45. self._store_secure = False
  46. if self._name not in DONT_EVER_STORE_LOCALLY:
  47. setattr(instance, self._name, value)
  48. Logger.logException("w", "No keyring backend present")
  49. except BaseException as e:
  50. # A BaseException can occur in Windows when the keyring attempts to write a token longer than 1024
  51. # characters in the Windows Credentials Manager.
  52. self._store_secure = False
  53. if self._name not in DONT_EVER_STORE_LOCALLY:
  54. setattr(instance, self._name, value)
  55. Logger.log("w", "Keyring failed: {}".format(e))
  56. else:
  57. setattr(instance, self._name, value)
  58. def __set_name__(self, owner: type, name: str):
  59. self._name = "_{}".format(name)
  60. self._keyring_name = name
  61. self._store_secure = False
  62. try:
  63. self._store_secure = KeyringBackend.viable
  64. except NoKeyringError:
  65. Logger.logException("w", "Could not use keyring")
  66. setattr(owner, self._name, None)