KeyringAttribute.py 4.0 KB

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