KeyringAttribute.py 4.2 KB

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