Welcome to mirror list, hosted at ThFree Co, Russian Federation.

KeyringAttribute.py « OAuth2 « cura - github.com/Ultimaker/Cura.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 58b45a67ef8600ad045dc891c6358e9321310f26 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# Copyright (c) 2021 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Type, TYPE_CHECKING, Optional, List

from io import BlockingIOError
import keyring
from keyring.backend import KeyringBackend
from keyring.errors import NoKeyringError, PasswordSetError, KeyringLocked, KeyringError

from UM.Logger import Logger

if TYPE_CHECKING:
    from cura.OAuth2.Models import BaseModel

# Need to do some extra workarounds on windows:
import sys
from UM.Platform import Platform
if Platform.isWindows():
    if hasattr(sys, "frozen"):
        import win32timezone
    from keyring.backends.Windows import WinVaultKeyring
    keyring.set_keyring(WinVaultKeyring())
if Platform.isOSX():
    from keyring.backends.macOS import Keyring
    keyring.set_keyring(Keyring())
if Platform.isLinux():
    # We do not support the keyring on Linux, so make sure no Keyring backend is loaded, even if there is a system one.
    from keyring.backends.fail import Keyring as NoKeyringBackend
    keyring.set_keyring(NoKeyringBackend())

# Even if errors happen, we don't want this stored locally:
DONT_EVER_STORE_LOCALLY: List[str] = ["refresh_token"]


class KeyringAttribute:
    """
    Descriptor for attributes that need to be stored in the keyring. With Fallback behaviour to the preference cfg file
    """
    def __get__(self, instance: "BaseModel", owner: type) -> Optional[str]:
        if self._store_secure:  # type: ignore
            try:
                value = keyring.get_password("cura", self._keyring_name)
                return value if value != "" else None
            except NoKeyringError:
                self._store_secure = False
                Logger.logException("w", "No keyring backend present")
                return getattr(instance, self._name)
            except (KeyringLocked, BlockingIOError):
                self._store_secure = False
                Logger.log("i", "Access to the keyring was denied.")
                return getattr(instance, self._name)
            except UnicodeDecodeError:
                self._store_secure = False
                Logger.log("w", "The password retrieved from the keyring cannot be used because it contains characters that cannot be decoded.")
                return getattr(instance, self._name)
            except KeyringError:
                self._store_secure = False
                Logger.logException("w", "Unknown keyring error.")
                return getattr(instance, self._name)
        else:
            return getattr(instance, self._name)

    def __set__(self, instance: "BaseModel", value: Optional[str]):
        if self._store_secure:
            setattr(instance, self._name, None)
            if value is not None:
                try:
                    keyring.set_password("cura", self._keyring_name, value)
                except (PasswordSetError, KeyringLocked):
                    self._store_secure = False
                    if self._name not in DONT_EVER_STORE_LOCALLY:
                        setattr(instance, self._name, value)
                    Logger.logException("w", "Keyring access denied")
                except NoKeyringError:
                    self._store_secure = False
                    if self._name not in DONT_EVER_STORE_LOCALLY:
                        setattr(instance, self._name, value)
                    Logger.logException("w", "No keyring backend present")
                except BaseException as e:
                    # A BaseException can occur in Windows when the keyring attempts to write a token longer than 1024
                    # characters in the Windows Credentials Manager.
                    self._store_secure = False
                    if self._name not in DONT_EVER_STORE_LOCALLY:
                        setattr(instance, self._name, value)
                    Logger.log("w", "Keyring failed: {}".format(e))
        else:
            setattr(instance, self._name, value)

    def __set_name__(self, owner: type, name: str):
        self._name = "_{}".format(name)
        self._keyring_name = name
        self._store_secure = False
        try:
            self._store_secure = KeyringBackend.viable
        except NoKeyringError:
            Logger.logException("w", "Could not use keyring")
        setattr(owner, self._name, None)