Merge pull request #1285 from pypeclub/bugfix/openpype_settings_registry

OpenPype settings registry
This commit is contained in:
Milan Kolar 2021-04-09 09:41:46 +02:00 committed by GitHub
commit 8710133c6d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 276 additions and 202 deletions

View file

@ -14,7 +14,10 @@ from zipfile import ZipFile, BadZipFile
from appdirs import user_data_dir
from speedcopy import copyfile
from .user_settings import OpenPypeSettingsRegistry
from .user_settings import (
OpenPypeSecureRegistry,
OpenPypeSettingsRegistry
)
from .tools import get_openpype_path_from_db
@ -239,6 +242,7 @@ class BootstrapRepos:
self._app = "openpype"
self._log = log.getLogger(str(__class__))
self.data_dir = Path(user_data_dir(self._app, self._vendor))
self.secure_registry = OpenPypeSecureRegistry("mongodb")
self.registry = OpenPypeSettingsRegistry()
self.zip_filter = [".pyc", "__pycache__"]
self.openpype_filter = [

View file

@ -13,7 +13,7 @@ from .tools import (
validate_mongo_connection,
get_openpype_path_from_db
)
from .user_settings import OpenPypeSettingsRegistry
from .user_settings import OpenPypeSecureRegistry
from .version import __version__
@ -42,13 +42,13 @@ class InstallDialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super(InstallDialog, self).__init__(parent)
self.registry = OpenPypeSettingsRegistry()
self.secure_registry = OpenPypeSecureRegistry("mongodb")
self.mongo_url = ""
try:
self.mongo_url = (
os.getenv("OPENPYPE_MONGO", "")
or self.registry.get_secure_item("openPypeMongo")
or self.secure_registry.get_item("openPypeMongo")
)
except ValueError:
pass

View file

@ -71,7 +71,7 @@ class InstallThread(QThread):
if not os.getenv("OPENPYPE_MONGO"):
# try to get it from settings registry
try:
self._mongo = bs.registry.get_secure_item(
self._mongo = bs.secure_registry.get_item(
"openPypeMongo")
except ValueError:
self.message.emit(
@ -82,7 +82,7 @@ class InstallThread(QThread):
self._mongo = os.getenv("OPENPYPE_MONGO")
else:
self.message.emit("Saving mongo connection string ...", False)
bs.registry.set_secure_item("openPypeMongo", self._mongo)
bs.secure_registry.set_item("openPypeMongo", self._mongo)
os.environ["OPENPYPE_MONGO"] = self._mongo
@ -169,7 +169,7 @@ class InstallThread(QThread):
f"!!! invalid mongo url {self._mongo}", True)
self.finished.emit(InstallResult(-1))
return
bs.registry.set_secure_item("openPypeMongo", self._mongo)
bs.secure_registry.set_item("openPypeMongo", self._mongo)
os.environ["OPENPYPE_MONGO"] = self._mongo
self.message.emit(f"processing {self._path}", True)

View file

@ -25,8 +25,112 @@ except ImportError:
import platform
import appdirs
import six
import appdirs
_PLACEHOLDER = object()
class OpenPypeSecureRegistry:
"""Store information using keyring.
Registry should be used for private data that should be available only for
user.
All passed registry names will have added prefix `OpenPype/` to easier
identify which data were created by OpenPype.
Args:
name(str): Name of registry used as identifier for data.
"""
def __init__(self, name):
try:
import keyring
except Exception:
raise NotImplementedError(
"Python module `keyring` is not available."
)
# hack for cx_freeze and Windows keyring backend
if platform.system().lower() == "windows":
from keyring.backends import Windows
keyring.set_keyring(Windows.WinVaultKeyring())
# Force "OpenPype" prefix
self._name = "/".join(("OpenPype", name))
def set_item(self, name, value):
# type: (str, str) -> None
"""Set sensitive item into system's keyring.
This uses `Keyring module`_ to save sensitive stuff into system's
keyring.
Args:
name (str): Name of the item.
value (str): Value of the item.
.. _Keyring module:
https://github.com/jaraco/keyring
"""
import keyring
keyring.set_password(self._name, name, value)
@lru_cache(maxsize=32)
def get_item(self, name, default=_PLACEHOLDER):
"""Get value of sensitive item from system's keyring.
See also `Keyring module`_
Args:
name (str): Name of the item.
default (Any): Default value if item is not available.
Returns:
value (str): Value of the item.
Raises:
ValueError: If item doesn't exist and default is not defined.
.. _Keyring module:
https://github.com/jaraco/keyring
"""
import keyring
value = keyring.get_password(self._name, name)
if value:
return value
if default is not _PLACEHOLDER:
return default
# NOTE Should raise `KeyError`
raise ValueError(
"Item {}:{} does not exist in keyring.".format(self._name, name)
)
def delete_item(self, name):
# type: (str) -> None
"""Delete value stored in system's keyring.
See also `Keyring module`_
Args:
name (str): Name of the item to be deleted.
.. _Keyring module:
https://github.com/jaraco/keyring
"""
import keyring
self.get_item.cache_clear()
keyring.delete_password(self._name, name)
@six.add_metaclass(ABCMeta)
@ -46,13 +150,6 @@ class ASettingRegistry():
# type: (str) -> ASettingRegistry
super(ASettingRegistry, self).__init__()
if six.PY3:
import keyring
# hack for cx_freeze and Windows keyring backend
if platform.system() == "Windows":
from keyring.backends import Windows
keyring.set_keyring(Windows.WinVaultKeyring())
self._name = name
self._items = {}
@ -127,78 +224,6 @@ class ASettingRegistry():
del self._items[name]
self._delete_item(name)
def set_secure_item(self, name, value):
# type: (str, str) -> None
"""Set sensitive item into system's keyring.
This uses `Keyring module`_ to save sensitive stuff into system's
keyring.
Args:
name (str): Name of the item.
value (str): Value of the item.
.. _Keyring module:
https://github.com/jaraco/keyring
"""
if six.PY2:
raise NotImplementedError(
"Keyring not available on Python 2 hosts")
import keyring
keyring.set_password(self._name, name, value)
@lru_cache(maxsize=32)
def get_secure_item(self, name):
# type: (str) -> str
"""Get value of sensitive item from system's keyring.
See also `Keyring module`_
Args:
name (str): Name of the item.
Returns:
value (str): Value of the item.
Raises:
ValueError: If item doesn't exist.
.. _Keyring module:
https://github.com/jaraco/keyring
"""
if six.PY2:
raise NotImplementedError(
"Keyring not available on Python 2 hosts")
import keyring
value = keyring.get_password(self._name, name)
if not value:
raise ValueError(
"Item {}:{} does not exist in keyring.".format(
self._name, name))
return value
def delete_secure_item(self, name):
# type: (str) -> None
"""Delete value stored in system's keyring.
See also `Keyring module`_
Args:
name (str): Name of the item to be deleted.
.. _Keyring module:
https://github.com/jaraco/keyring
"""
if six.PY2:
raise NotImplementedError(
"Keyring not available on Python 2 hosts")
import keyring
self.get_secure_item.cache_clear()
keyring.delete_password(self._name, name)
class IniSettingRegistry(ASettingRegistry):
"""Class using :mod:`configparser`.
@ -459,9 +484,10 @@ class OpenPypeSettingsRegistry(JSONSettingRegistry):
"""
def __init__(self):
def __init__(self, name=None):
self.vendor = "pypeclub"
self.product = "openpype"
if not name:
name = "openpype_settings"
path = appdirs.user_data_dir(self.product, self.vendor)
super(OpenPypeSettingsRegistry, self).__init__(
"openpype_settings", path)
super(OpenPypeSettingsRegistry, self).__init__(name, path)

View file

@ -104,7 +104,8 @@ from .plugin_tools import (
from .local_settings import (
IniSettingRegistry,
JSONSettingRegistry,
PypeSettingsRegistry,
OpenPypeSecureRegistry,
OpenPypeSettingsRegistry,
get_local_site_id,
change_openpype_mongo_url
)
@ -217,7 +218,8 @@ __all__ = [
"IniSettingRegistry",
"JSONSettingRegistry",
"PypeSettingsRegistry",
"OpenPypeSecureRegistry",
"OpenPypeSettingsRegistry",
"get_local_site_id",
"change_openpype_mongo_url",

View file

@ -5,6 +5,7 @@ from datetime import datetime
from abc import ABCMeta, abstractmethod
import json
# TODO Use pype igniter logic instead of using duplicated code
# disable lru cache in Python 2
try:
from functools import lru_cache
@ -25,11 +26,115 @@ except ImportError:
import platform
import appdirs
import six
import appdirs
from .import validate_mongo_connection
_PLACEHOLDER = object()
class OpenPypeSecureRegistry:
"""Store information using keyring.
Registry should be used for private data that should be available only for
user.
All passed registry names will have added prefix `OpenPype/` to easier
identify which data were created by OpenPype.
Args:
name(str): Name of registry used as identifier for data.
"""
def __init__(self, name):
try:
import keyring
except Exception:
raise NotImplementedError(
"Python module `keyring` is not available."
)
# hack for cx_freeze and Windows keyring backend
if platform.system().lower() == "windows":
from keyring.backends import Windows
keyring.set_keyring(Windows.WinVaultKeyring())
# Force "OpenPype" prefix
self._name = "/".join(("OpenPype", name))
def set_item(self, name, value):
# type: (str, str) -> None
"""Set sensitive item into system's keyring.
This uses `Keyring module`_ to save sensitive stuff into system's
keyring.
Args:
name (str): Name of the item.
value (str): Value of the item.
.. _Keyring module:
https://github.com/jaraco/keyring
"""
import keyring
keyring.set_password(self._name, name, value)
@lru_cache(maxsize=32)
def get_item(self, name, default=_PLACEHOLDER):
"""Get value of sensitive item from system's keyring.
See also `Keyring module`_
Args:
name (str): Name of the item.
default (Any): Default value if item is not available.
Returns:
value (str): Value of the item.
Raises:
ValueError: If item doesn't exist and default is not defined.
.. _Keyring module:
https://github.com/jaraco/keyring
"""
import keyring
value = keyring.get_password(self._name, name)
if value is not None:
return value
if default is not _PLACEHOLDER:
return default
# NOTE Should raise `KeyError`
raise ValueError(
"Item {}:{} does not exist in keyring.".format(self._name, name)
)
def delete_item(self, name):
# type: (str) -> None
"""Delete value stored in system's keyring.
See also `Keyring module`_
Args:
name (str): Name of the item to be deleted.
.. _Keyring module:
https://github.com/jaraco/keyring
"""
import keyring
self.get_item.cache_clear()
keyring.delete_password(self._name, name)
@six.add_metaclass(ABCMeta)
class ASettingRegistry():
@ -48,13 +153,6 @@ class ASettingRegistry():
# type: (str) -> ASettingRegistry
super(ASettingRegistry, self).__init__()
if six.PY3:
import keyring
# hack for cx_freeze and Windows keyring backend
if platform.system() == "Windows":
from keyring.backends import Windows
keyring.set_keyring(Windows.WinVaultKeyring())
self._name = name
self._items = {}
@ -120,7 +218,7 @@ class ASettingRegistry():
"""Delete item from settings.
Note:
see :meth:`pype.lib.local_settings.ARegistrySettings.delete_item`
see :meth:`openpype.lib.user_settings.ARegistrySettings.delete_item`
"""
pass
@ -129,78 +227,6 @@ class ASettingRegistry():
del self._items[name]
self._delete_item(name)
def set_secure_item(self, name, value):
# type: (str, str) -> None
"""Set sensitive item into system's keyring.
This uses `Keyring module`_ to save sensitive stuff into system's
keyring.
Args:
name (str): Name of the item.
value (str): Value of the item.
.. _Keyring module:
https://github.com/jaraco/keyring
"""
if six.PY2:
raise NotImplementedError(
"Keyring not available on Python 2 hosts")
import keyring
keyring.set_password(self._name, name, value)
@lru_cache(maxsize=32)
def get_secure_item(self, name):
# type: (str) -> str
"""Get value of sensitive item from system's keyring.
See also `Keyring module`_
Args:
name (str): Name of the item.
Returns:
value (str): Value of the item.
Raises:
ValueError: If item doesn't exist.
.. _Keyring module:
https://github.com/jaraco/keyring
"""
if six.PY2:
raise NotImplementedError(
"Keyring not available on Python 2 hosts")
import keyring
value = keyring.get_password(self._name, name)
if not value:
raise ValueError(
"Item {}:{} does not exist in keyring.".format(
self._name, name))
return value
def delete_secure_item(self, name):
# type: (str) -> None
"""Delete value stored in system's keyring.
See also `Keyring module`_
Args:
name (str): Name of the item to be deleted.
.. _Keyring module:
https://github.com/jaraco/keyring
"""
if six.PY2:
raise NotImplementedError(
"Keyring not available on Python 2 hosts")
import keyring
self.get_secure_item.cache_clear()
keyring.delete_password(self._name, name)
class IniSettingRegistry(ASettingRegistry):
"""Class using :mod:`configparser`.
@ -218,7 +244,7 @@ class IniSettingRegistry(ASettingRegistry):
if not os.path.exists(self._registry_file):
with open(self._registry_file, mode="w") as cfg:
print("# Settings registry", cfg)
print("# Generated by Pype {}".format(version), cfg)
print("# Generated by OpenPype {}".format(version), cfg)
now = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
print("# {}".format(now), cfg)
@ -352,7 +378,7 @@ class IniSettingRegistry(ASettingRegistry):
"""Delete item from default section.
Note:
See :meth:`~pype.lib.IniSettingsRegistry.delete_item_from_section`
See :meth:`~openpype.lib.IniSettingsRegistry.delete_item_from_section`
"""
self.delete_item_from_section("MAIN", name)
@ -369,7 +395,7 @@ class JSONSettingRegistry(ASettingRegistry):
now = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
header = {
"__metadata__": {
"pype-version": os.getenv("OPENPYPE_VERSION", "N/A"),
"openpype-version": os.getenv("OPENPYPE_VERSION", "N/A"),
"generated": now
},
"registry": {}
@ -387,7 +413,7 @@ class JSONSettingRegistry(ASettingRegistry):
"""Get item value from registry json.
Note:
See :meth:`pype.lib.JSONSettingRegistry.get_item`
See :meth:`openpype.lib.JSONSettingRegistry.get_item`
"""
with open(self._registry_file, mode="r") as cfg:
@ -420,7 +446,7 @@ class JSONSettingRegistry(ASettingRegistry):
"""Set item value to registry json.
Note:
See :meth:`pype.lib.JSONSettingRegistry.set_item`
See :meth:`openpype.lib.JSONSettingRegistry.set_item`
"""
with open(self._registry_file, "r+") as cfg:
@ -452,8 +478,8 @@ class JSONSettingRegistry(ASettingRegistry):
json.dump(data, cfg, indent=4)
class PypeSettingsRegistry(JSONSettingRegistry):
"""Class handling Pype general settings registry.
class OpenPypeSettingsRegistry(JSONSettingRegistry):
"""Class handling OpenPype general settings registry.
Attributes:
vendor (str): Name used for path construction.
@ -461,11 +487,13 @@ class PypeSettingsRegistry(JSONSettingRegistry):
"""
def __init__(self):
def __init__(self, name=None):
self.vendor = "pypeclub"
self.product = "pype"
self.product = "openpype"
if not name:
name = "openpype_settings"
path = appdirs.user_data_dir(self.product, self.vendor)
super(PypeSettingsRegistry, self).__init__("pype_settings", path)
super(OpenPypeSettingsRegistry, self).__init__(name, path)
def _create_local_site_id(registry=None):
@ -473,7 +501,7 @@ def _create_local_site_id(registry=None):
from uuid import uuid4
if registry is None:
registry = PypeSettingsRegistry()
registry = OpenPypeSettingsRegistry()
new_id = str(uuid4())
@ -489,7 +517,7 @@ def get_local_site_id():
Identifier is created if does not exists yet.
"""
registry = PypeSettingsRegistry()
registry = OpenPypeSettingsRegistry()
try:
return registry.get_item("localId")
except ValueError:
@ -504,5 +532,9 @@ def change_openpype_mongo_url(new_mongo_url):
"""
validate_mongo_connection(new_mongo_url)
registry = PypeSettingsRegistry()
registry.set_secure_item("pypeMongo", new_mongo_url)
key = "openPypeMongo"
registry = OpenPypeSecureRegistry("mongodb")
existing_value = registry.get_item(key, None)
if existing_value is not None:
registry.delete_item(key)
registry.set_item(key, new_mongo_url)

View file

@ -296,7 +296,7 @@ def _determine_mongodb() -> str:
if not openpype_mongo:
# try system keyring
try:
openpype_mongo = bootstrap.registry.get_secure_item(
openpype_mongo = bootstrap.secure_registry.get_item(
"openPypeMongo")
except ValueError:
print("*** No DB connection string specified.")
@ -305,7 +305,7 @@ def _determine_mongodb() -> str:
igniter.open_dialog()
try:
openpype_mongo = bootstrap.registry.get_secure_item(
openpype_mongo = bootstrap.secure_registry.get_item(
"openPypeMongo")
except ValueError:
raise RuntimeError("missing mongodb url")

View file

@ -11,7 +11,7 @@ import pytest
from igniter.bootstrap_repos import BootstrapRepos
from igniter.bootstrap_repos import PypeVersion
from pype.lib import PypeSettingsRegistry
from pype.lib import OpenPypeSettingsRegistry
@pytest.fixture
@ -348,7 +348,7 @@ def test_find_pype(fix_bootstrap, tmp_path_factory, monkeypatch, printer):
return d_path.as_posix()
monkeypatch.setattr(appdirs, "user_data_dir", mock_user_data_dir)
fix_bootstrap.registry = PypeSettingsRegistry()
fix_bootstrap.registry = OpenPypeSettingsRegistry()
fix_bootstrap.registry.set_item("pypePath", d_path.as_posix())
result = fix_bootstrap.find_pype(include_zips=True)

View file

@ -1,10 +1,20 @@
import pytest
from pype.lib import IniSettingRegistry
from pype.lib import JSONSettingRegistry
from pype.lib import (
IniSettingRegistry,
JSONSettingRegistry,
OpenPypeSecureRegistry
)
from uuid import uuid4
import configparser
@pytest.fixture
def secure_registry(tmpdir):
name = "pypetest_{}".format(str(uuid4()))
r = OpenPypeSecureRegistry(name, tmpdir)
yield r
@pytest.fixture
def json_registry(tmpdir):
name = "pypetest_{}".format(str(uuid4()))
@ -19,21 +29,21 @@ def ini_registry(tmpdir):
yield r
def test_keyring(json_registry):
json_registry.set_secure_item("item1", "foo")
json_registry.set_secure_item("item2", "bar")
result1 = json_registry.get_secure_item("item1")
result2 = json_registry.get_secure_item("item2")
def test_keyring(secure_registry):
secure_registry.set_item("item1", "foo")
secure_registry.set_item("item2", "bar")
result1 = secure_registry.get_item("item1")
result2 = secure_registry.get_item("item2")
assert result1 == "foo"
assert result2 == "bar"
json_registry.delete_secure_item("item1")
json_registry.delete_secure_item("item2")
secure_registry.delete_item("item1")
secure_registry.delete_item("item2")
with pytest.raises(ValueError):
json_registry.get_secure_item("item1")
json_registry.get_secure_item("item2")
secure_registry.get_item("item1")
secure_registry.get_item("item2")
def test_ini_registry(ini_registry):