KeyringAttribute.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  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, KeyringLocked
  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. if Platform.isOSX() and hasattr(sys, "frozen"):
  18. from keyring.backends.macOS import Keyring
  19. keyring.set_keyring(Keyring())
  20. # Even if errors happen, we don't want this stored locally:
  21. DONT_EVER_STORE_LOCALLY: List[str] = ["refresh_token"]
  22. class KeyringAttribute:
  23. """
  24. Descriptor for attributes that need to be stored in the keyring. With Fallback behaviour to the preference cfg file
  25. """
  26. def __get__(self, instance: "BaseModel", owner: type) -> Optional[str]:
  27. if self._store_secure: # type: ignore
  28. try:
  29. value = keyring.get_password("cura", self._keyring_name)
  30. return value if value != "" else None
  31. except NoKeyringError:
  32. self._store_secure = False
  33. Logger.logException("w", "No keyring backend present")
  34. return getattr(instance, self._name)
  35. except KeyringLocked:
  36. self._store_secure = False
  37. Logger.log("i", "Access to the keyring was denied.")
  38. return getattr(instance, self._name)
  39. except UnicodeDecodeError:
  40. self._store_secure = False
  41. Logger.log("w", "The password retrieved from the keyring cannot be used because it contains characters that cannot be decoded.")
  42. return getattr(instance, self._name)
  43. else:
  44. return getattr(instance, self._name)
  45. def __set__(self, instance: "BaseModel", value: Optional[str]):
  46. if self._store_secure:
  47. setattr(instance, self._name, None)
  48. if value is not None:
  49. try:
  50. keyring.set_password("cura", self._keyring_name, value)
  51. except (PasswordSetError, KeyringLocked):
  52. self._store_secure = False
  53. if self._name not in DONT_EVER_STORE_LOCALLY:
  54. setattr(instance, self._name, value)
  55. Logger.logException("w", "Keyring access denied")
  56. except NoKeyringError:
  57. self._store_secure = False
  58. if self._name not in DONT_EVER_STORE_LOCALLY:
  59. setattr(instance, self._name, value)
  60. Logger.logException("w", "No keyring backend present")
  61. except BaseException as e:
  62. # A BaseException can occur in Windows when the keyring attempts to write a token longer than 1024
  63. # characters in the Windows Credentials Manager.
  64. self._store_secure = False
  65. if self._name not in DONT_EVER_STORE_LOCALLY:
  66. setattr(instance, self._name, value)
  67. Logger.log("w", "Keyring failed: {}".format(e))
  68. else:
  69. setattr(instance, self._name, value)
  70. def __set_name__(self, owner: type, name: str):
  71. self._name = "_{}".format(name)
  72. self._keyring_name = name
  73. self._store_secure = False
  74. try:
  75. self._store_secure = KeyringBackend.viable
  76. except NoKeyringError:
  77. Logger.logException("w", "Could not use keyring")
  78. setattr(owner, self._name, None)