From 56b8265a52f2744feb8851f34aef9fa4c4e237d1 Mon Sep 17 00:00:00 2001 From: Wojtek Date: Mon, 29 Jun 2026 17:59:33 +0200 Subject: [PATCH 1/2] restricting access by context --- dbzero/dbzero/dbzero.pyi | 3 +- dbzero/dbzero/initialization.py | 6 + dbzero/setup.py | 2 +- pyproject.toml | 2 +- python_tests/test_restricted_memo_access.py | 114 ++++++++++++++++++ src/dbzero/bindings/python/Memo.cpp | 30 +++-- src/dbzero/bindings/python/PyAPI.cpp | 18 +-- .../bindings/python/PyRestrictedMethod.cpp | 75 ++++++++++++ .../bindings/python/PyRestrictedMethod.hpp | 10 ++ src/dbzero/bindings/python/PyWorkspace.cpp | 63 +++++++++- src/dbzero/bindings/python/PyWorkspace.hpp | 15 ++- .../object_model/object/ObjectAnyBase.cpp | 4 +- .../object_model/object/ObjectAnyBase.hpp | 14 ++- .../object_model/object/ObjectAnyImpl.cpp | 2 +- .../object_model/object/ObjectImplBase.cpp | 24 +++- .../object_model/object/ObjectImplBase.hpp | 4 +- src/dbzero/workspace/Fixture.hpp | 18 +++ src/dbzero/workspace/Workspace.cpp | 14 +++ src/dbzero/workspace/Workspace.hpp | 3 + src/dbzero/workspace/WorkspaceView.cpp | 1 + 20 files changed, 385 insertions(+), 37 deletions(-) diff --git a/dbzero/dbzero/dbzero.pyi b/dbzero/dbzero/dbzero.pyi index 6effe91f..0be04327 100644 --- a/dbzero/dbzero/dbzero.pyi +++ b/dbzero/dbzero/dbzero.pyi @@ -30,6 +30,7 @@ def open(prefix_name: str, open_mode: str = "rw", **kwargs: Any) -> None: * autocommit (bool) to disable automatic commits for this prefix * restricted (bool) to restrict memo reflection access for this prefix + * restricted_context (ContextVar) to dynamically restrict memo reflection access for this prefix * slab_size (int) for memory slab allocation size in bytes * meta_io_step_size (int) for metadata I/O operation chunk size * lock_flags (dict) to change locking behavior when opening the prefix for read-write @@ -1545,7 +1546,7 @@ def get_config() -> Dict[str, Any]: * autocommit (bool): True if autocommit enabled. Defaults to True. * autocommit_interval (int): Milliseconds between commits. Defaults to 250. - * restricted (bool): True if future opened prefixes default to restricted mode. + * restricted (bool): True if future opened prefixes default to static restricted mode. Raises ------ diff --git a/dbzero/dbzero/initialization.py b/dbzero/dbzero/initialization.py index 109a50f8..eb85b07e 100644 --- a/dbzero/dbzero/initialization.py +++ b/dbzero/dbzero/initialization.py @@ -26,6 +26,7 @@ def init(dbzero_root: str, **kwargs: Any) -> None: Configure global dbzero behavior: * restricted (bool, default False) to restrict memo reflection access for future opened prefixes + * restricted_context (ContextVar) to dynamically restrict memo reflection access * autocommit (bool, default True) to enable automatic commits * autocommit_interval (int, default 367) for commit interval in milliseconds * cache_size (int, default 2 GiB) for main object cache size in bytes @@ -56,6 +57,9 @@ def init(dbzero_root: str, **kwargs: Any) -> None: if "restricted" in kwargs: init_kwargs["restricted"] = kwargs["restricted"] + if "restricted_context" in kwargs: + init_kwargs["restricted_context"] = kwargs["restricted_context"] + rpc = kwargs.get("rpc") if rpc is not None and not isinstance(rpc, Mapping): raise TypeError("rpc must be a mapping") @@ -71,4 +75,6 @@ def init(dbzero_root: str, **kwargs: Any) -> None: open_kwargs = {} if "restricted" in kwargs: open_kwargs["restricted"] = kwargs["restricted"] + if "restricted_context" in kwargs: + open_kwargs["restricted_context"] = kwargs["restricted_context"] dbzero_open(kwargs["prefix"], open_mode=open_mode, **open_kwargs) diff --git a/dbzero/setup.py b/dbzero/setup.py index aed92383..5235cde4 100644 --- a/dbzero/setup.py +++ b/dbzero/setup.py @@ -10,7 +10,7 @@ setup( name='dbzero', - version='0.4.1', + version='0.4.2', description='DBZero community edition', packages=['dbzero'], python_requires='>=3.9', diff --git a/pyproject.toml b/pyproject.toml index d5adfb52..a98b1239 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ requires = ['meson-python'] [project] name = 'dbzero' -version = '0.4.1' +version = '0.4.2' description = 'A state management system for Python 3.x that unifies your applications business logic, data persistence, and caching into a single, efficient layer.' readme = 'README.md' requires-python = '>=3.9' diff --git a/python_tests/test_restricted_memo_access.py b/python_tests/test_restricted_memo_access.py index b4c068c7..0bd27f43 100644 --- a/python_tests/test_restricted_memo_access.py +++ b/python_tests/test_restricted_memo_access.py @@ -7,6 +7,7 @@ import subprocess import sys import textwrap +from contextvars import ContextVar import pytest import dbzero as db0 @@ -200,3 +201,116 @@ def test_global_restricted_applies_to_auto_opened_prefix(db0_restricted_root): assert db0.get_config()["restricted"] is True assert db0.get_prefix_stats("auto-open-prefix")["restricted"] is True _assert_restricted(fetched, writable=False) + + +def test_global_restricted_context_controls_existing_objects(db0_restricted_root): + restricted_context = ContextVar("restricted_context", default=False) + db0.init(DB0_DIR, restricted_context=restricted_context) + assert db0.get_config()["restricted"] is False + db0.open("dynamic-global-prefix") + assert db0.get_prefix_stats("dynamic-global-prefix")["restricted"] is False + obj = RestrictedMemoAccessData(123) + + assert obj.__class__ is RestrictedMemoAccessData + assert obj.get_value.__self__ is obj + + token = restricted_context.set(True) + try: + _assert_restricted(obj) + finally: + restricted_context.reset(token) + + assert obj.__class__ is RestrictedMemoAccessData + assert obj.get_value.__self__ is obj + + +def test_prefix_restricted_context_overrides_global_context(db0_restricted_root): + global_context = ContextVar("global_context", default=True) + prefix_context = ContextVar("prefix_context", default=False) + db0.init(DB0_DIR, restricted_context=global_context) + + db0.open("global-dynamic-prefix") + global_obj = RestrictedMemoAccessData(123) + + db0.open("prefix-dynamic-prefix", restricted_context=prefix_context) + prefix_obj = RestrictedMemoAccessData(123) + + _assert_restricted(global_obj) + assert prefix_obj.__class__ is RestrictedMemoAccessData + assert prefix_obj.get_value.__self__ is prefix_obj + + token = prefix_context.set(True) + try: + _assert_restricted(prefix_obj) + finally: + prefix_context.reset(token) + + +def test_prefix_restricted_context_none_restores_global_context(db0_restricted_root): + global_context = ContextVar("global_context", default=True) + prefix_context = ContextVar("prefix_context", default=False) + db0.init(DB0_DIR, restricted_context=global_context) + db0.open("prefix-reset-dynamic-prefix", restricted_context=prefix_context) + obj = RestrictedMemoAccessData(123) + + assert obj.__class__ is RestrictedMemoAccessData + assert obj.get_value.__self__ is obj + + db0.open("prefix-reset-dynamic-prefix", restricted_context=None) + + _assert_restricted(obj) + + +def test_static_restricted_without_restricted_context_remains_restricted(db0_restricted_root): + db0.init(DB0_DIR) + db0.open("static-restricted-prefix", restricted=True) + assert db0.get_prefix_stats("static-restricted-prefix")["restricted"] is True + + _assert_restricted(RestrictedMemoAccessData(123)) + + +def test_static_restricted_object_does_not_resolve_later_restricted_context(db0_restricted_root): + class ExplodingContext: + def get(self): + raise AssertionError("restricted_context should not be resolved") + + db0.init(DB0_DIR) + db0.open("static-no-context-prefix", restricted=True) + obj = RestrictedMemoAccessData(123) + + db0.open("static-no-context-prefix", restricted_context=ExplodingContext()) + + _assert_restricted(obj) + + +def test_unset_restricted_context_is_treated_as_false(db0_restricted_root): + restricted_context = ContextVar("restricted_context") + db0.init(DB0_DIR, restricted_context=restricted_context) + db0.open("unset-dynamic-prefix") + obj = RestrictedMemoAccessData(123) + + assert obj.__class__ is RestrictedMemoAccessData + assert obj.get_value.__self__ is obj + + +def test_restricted_context_uses_python_truthiness(db0_restricted_root): + restricted_context = ContextVar("restricted_context", default=[]) + db0.init(DB0_DIR, restricted_context=restricted_context) + db0.open("truthy-dynamic-prefix") + obj = RestrictedMemoAccessData(123) + + assert obj.__class__ is RestrictedMemoAccessData + + token = restricted_context.set([object()]) + try: + _assert_restricted(obj) + finally: + restricted_context.reset(token) + + +def test_init_prefix_shortcut_forwards_restricted_context(db0_restricted_root): + restricted_context = ContextVar("restricted_context", default=True) + db0.init(DB0_DIR, prefix="shortcut-dynamic-prefix", restricted_context=restricted_context) + assert db0.get_prefix_stats("shortcut-dynamic-prefix")["restricted"] is False + + _assert_restricted(RestrictedMemoAccessData(123)) diff --git a/src/dbzero/bindings/python/Memo.cpp b/src/dbzero/bindings/python/Memo.cpp index 2bf83852..e2cddf27 100644 --- a/src/dbzero/bindings/python/Memo.cpp +++ b/src/dbzero/bindings/python/Memo.cpp @@ -71,17 +71,17 @@ namespace db0::python MemoImplT *memo_obj = reinterpret_cast(py_type->tp_alloc(py_type, 0)); // if type cannot be retrieved due to access mode then defer this operation (fallback) - auto restricted = fixture->isRestricted(); + auto restricted_flags = fixture->getObjectRestrictedFlags(); if (type) { // prepare a new dbzero instance of a known db0 class - memo_obj->makeNew(type, restricted); + memo_obj->makeNew(type, restricted_flags); } else { auto type_initializer = [py_type](db0::swine_ptr &fixture) { auto &class_factory = fixture->get(); return class_factory.getOrCreateType(py_type); }; // prepare a new db0 instance of a known db0 class - memo_obj->makeNew(std::move(type_initializer), restricted); + memo_obj->makeNew(std::move(type_initializer), restricted_flags); } return memo_obj; @@ -192,10 +192,10 @@ namespace db0::python // find py type associated dbzero class with the ClassFactory auto type = class_factory.tryGetOrCreateType(py_type); MemoObject *memo_obj = nullptr; - auto restricted = fixture->isRestricted(); + auto restricted_flags = fixture->getObjectRestrictedFlags(); if (type) { memo_obj = reinterpret_cast(py_type->tp_alloc(py_type, 0)); - memo_obj->makeNew(type, restricted); + memo_obj->makeNew(type, restricted_flags); } else { // if type cannot be retrieved due to access mode then deferr this operation (fallback) auto type_initializer = [py_type](db0::swine_ptr &fixture) { @@ -203,7 +203,7 @@ namespace db0::python return class_factory.getOrCreateType(py_type); }; memo_obj = reinterpret_cast(py_type->tp_alloc(py_type, 0)); - memo_obj->makeNew(std::move(type_initializer), restricted); + memo_obj->makeNew(std::move(type_initializer), restricted_flags); } return memo_obj; @@ -260,7 +260,14 @@ namespace db0::python return false; } - return memo_obj->ext().isRestricted(); + if (!memo_obj->ext().isRestricted()) { + return false; + } + if (!memo_obj->ext().isRestrictedCtx()) { + return true; + } + auto fixture = memo_obj->ext().tryGetFixture(); + return !!fixture && resolveRestrictedCtx(*fixture); } template @@ -404,7 +411,14 @@ namespace db0::python return false; } - return memo_obj->ext().isRestricted() || isRestrictedMemoContextActive(); + if (!memo_obj->ext().isRestricted()) { + return false; + } + if (!memo_obj->ext().isRestrictedCtx()) { + return true; + } + auto fixture = memo_obj->ext().tryGetFixture(); + return !!fixture && resolveRestrictedCtx(*fixture); } template diff --git a/src/dbzero/bindings/python/PyAPI.cpp b/src/dbzero/bindings/python/PyAPI.cpp index 00903b6d..2dacc58c 100644 --- a/src/dbzero/bindings/python/PyAPI.cpp +++ b/src/dbzero/bindings/python/PyAPI.cpp @@ -173,7 +173,7 @@ namespace db0::python // prefix_name, open_mode, autocommit (bool) static const char *kwlist[] = { "prefix_name", "open_mode", "autocommit", "slab_size", "lock_flags", "meta_io_step_size", - "page_io_step_size", "restricted", NULL + "page_io_step_size", "restricted", "restricted_context", NULL }; const char *prefix_name = nullptr; const char *open_mode = nullptr; @@ -183,9 +183,10 @@ namespace db0::python PyObject *py_meta_io_step_size = nullptr; PyObject *py_page_io_step_size = nullptr; PyObject *py_restricted = nullptr; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|sOOOOOO:open", const_cast(kwlist), + PyObject *py_restricted_context = nullptr; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|sOOOOOOO:open", const_cast(kwlist), &prefix_name, &open_mode, &py_autocommit, &py_slab_size, &py_lock_flags, &py_meta_io_step_size, - &py_page_io_step_size, &py_restricted)) + &py_page_io_step_size, &py_restricted, &py_restricted_context)) { return NULL; } @@ -248,7 +249,7 @@ namespace db0::python auto access_type = open_mode ? parseAccessType(open_mode) : db0::AccessType::READ_WRITE; PyToolkit::getPyWorkspace().open( prefix_name, access_type, autocommit, slab_size, py_lock_flags, meta_io_step_size, page_io_step_size, - restricted + restricted, py_restricted_context, py_restricted_context != nullptr ); Py_RETURN_NONE; } @@ -265,10 +266,11 @@ namespace db0::python PyObject *py_config = nullptr; PyObject *py_flags = nullptr; PyObject *py_restricted = nullptr; + PyObject *py_restricted_context = nullptr; // extract optional "path" string argument and "autcommit_interval" keyword argument - static const char *kwlist[] = {"path", "config", "lock_flags", "restricted", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OOOO:init", const_cast(kwlist), &py_path, &py_config, - &py_flags, &py_restricted)) { + static const char *kwlist[] = {"path", "config", "lock_flags", "restricted", "restricted_context", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OOOOO:init", const_cast(kwlist), &py_path, &py_config, + &py_flags, &py_restricted, &py_restricted_context)) { return NULL; } @@ -346,7 +348,7 @@ namespace db0::python if (PyDict_SetItemString(*config_obj, "restricted", restricted ? Py_True : Py_False)) { return nullptr; } - PyToolkit::getPyWorkspace().initWorkspace(str_path, *config_obj, py_flags, restricted); + PyToolkit::getPyWorkspace().initWorkspace(str_path, *config_obj, py_flags, restricted, py_restricted_context); Py_RETURN_NONE; } diff --git a/src/dbzero/bindings/python/PyRestrictedMethod.cpp b/src/dbzero/bindings/python/PyRestrictedMethod.cpp index 5fcf5256..6f571768 100644 --- a/src/dbzero/bindings/python/PyRestrictedMethod.cpp +++ b/src/dbzero/bindings/python/PyRestrictedMethod.cpp @@ -3,6 +3,8 @@ #include "PyRestrictedMethod.hpp" #include "PyToolkit.hpp" +#include +#include #include #include #include @@ -24,6 +26,37 @@ namespace db0::python thread_local bool g_restricted_memo_init = false; thread_local std::size_t g_restricted_memo_user_code_depth = 0; + class RestrictedContextResource + { + public: + explicit RestrictedContextResource(PyTypes::ObjectPtr restricted_context) + { + set(restricted_context); + } + + void set(PyTypes::ObjectPtr restricted_context) + { + if (restricted_context && restricted_context != Py_None) { + m_restricted_context = Py_BORROW(restricted_context); + } else { + m_restricted_context.reset(); + } + } + + PyTypes::ObjectPtr get() const + { + return m_restricted_context.get(); + } + + bool hasContext() const + { + return m_restricted_context.get() != nullptr; + } + + private: + PyTypes::ObjectSharedPtr m_restricted_context; + }; + struct PyRestrictedMethod { PyObject_HEAD @@ -123,6 +156,48 @@ namespace db0::python return g_restricted_memo_init || g_restricted_memo_user_code_depth > 0; } + void setFixtureRestrictedContext(db0::swine_ptr &fixture, PyTypes::ObjectPtr restricted_context) + { + auto *resource = fixture->tryGet(); + if (resource) { + resource->set(restricted_context); + } else { + resource = &fixture->addResource(restricted_context); + } + fixture->setRestrictedCtx(resource->hasContext()); + } + + bool resolveRestrictedContextVar(PyTypes::ObjectPtr restricted_context) + { + if (!restricted_context) { + return false; + } + + auto value = Py_OWN(PyObject_CallMethod(restricted_context, "get", nullptr)); + if (!value) { + if (PyErr_ExceptionMatches(PyExc_LookupError)) { + PyErr_Clear(); + return false; + } + THROWF(db0::InputException) << "restricted_context.get() failed"; + } + + auto is_restricted = PyObject_IsTrue(*value); + if (is_restricted < 0) { + THROWF(db0::InputException) << "restricted_context truthiness check failed"; + } + return is_restricted; + } + + bool resolveRestrictedCtx(const db0::Fixture &fixture) + { + auto *resource = fixture.tryGet(); + if (!resource) { + return false; + } + return resolveRestrictedContextVar(resource->get()); + } + bool isDunderName(const char *attr_name) { if (attr_name[0] != '_' || attr_name[1] != '_') { diff --git a/src/dbzero/bindings/python/PyRestrictedMethod.hpp b/src/dbzero/bindings/python/PyRestrictedMethod.hpp index af893bc5..b248ea94 100644 --- a/src/dbzero/bindings/python/PyRestrictedMethod.hpp +++ b/src/dbzero/bindings/python/PyRestrictedMethod.hpp @@ -6,6 +6,14 @@ #include #include #include "PyTypes.hpp" +#include + +namespace db0 +{ + + class Fixture; + +} namespace db0::python @@ -41,6 +49,8 @@ namespace db0::python bool isRestrictedName(const char *attr_name); bool isRestrictedMemoContextActive(); + void setFixtureRestrictedContext(db0::swine_ptr &fixture, PyTypes::ObjectPtr restricted_context); + bool resolveRestrictedCtx(const db0::Fixture &fixture); PyObject *tryRestrictedMemoGetattro( PyObject *memo_obj, PyObject *attr, diff --git a/src/dbzero/bindings/python/PyWorkspace.cpp b/src/dbzero/bindings/python/PyWorkspace.cpp index 0277c495..ab7ba352 100644 --- a/src/dbzero/bindings/python/PyWorkspace.cpp +++ b/src/dbzero/bindings/python/PyWorkspace.cpp @@ -3,13 +3,16 @@ #include "PyWorkspace.hpp" #include +#include #include #include +#include #include #include #include #include #include +#include "PyRestrictedMethod.hpp" #include "PyToolkit.hpp" namespace db0::python @@ -34,7 +37,8 @@ namespace db0::python void PyWorkspace::open(const std::string &prefix_name, AccessType access_type, std::optional autocommit, std::optional slab_size, ObjectPtr py_lock_flags, std::optional meta_io_step_size, - std::optional page_io_step_size, std::optional restricted) + std::optional page_io_step_size, std::optional restricted, + ObjectPtr restricted_context, bool restricted_context_given) { if (!m_workspace) { // initialize dbzero with current working directory @@ -51,10 +55,15 @@ namespace db0::python {}, meta_io_step_size, page_io_step_size, restricted ); } + if (restricted_context_given) { + setPrefixRestrictedContext(prefix_name, restricted_context); + } else { + syncRestrictedCtxFlag(prefix_name); + } } void PyWorkspace::initWorkspace(const std::string &root_path, ObjectPtr py_config, ObjectPtr py_lock_flags, - bool restricted) + bool restricted, ObjectPtr restricted_context) { if (m_workspace) { THROWF(db0::InternalException) << "dbzero already initialized"; @@ -66,10 +75,11 @@ namespace db0::python auto cache_size = m_config->get("cache_size"); auto object_model_initializer = db0::object_model::initializer(); - auto python_fixture_initializer = [object_model_initializer](db0::swine_ptr &fixture, + auto python_fixture_initializer = [this, object_model_initializer](db0::swine_ptr &fixture, bool is_new, bool read_only, bool is_snapshot) { object_model_initializer(fixture, is_new, read_only, is_snapshot); + db0::python::setFixtureRestrictedContext(fixture, getEffectiveRestrictedContext(*fixture)); if (!is_snapshot) { auto &iterator_pool = fixture->addResource(); fixture->addIteratorDetachHandler([&iterator_pool](std::uint64_t generation) { @@ -86,6 +96,7 @@ namespace db0::python m_workspace = std::shared_ptr( new Workspace(root_path, std::move(cache_size), {}, {}, {}, python_fixture_initializer, m_config, default_lock_flags)); m_workspace->setDefaultRestricted(restricted); + setRestrictedContext(restricted_context); // register a callback to register bindings between known memo types (language specific objects) // and the corresponding Class instances. Note that types may be prefix agnostic therefore bindings may or @@ -130,6 +141,8 @@ namespace db0::python db0::object_model::InitManager::instance.close(); PyToolkit::getTypeManager().close(timer.get()); m_config = nullptr; + m_restricted_context.reset(); + m_prefix_restricted_contexts.clear(); m_workspace = nullptr; } @@ -155,4 +168,48 @@ namespace db0::python } return m_config; } + + void PyWorkspace::setRestrictedContext(ObjectPtr restricted_context) + { + if (restricted_context && restricted_context != Py_None) { + db0::Settings::markRestrictedAccessUsed(); + m_restricted_context = Py_BORROW(restricted_context); + } else { + m_restricted_context.reset(); + } + if (m_workspace) { + m_workspace->setDefaultRestrictedCtx(m_restricted_context.get() != nullptr); + } + } + + void PyWorkspace::setPrefixRestrictedContext(const std::string &prefix_name, ObjectPtr restricted_context) + { + if (restricted_context && restricted_context != Py_None) { + db0::Settings::markRestrictedAccessUsed(); + m_prefix_restricted_contexts[prefix_name] = Py_BORROW(restricted_context); + } else { + m_prefix_restricted_contexts.erase(prefix_name); + } + syncRestrictedCtxFlag(prefix_name); + } + + PyWorkspace::ObjectPtr PyWorkspace::getEffectiveRestrictedContext(const db0::Fixture &fixture) const + { + auto it = m_prefix_restricted_contexts.find(fixture.getPrefix().getName()); + if (it != m_prefix_restricted_contexts.end()) { + return it->second.get(); + } + return m_restricted_context.get(); + } + + void PyWorkspace::syncRestrictedCtxFlag(const std::string &prefix_name) + { + if (!m_workspace) { + return; + } + auto fixture = m_workspace->tryFindFixture(prefix_name); + if (!!fixture) { + db0::python::setFixtureRestrictedContext(fixture, getEffectiveRestrictedContext(*fixture)); + } + } } diff --git a/src/dbzero/bindings/python/PyWorkspace.hpp b/src/dbzero/bindings/python/PyWorkspace.hpp index a23971e3..c88024c6 100644 --- a/src/dbzero/bindings/python/PyWorkspace.hpp +++ b/src/dbzero/bindings/python/PyWorkspace.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include "PyTypes.hpp" #include "PyWrapper.hpp" #include @@ -58,7 +59,7 @@ namespace db0::python * is dynamically fetched just-in-time */ void initWorkspace(const std::string &root_path, ObjectPtr py_config = nullptr, ObjectPtr py_lock_flags = nullptr, - bool restricted = false); + bool restricted = false, ObjectPtr restricted_context = nullptr); /** * Opens a specific prefix for read or read/write @@ -69,7 +70,8 @@ namespace db0::python void open(const std::string &prefix_name, AccessType, std::optional autocommit = {}, std::optional slab_size = {}, ObjectPtr lock_flags = nullptr, std::optional meta_io_step_size = {}, std::optional page_io_step_size = {}, - std::optional restricted = {} + std::optional restricted = {}, ObjectPtr restricted_context = nullptr, + bool restricted_context_given = false ); db0::Workspace &getWorkspace() const; @@ -88,7 +90,14 @@ namespace db0::python private: std::shared_ptr m_workspace; // optional DB0 config object - std::shared_ptr m_config; + std::shared_ptr m_config; + ObjectSharedPtr m_restricted_context; + std::unordered_map m_prefix_restricted_contexts; + + void setRestrictedContext(ObjectPtr restricted_context); + void setPrefixRestrictedContext(const std::string &prefix_name, ObjectPtr restricted_context); + ObjectPtr getEffectiveRestrictedContext(const db0::Fixture &fixture) const; + void syncRestrictedCtxFlag(const std::string &prefix_name); }; } diff --git a/src/dbzero/object_model/object/ObjectAnyBase.cpp b/src/dbzero/object_model/object/ObjectAnyBase.cpp index b3fce083..f850d68b 100644 --- a/src/dbzero/object_model/object/ObjectAnyBase.cpp +++ b/src/dbzero/object_model/object/ObjectAnyBase.cpp @@ -58,7 +58,9 @@ namespace db0::object_model auto fixture = this->getFixture(); LangToolkit::setError(LangToolkit::getTypeManager().getBadPrefixError(), fixture->getUUID()); } - this->setRestricted(new_fixture->isRestricted()); + auto [restricted, restricted_ctx] = new_fixture->getObjectRestrictedFlags(); + this->setRestricted(restricted); + this->setRestrictedCtx(restricted_ctx); } template diff --git a/src/dbzero/object_model/object/ObjectAnyBase.hpp b/src/dbzero/object_model/object/ObjectAnyBase.hpp index edce3e72..4ca5fdc6 100644 --- a/src/dbzero/object_model/object/ObjectAnyBase.hpp +++ b/src/dbzero/object_model/object/ObjectAnyBase.hpp @@ -42,7 +42,9 @@ namespace db0::object_model // runtime state: initialization has finalized INIT_COMPLETE = 0x04, // runtime state: access to this object is restricted - RESTRICTED = 0x08 + RESTRICTED = 0x08, + // runtime state: restricted access is dynamically resolved from Python ContextVar + RESTRICTED_CTX = 0x10 }; using ObjectFlags = db0::FlagSet; @@ -129,6 +131,14 @@ namespace db0::object_model return m_flags.test(ObjectOptions::RESTRICTED); } + inline void setRestrictedCtx(bool enabled) const { + m_flags.set(ObjectOptions::RESTRICTED_CTX, enabled); + } + + inline bool isRestrictedCtx() const { + return m_flags.test(ObjectOptions::RESTRICTED_CTX); + } + void markDead(); inline bool isDropped() const { @@ -190,4 +200,4 @@ namespace db0::object_model } -DECLARE_ENUM_VALUES(db0::object_model::ObjectOptions, 4) +DECLARE_ENUM_VALUES(db0::object_model::ObjectOptions, 5) diff --git a/src/dbzero/object_model/object/ObjectAnyImpl.cpp b/src/dbzero/object_model/object/ObjectAnyImpl.cpp index 6e5d63a7..9bfe6855 100644 --- a/src/dbzero/object_model/object/ObjectAnyImpl.cpp +++ b/src/dbzero/object_model/object/ObjectAnyImpl.cpp @@ -4,7 +4,7 @@ #include "ObjectAnyImpl.hpp" #include "o_object.hpp" -DEFINE_ENUM_VALUES(db0::object_model::ObjectOptions, "DROPPED", "DEFUNCT", "INIT_COMPLETE", "RESTRICTED") +DEFINE_ENUM_VALUES(db0::object_model::ObjectOptions, "DROPPED", "DEFUNCT", "INIT_COMPLETE", "RESTRICTED", "RESTRICTED_CTX") namespace db0::object_model diff --git a/src/dbzero/object_model/object/ObjectImplBase.cpp b/src/dbzero/object_model/object/ObjectImplBase.cpp index 79adf42e..b077d2f3 100644 --- a/src/dbzero/object_model/object/ObjectImplBase.cpp +++ b/src/dbzero/object_model/object/ObjectImplBase.cpp @@ -76,24 +76,30 @@ namespace db0::object_model { this->m_type = type; this->setInitComplete(true); - this->setRestricted(fixture->isRestricted()); + auto [restricted, restricted_ctx] = fixture->getObjectRestrictedFlags(); + this->setRestricted(restricted); + this->setRestrictedCtx(restricted_ctx); assert(hasValidClassRef()); } template - ObjectImplBase::ObjectImplBase(std::shared_ptr db0_class, bool restricted) + ObjectImplBase::ObjectImplBase(std::shared_ptr db0_class, std::pair restricted_flags) { // prepare for initialization InitManager::instance.template addInitializerFor(*this, db0_class); + auto [restricted, restricted_ctx] = restricted_flags; this->setRestricted(restricted); + this->setRestrictedCtx(restricted_ctx); } template - ObjectImplBase::ObjectImplBase(TypeInitializer &&type_initializer, bool restricted) + ObjectImplBase::ObjectImplBase(TypeInitializer &&type_initializer, std::pair restricted_flags) { // prepare for initialization InitManager::instance.template addInitializerFor(*this, std::move(type_initializer)); + auto [restricted, restricted_ctx] = restricted_flags; this->setRestricted(restricted); + this->setRestrictedCtx(restricted_ctx); } template @@ -105,7 +111,9 @@ namespace db0::object_model { this->m_type = type; this->setInitComplete(true); - this->setRestricted(fixture->isRestricted()); + auto [restricted, restricted_ctx] = fixture->getObjectRestrictedFlags(); + this->setRestricted(restricted); + this->setRestrictedCtx(restricted_ctx); } template @@ -113,7 +121,9 @@ namespace db0::object_model : super_t(typename super_t::tag_from_address(), fixture, address, access_mode) { this->setInitComplete(true); - this->setRestricted(fixture->isRestricted()); + auto [restricted, restricted_ctx] = fixture->getObjectRestrictedFlags(); + this->setRestricted(restricted); + this->setRestrictedCtx(restricted_ctx); } template @@ -122,7 +132,9 @@ namespace db0::object_model { this->m_type = type; this->setInitComplete(true); - this->setRestricted(fixture->isRestricted()); + auto [restricted, restricted_ctx] = fixture->getObjectRestrictedFlags(); + this->setRestricted(restricted); + this->setRestrictedCtx(restricted_ctx); assert(hasValidClassRef()); } diff --git a/src/dbzero/object_model/object/ObjectImplBase.hpp b/src/dbzero/object_model/object/ObjectImplBase.hpp index 865b0c2a..14f038e7 100644 --- a/src/dbzero/object_model/object/ObjectImplBase.hpp +++ b/src/dbzero/object_model/object/ObjectImplBase.hpp @@ -67,8 +67,8 @@ namespace db0::object_model /** * Construct new Object (uninitialized, without corresponding dbzero instance yet) */ - ObjectImplBase(std::shared_ptr, bool restricted = false); - ObjectImplBase(TypeInitializer &&, bool restricted = false); + ObjectImplBase(std::shared_ptr, std::pair restricted_flags = {}); + ObjectImplBase(TypeInitializer &&, std::pair restricted_flags = {}); // Unload from address with a known type (possibly a base type) // NOTE: unload works faster if type_hint is the exact object's type diff --git a/src/dbzero/workspace/Fixture.hpp b/src/dbzero/workspace/Fixture.hpp index 114b1bce..6f5a848b 100644 --- a/src/dbzero/workspace/Fixture.hpp +++ b/src/dbzero/workspace/Fixture.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -281,6 +282,22 @@ DB0_PACKED_BEGIN } m_restricted = restricted; } + + inline bool isRestrictedCtx() const { + return m_restricted_ctx; + } + + inline std::pair getObjectRestrictedFlags() const { + auto restricted_ctx = m_restricted_ctx; + return {m_restricted || restricted_ctx, restricted_ctx}; + } + + inline void setRestrictedCtx(bool restricted_ctx) { + if (restricted_ctx) { + Settings::markRestrictedAccessUsed(); + } + m_restricted_ctx = restricted_ctx; + } bool operator==(const Fixture &) const; bool operator!=(const Fixture &) const; @@ -340,6 +357,7 @@ DB0_PACKED_BEGIN std::atomic m_closed = false; std::atomic m_commit_pending = false; bool m_restricted = false; + bool m_restricted_ctx = false; // For read/write fixtures: // the onUpdated is called whenever the fixture is modified diff --git a/src/dbzero/workspace/Workspace.cpp b/src/dbzero/workspace/Workspace.cpp index f12866cb..647d2e1e 100644 --- a/src/dbzero/workspace/Workspace.cpp +++ b/src/dbzero/workspace/Workspace.cpp @@ -381,6 +381,7 @@ namespace db0 } auto fixture = db0::make_swine(*this, prefix, allocator, m_next_locked_section_id); fixture->setRestricted(restricted.value_or(m_default_restricted)); + fixture->setRestrictedCtx(m_default_restricted_ctx); if (m_fixture_initializer) { // initialize fixture with a model-specific initializer m_fixture_initializer(fixture, file_created, read_only, false); @@ -615,6 +616,19 @@ namespace db0 { return m_default_restricted; } + + void Workspace::setDefaultRestrictedCtx(bool restricted_ctx) + { + if (restricted_ctx) { + Settings::markRestrictedAccessUsed(); + } + m_default_restricted_ctx = restricted_ctx; + } + + bool Workspace::isDefaultRestrictedCtx() const + { + return m_default_restricted_ctx; + } FixedObjectList &Workspace::getSharedObjectList() const { return m_shared_object_list; diff --git a/src/dbzero/workspace/Workspace.hpp b/src/dbzero/workspace/Workspace.hpp index 90a87f60..7d28f991 100644 --- a/src/dbzero/workspace/Workspace.hpp +++ b/src/dbzero/workspace/Workspace.hpp @@ -233,6 +233,8 @@ namespace db0 void setDefaultRestricted(bool restricted); bool isDefaultRestricted() const; + void setDefaultRestrictedCtx(bool restricted_ctx); + bool isDefaultRestrictedCtx() const; bool drop(const PrefixName &, bool if_exists = true); @@ -329,6 +331,7 @@ namespace db0 mutable std::shared_ptr m_lang_cache; std::unique_ptr m_workspace_threads; bool m_default_restricted = false; + bool m_default_restricted_ctx = false; // associated workspace views (some of which may already be deleted) mutable db0::weak_vector m_views; // the designated "head" view with the prolonged lifetime diff --git a/src/dbzero/workspace/WorkspaceView.cpp b/src/dbzero/workspace/WorkspaceView.cpp index d4f03e21..bddc66e9 100644 --- a/src/dbzero/workspace/WorkspaceView.cpp +++ b/src/dbzero/workspace/WorkspaceView.cpp @@ -19,6 +19,7 @@ namespace db0 // copy all known type mappings from the head fixture view->get().initWith(class_factory); view->setRestricted(head->isRestricted()); + view->setRestrictedCtx(head->isRestrictedCtx()); } WorkspaceView::WorkspaceView(std::shared_ptr workspace, std::optional state_num, From bde718148fc3bb73c60a8af03932f381bf193de3 Mon Sep 17 00:00:00 2001 From: Wojtek Date: Mon, 29 Jun 2026 18:42:46 +0200 Subject: [PATCH 2/2] restricted context updates --- dbzero/dbzero/dbzero.pyi | 8 + python_tests/test_restricted_memo_access.py | 100 +++++++- src/dbzero/bindings/python/Memo.cpp | 34 +-- src/dbzero/bindings/python/PyAPI.cpp | 42 ++++ src/dbzero/bindings/python/PyAPI.hpp | 1 + .../bindings/python/PyRestrictedMethod.cpp | 223 ++++++++++++++++++ .../bindings/python/PyRestrictedMethod.hpp | 48 ++++ src/dbzero/bindings/python/PyWorkspace.cpp | 56 ++--- src/dbzero/bindings/python/PyWorkspace.hpp | 13 +- src/dbzero/bindings/python/dbzero.cpp | 1 + .../object_model/object/ObjectAnyBase.cpp | 3 - .../object_model/object/ObjectAnyBase.hpp | 24 +- .../object_model/object/ObjectAnyImpl.cpp | 2 +- .../object_model/object/ObjectImplBase.cpp | 22 +- .../object_model/object/ObjectImplBase.hpp | 4 +- .../object_model/object/ObjectInitializer.cpp | 5 +- src/dbzero/workspace/Fixture.hpp | 5 - 17 files changed, 471 insertions(+), 120 deletions(-) diff --git a/dbzero/dbzero/dbzero.pyi b/dbzero/dbzero/dbzero.pyi index 0be04327..ab72c8a5 100644 --- a/dbzero/dbzero/dbzero.pyi +++ b/dbzero/dbzero/dbzero.pyi @@ -56,6 +56,14 @@ def open(prefix_name: str, open_mode: str = "rw", **kwargs: Any) -> None: """ ... +def set_restricted(*, restricted: Optional[bool] = None, restricted_context: Any = None, + prefix: Optional[str] = None) -> None: + """Increase memo restricted-access mode globally or for an open prefix. + + The restriction level can only increase: unrestricted -> restricted_context -> restricted. + """ + ... + def close(prefix_name: Optional[str] = None) -> None: """Gracefully shut down dbzero, persisting changes and releasing resources. diff --git a/python_tests/test_restricted_memo_access.py b/python_tests/test_restricted_memo_access.py index 0bd27f43..c3363642 100644 --- a/python_tests/test_restricted_memo_access.py +++ b/python_tests/test_restricted_memo_access.py @@ -246,7 +246,7 @@ def test_prefix_restricted_context_overrides_global_context(db0_restricted_root) prefix_context.reset(token) -def test_prefix_restricted_context_none_restores_global_context(db0_restricted_root): +def test_prefix_restricted_context_none_cannot_replace_existing_context(db0_restricted_root): global_context = ContextVar("global_context", default=True) prefix_context = ContextVar("prefix_context", default=False) db0.init(DB0_DIR, restricted_context=global_context) @@ -256,9 +256,10 @@ def test_prefix_restricted_context_none_restores_global_context(db0_restricted_r assert obj.__class__ is RestrictedMemoAccessData assert obj.get_value.__self__ is obj - db0.open("prefix-reset-dynamic-prefix", restricted_context=None) + with pytest.raises(RuntimeError): + db0.open("prefix-reset-dynamic-prefix", restricted_context=None) - _assert_restricted(obj) + assert obj.__class__ is RestrictedMemoAccessData def test_static_restricted_without_restricted_context_remains_restricted(db0_restricted_root): @@ -278,7 +279,8 @@ def get(self): db0.open("static-no-context-prefix", restricted=True) obj = RestrictedMemoAccessData(123) - db0.open("static-no-context-prefix", restricted_context=ExplodingContext()) + with pytest.raises(RuntimeError): + db0.open("static-no-context-prefix", restricted_context=ExplodingContext()) _assert_restricted(obj) @@ -314,3 +316,93 @@ def test_init_prefix_shortcut_forwards_restricted_context(db0_restricted_root): assert db0.get_prefix_stats("shortcut-dynamic-prefix")["restricted"] is False _assert_restricted(RestrictedMemoAccessData(123)) + + +def test_set_restricted_context_upgrades_existing_and_future_prefixes(db0_restricted_root): + restricted_context = ContextVar("restricted_context", default=False) + db0.init(DB0_DIR) + db0.open("dynamic-upgrade-prefix") + obj = RestrictedMemoAccessData(123) + + assert obj.__class__ is RestrictedMemoAccessData + db0.set_restricted(restricted_context=restricted_context) + assert db0.get_config()["restricted"] is False + + assert obj.__class__ is RestrictedMemoAccessData + token = restricted_context.set(True) + try: + _assert_restricted(obj) + db0.open("future-dynamic-upgrade-prefix") + _assert_restricted(RestrictedMemoAccessData(123)) + finally: + restricted_context.reset(token) + + +def test_set_restricted_static_upgrades_dynamic_context(db0_restricted_root): + restricted_context = ContextVar("restricted_context", default=False) + db0.init(DB0_DIR) + db0.set_restricted(restricted_context=restricted_context) + db0.open("dynamic-to-static-prefix") + obj = RestrictedMemoAccessData(123) + + assert obj.__class__ is RestrictedMemoAccessData + db0.set_restricted(restricted=True) + + _assert_restricted(obj) + db0.open("future-static-upgrade-prefix") + assert db0.get_prefix_stats("future-static-upgrade-prefix")["restricted"] is True + _assert_restricted(RestrictedMemoAccessData(123)) + + +def test_set_restricted_rejects_context_after_static_restricted(db0_restricted_root): + restricted_context = ContextVar("restricted_context", default=False) + db0.init(DB0_DIR, restricted=True) + db0.open("static-terminal-prefix") + obj = RestrictedMemoAccessData(123) + + with pytest.raises(RuntimeError): + db0.set_restricted(restricted_context=restricted_context) + + _assert_restricted(obj) + + +def test_set_restricted_rejects_context_replacement(db0_restricted_root): + first_context = ContextVar("first_context", default=False) + second_context = ContextVar("second_context", default=True) + db0.init(DB0_DIR) + db0.set_restricted(restricted_context=first_context) + db0.open("context-replacement-prefix") + obj = RestrictedMemoAccessData(123) + + with pytest.raises(RuntimeError): + db0.set_restricted(restricted_context=second_context) + + assert obj.__class__ is RestrictedMemoAccessData + token = first_context.set(True) + try: + _assert_restricted(obj) + finally: + first_context.reset(token) + + +def test_set_restricted_rejects_false(db0_restricted_root): + db0.init(DB0_DIR) + + with pytest.raises(RuntimeError): + db0.set_restricted(restricted=False) + + +def test_set_restricted_prefix_only_upgrades_target_prefix(db0_restricted_root): + restricted_context = ContextVar("restricted_context", default=True) + db0.init(DB0_DIR) + + db0.open("target-prefix") + target = RestrictedMemoAccessData(123) + db0.open("other-prefix") + other = RestrictedMemoAccessData(123) + + db0.set_restricted(prefix="target-prefix", restricted_context=restricted_context) + + _assert_restricted(target) + assert other.__class__ is RestrictedMemoAccessData + assert other.get_value.__self__ is other diff --git a/src/dbzero/bindings/python/Memo.cpp b/src/dbzero/bindings/python/Memo.cpp index e2cddf27..9e13a6f2 100644 --- a/src/dbzero/bindings/python/Memo.cpp +++ b/src/dbzero/bindings/python/Memo.cpp @@ -71,17 +71,16 @@ namespace db0::python MemoImplT *memo_obj = reinterpret_cast(py_type->tp_alloc(py_type, 0)); // if type cannot be retrieved due to access mode then defer this operation (fallback) - auto restricted_flags = fixture->getObjectRestrictedFlags(); if (type) { // prepare a new dbzero instance of a known db0 class - memo_obj->makeNew(type, restricted_flags); + memo_obj->makeNew(type); } else { auto type_initializer = [py_type](db0::swine_ptr &fixture) { auto &class_factory = fixture->get(); return class_factory.getOrCreateType(py_type); }; // prepare a new db0 instance of a known db0 class - memo_obj->makeNew(std::move(type_initializer), restricted_flags); + memo_obj->makeNew(std::move(type_initializer)); } return memo_obj; @@ -192,10 +191,9 @@ namespace db0::python // find py type associated dbzero class with the ClassFactory auto type = class_factory.tryGetOrCreateType(py_type); MemoObject *memo_obj = nullptr; - auto restricted_flags = fixture->getObjectRestrictedFlags(); if (type) { memo_obj = reinterpret_cast(py_type->tp_alloc(py_type, 0)); - memo_obj->makeNew(type, restricted_flags); + memo_obj->makeNew(type); } else { // if type cannot be retrieved due to access mode then deferr this operation (fallback) auto type_initializer = [py_type](db0::swine_ptr &fixture) { @@ -203,7 +201,7 @@ namespace db0::python return class_factory.getOrCreateType(py_type); }; memo_obj = reinterpret_cast(py_type->tp_alloc(py_type, 0)); - memo_obj->makeNew(std::move(type_initializer), restricted_flags); + memo_obj->makeNew(std::move(type_initializer)); } return memo_obj; @@ -260,14 +258,18 @@ namespace db0::python return false; } - if (!memo_obj->ext().isRestricted()) { + auto fixture = memo_obj->ext().tryGetFixture(); + if (!fixture) { return false; } - if (!memo_obj->ext().isRestrictedCtx()) { + + if (fixture->isRestricted()) { return true; } - auto fixture = memo_obj->ext().tryGetFixture(); - return !!fixture && resolveRestrictedCtx(*fixture); + if (!fixture->isRestrictedCtx()) { + return false; + } + return resolveRestrictedCtx(*fixture); } template @@ -411,14 +413,18 @@ namespace db0::python return false; } - if (!memo_obj->ext().isRestricted()) { + auto fixture = memo_obj->ext().tryGetFixture(); + if (!fixture) { return false; } - if (!memo_obj->ext().isRestrictedCtx()) { + + if (fixture->isRestricted()) { return true; } - auto fixture = memo_obj->ext().tryGetFixture(); - return !!fixture && resolveRestrictedCtx(*fixture); + if (!fixture->isRestrictedCtx()) { + return false; + } + return resolveRestrictedCtx(*fixture); } template diff --git a/src/dbzero/bindings/python/PyAPI.cpp b/src/dbzero/bindings/python/PyAPI.cpp index 2dacc58c..7bca5546 100644 --- a/src/dbzero/bindings/python/PyAPI.cpp +++ b/src/dbzero/bindings/python/PyAPI.cpp @@ -259,6 +259,48 @@ namespace db0::python PY_API_FUNC return runSafe(tryOpen, self, args, kwargs); } + + PyObject *trySetRestricted(PyObject *, PyObject *args, PyObject *kwargs) + { + PyObject *py_restricted = nullptr; + PyObject *py_restricted_context = nullptr; + PyObject *py_prefix = nullptr; + static const char *kwlist[] = {"restricted", "restricted_context", "prefix", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OOO:set_restricted", const_cast(kwlist), + &py_restricted, &py_restricted_context, &py_prefix)) + { + return NULL; + } + + std::optional restricted; + if (py_restricted && py_restricted != Py_None) { + auto is_restricted = PyObject_IsTrue(py_restricted); + if (is_restricted < 0) { + return NULL; + } + restricted = is_restricted; + } + + std::optional prefix_name; + if (py_prefix && py_prefix != Py_None) { + if (!PyUnicode_Check(py_prefix)) { + PyErr_SetString(PyExc_TypeError, "Invalid argument type: prefix"); + return NULL; + } + prefix_name = PyUnicode_AsUTF8(py_prefix); + } + + PyToolkit::getPyWorkspace().setRestricted( + restricted, py_restricted_context, py_restricted_context != nullptr, prefix_name + ); + Py_RETURN_NONE; + } + + PyObject *PyAPI_setRestricted(PyObject *self, PyObject *args, PyObject *kwargs) + { + PY_API_FUNC + return runSafe(trySetRestricted, self, args, kwargs); + } PyObject *tryInit(PyObject *self, PyObject *args, PyObject *kwargs) { diff --git a/src/dbzero/bindings/python/PyAPI.hpp b/src/dbzero/bindings/python/PyAPI.hpp index abe284ce..e575695b 100644 --- a/src/dbzero/bindings/python/PyAPI.hpp +++ b/src/dbzero/bindings/python/PyAPI.hpp @@ -52,6 +52,7 @@ namespace db0::python * Opens or creates a prefix for read or read/write */ PyObject *PyAPI_open(PyObject *self, PyObject *args, PyObject *kwargs); + PyObject *PyAPI_setRestricted(PyObject *self, PyObject *args, PyObject *kwargs); PyObject *PyAPI_drop(PyObject *self, PyObject *args); diff --git a/src/dbzero/bindings/python/PyRestrictedMethod.cpp b/src/dbzero/bindings/python/PyRestrictedMethod.cpp index 6f571768..e1ba230d 100644 --- a/src/dbzero/bindings/python/PyRestrictedMethod.cpp +++ b/src/dbzero/bindings/python/PyRestrictedMethod.cpp @@ -3,11 +3,16 @@ #include "PyRestrictedMethod.hpp" #include "PyToolkit.hpp" +#include "PySafeAPI.hpp" #include +#include +#include +#include #include #include #include #include +#include #ifndef PYVAROBJECT_HEAD_INIT_DESIGNATED #define PYVAROBJECT_HEAD_INIT_DESIGNATED \ @@ -156,6 +161,224 @@ namespace db0::python return g_restricted_memo_init || g_restricted_memo_user_code_depth > 0; } + void RestrictedContextManager::clear() + { + m_default_context.reset(); + m_prefix_contexts.clear(); + } + + void RestrictedContextManager::initDefault(db0::Workspace &workspace, ObjectPtr restricted_context) + { + setDefaultContext(workspace, restricted_context); + } + + RestrictedContextManager::RestrictionLevel RestrictedContextManager::getDefaultLevel( + const db0::Workspace &workspace) const + { + if (workspace.isDefaultRestricted()) { + return RestrictionLevel::statically_restricted; + } + if (workspace.isDefaultRestrictedCtx()) { + return RestrictionLevel::context; + } + return RestrictionLevel::unrestricted; + } + + RestrictedContextManager::RestrictionLevel RestrictedContextManager::getFixtureLevel( + const db0::Fixture &fixture) const + { + if (fixture.isRestricted()) { + return RestrictionLevel::statically_restricted; + } + if (fixture.isRestrictedCtx()) { + return RestrictionLevel::context; + } + return RestrictionLevel::unrestricted; + } + + RestrictedContextManager::RestrictionLevel RestrictedContextManager::getRequestedLevel( + std::optional restricted, ObjectPtr restricted_context, bool restricted_context_given) const + { + if (restricted && !*restricted) { + THROWF(db0::InputException) << "restricted mode cannot be weakened"; + } + if (restricted && *restricted) { + if (restricted_context_given && restricted_context && restricted_context != Py_None) { + THROWF(db0::InputException) << "restricted=True cannot be combined with restricted_context"; + } + return RestrictionLevel::statically_restricted; + } + if (restricted_context_given && restricted_context && restricted_context != Py_None) { + return RestrictionLevel::context; + } + return RestrictionLevel::unrestricted; + } + + RestrictedContextManager::ObjectPtr RestrictedContextManager::getPrefixContext(const std::string &prefix_name) const + { + auto it = m_prefix_contexts.find(prefix_name); + if (it != m_prefix_contexts.end()) { + return it->second.get(); + } + return nullptr; + } + + void RestrictedContextManager::validateUpgrade(RestrictionLevel current_level, ObjectPtr current_context, + RestrictionLevel requested_level, ObjectPtr requested_context) const + { + if (requested_level == RestrictionLevel::unrestricted) { + if (current_level != RestrictionLevel::unrestricted) { + THROWF(db0::InputException) << "restricted mode cannot be weakened"; + } + return; + } + if (current_level == RestrictionLevel::statically_restricted && + requested_level != RestrictionLevel::statically_restricted) + { + THROWF(db0::InputException) << "static restricted mode cannot be changed to context restricted mode"; + } + if (current_level == RestrictionLevel::context && requested_level == RestrictionLevel::context && + current_context != requested_context) + { + THROWF(db0::InputException) << "restricted_context cannot be replaced"; + } + } + + void RestrictedContextManager::setDefaultContext(db0::Workspace &workspace, ObjectPtr restricted_context) + { + if (restricted_context && restricted_context != Py_None) { + db0::Settings::markRestrictedAccessUsed(); + m_default_context = Py_BORROW(restricted_context); + } else { + m_default_context.reset(); + } + workspace.setDefaultRestrictedCtx(m_default_context.get() != nullptr); + } + + void RestrictedContextManager::setPrefixContext(db0::Workspace &workspace, const std::string &prefix_name, + ObjectPtr restricted_context) + { + if (restricted_context && restricted_context != Py_None) { + db0::Settings::markRestrictedAccessUsed(); + m_prefix_contexts[prefix_name] = Py_BORROW(restricted_context); + } else { + m_prefix_contexts.erase(prefix_name); + } + syncFixture(workspace, prefix_name); + } + + void RestrictedContextManager::syncFixture(db0::Workspace &workspace, const std::string &prefix_name) const + { + auto fixture = workspace.tryFindFixture(prefix_name); + if (!!fixture) { + db0::python::setFixtureRestrictedContext(fixture, fixture->isRestricted() ? nullptr : getEffectiveContext(*fixture)); + } + } + + void RestrictedContextManager::syncAllFixtures(db0::Workspace &workspace) const + { + std::vector prefix_names; + workspace.forEachFixture([&prefix_names](const db0::Fixture &fixture) { + prefix_names.push_back(fixture.getPrefix().getName()); + return true; + }); + for (auto &prefix_name: prefix_names) { + syncFixture(workspace, prefix_name); + } + } + + RestrictedContextManager::ObjectPtr RestrictedContextManager::getEffectiveContext(const db0::Fixture &fixture) const + { + auto prefix_context = getPrefixContext(fixture.getPrefix().getName()); + return prefix_context ? prefix_context : m_default_context.get(); + } + + void RestrictedContextManager::setConfigRestricted(std::shared_ptr config, bool restricted) const + { + if (!config) { + return; + } + PySafeDict_SetItemString(config->getRawConfig().get(), "restricted", Py_BORROW(restricted ? Py_True : Py_False)); + } + + void RestrictedContextManager::setRestricted(db0::Workspace &workspace, std::shared_ptr config, + std::optional restricted, ObjectPtr restricted_context, bool restricted_context_given, + const std::optional &prefix_name) + { + auto requested_level = getRequestedLevel(restricted, restricted_context, restricted_context_given); + if (prefix_name) { + auto fixture = workspace.tryFindFixture(*prefix_name); + if (!fixture) { + THROWF(db0::InputException) << "Prefix is not open: " << *prefix_name; + } + validateUpgrade(getFixtureLevel(*fixture), getEffectiveContext(*fixture), requested_level, restricted_context); + if (requested_level == RestrictionLevel::statically_restricted) { + fixture->setRestricted(true); + setPrefixContext(workspace, *prefix_name, nullptr); + } else if (requested_level == RestrictionLevel::context) { + setPrefixContext(workspace, *prefix_name, restricted_context); + } + return; + } + + validateUpgrade(getDefaultLevel(workspace), m_default_context.get(), requested_level, restricted_context); + if (requested_level == RestrictionLevel::statically_restricted) { + workspace.setDefaultRestricted(true); + setDefaultContext(workspace, nullptr); + setConfigRestricted(config, true); + std::vector prefix_names; + workspace.forEachFixture([&prefix_names](const db0::Fixture &fixture) { + prefix_names.push_back(fixture.getPrefix().getName()); + return true; + }); + for (auto &name: prefix_names) { + auto fixture = workspace.tryFindFixture(name); + if (!!fixture) { + fixture->setRestricted(true); + } + } + syncAllFixtures(workspace); + } else if (requested_level == RestrictionLevel::context) { + setDefaultContext(workspace, restricted_context); + syncAllFixtures(workspace); + } + } + + void RestrictedContextManager::validateOpenRestricted(db0::Workspace &workspace, const std::string &prefix_name, + std::optional restricted) const + { + if (!restricted || *restricted) { + return; + } + auto fixture = workspace.tryFindFixture(prefix_name); + if (!!fixture && getFixtureLevel(*fixture) != RestrictionLevel::unrestricted) { + THROWF(db0::InputException) << "restricted mode cannot be weakened"; + } + } + + void RestrictedContextManager::applyOpenRestrictedContext(db0::Workspace &workspace, const std::string &prefix_name, + ObjectPtr restricted_context, bool restricted_context_given, bool is_initial_prefix_config) + { + if (!restricted_context_given) { + syncFixture(workspace, prefix_name); + return; + } + auto fixture = workspace.tryFindFixture(prefix_name); + if (!fixture) { + return; + } + auto requested_level = restricted_context && restricted_context != Py_None + ? RestrictionLevel::context : RestrictionLevel::unrestricted; + if (!is_initial_prefix_config) { + validateUpgrade(getFixtureLevel(*fixture), getEffectiveContext(*fixture), requested_level, restricted_context); + } + if (requested_level == RestrictionLevel::context) { + setPrefixContext(workspace, prefix_name, restricted_context); + } else { + syncFixture(workspace, prefix_name); + } + } + void setFixtureRestrictedContext(db0::swine_ptr &fixture, PyTypes::ObjectPtr restricted_context) { auto *resource = fixture->tryGet(); diff --git a/src/dbzero/bindings/python/PyRestrictedMethod.hpp b/src/dbzero/bindings/python/PyRestrictedMethod.hpp index b248ea94..0bd8e30d 100644 --- a/src/dbzero/bindings/python/PyRestrictedMethod.hpp +++ b/src/dbzero/bindings/python/PyRestrictedMethod.hpp @@ -5,13 +5,19 @@ #include #include +#include +#include +#include +#include #include "PyTypes.hpp" #include namespace db0 { + class Config; class Fixture; + class Workspace; } @@ -49,6 +55,48 @@ namespace db0::python bool isRestrictedName(const char *attr_name); bool isRestrictedMemoContextActive(); + + class RestrictedContextManager + { + public: + using ObjectPtr = PyTypes::ObjectPtr; + using ObjectSharedPtr = PyTypes::ObjectSharedPtr; + + void clear(); + void initDefault(db0::Workspace &workspace, ObjectPtr restricted_context); + void setRestricted(db0::Workspace &workspace, std::shared_ptr config, + std::optional restricted, ObjectPtr restricted_context, bool restricted_context_given, + const std::optional &prefix_name = {}); + void validateOpenRestricted(db0::Workspace &workspace, const std::string &prefix_name, + std::optional restricted) const; + void applyOpenRestrictedContext(db0::Workspace &workspace, const std::string &prefix_name, + ObjectPtr restricted_context, bool restricted_context_given, bool is_initial_prefix_config); + ObjectPtr getEffectiveContext(const db0::Fixture &fixture) const; + void syncFixture(db0::Workspace &workspace, const std::string &prefix_name) const; + + private: + enum class RestrictionLevel { + unrestricted, + context, + statically_restricted + }; + + ObjectSharedPtr m_default_context; + std::unordered_map m_prefix_contexts; + + RestrictionLevel getDefaultLevel(const db0::Workspace &workspace) const; + RestrictionLevel getFixtureLevel(const db0::Fixture &fixture) const; + RestrictionLevel getRequestedLevel(std::optional restricted, ObjectPtr restricted_context, + bool restricted_context_given) const; + ObjectPtr getPrefixContext(const std::string &prefix_name) const; + void validateUpgrade(RestrictionLevel current_level, ObjectPtr current_context, + RestrictionLevel requested_level, ObjectPtr requested_context) const; + void setDefaultContext(db0::Workspace &workspace, ObjectPtr restricted_context); + void setPrefixContext(db0::Workspace &workspace, const std::string &prefix_name, ObjectPtr restricted_context); + void syncAllFixtures(db0::Workspace &workspace) const; + void setConfigRestricted(std::shared_ptr config, bool restricted) const; + }; + void setFixtureRestrictedContext(db0::swine_ptr &fixture, PyTypes::ObjectPtr restricted_context); bool resolveRestrictedCtx(const db0::Fixture &fixture); PyObject *tryRestrictedMemoGetattro( diff --git a/src/dbzero/bindings/python/PyWorkspace.cpp b/src/dbzero/bindings/python/PyWorkspace.cpp index ab7ba352..c703a5c1 100644 --- a/src/dbzero/bindings/python/PyWorkspace.cpp +++ b/src/dbzero/bindings/python/PyWorkspace.cpp @@ -44,6 +44,9 @@ namespace db0::python // initialize dbzero with current working directory initWorkspace(""); } + + auto is_initial_prefix_config = !m_workspace->tryFindFixture(prefix_name); + m_restricted_contexts.validateOpenRestricted(*m_workspace, prefix_name, restricted); if (py_lock_flags) { db0::Config lock_flags_config(py_lock_flags); @@ -55,10 +58,11 @@ namespace db0::python {}, meta_io_step_size, page_io_step_size, restricted ); } - if (restricted_context_given) { - setPrefixRestrictedContext(prefix_name, restricted_context); + if (restricted && *restricted) { + m_restricted_contexts.setRestricted(*m_workspace, m_config, restricted, nullptr, false, prefix_name); } else { - syncRestrictedCtxFlag(prefix_name); + m_restricted_contexts.applyOpenRestrictedContext(*m_workspace, prefix_name, restricted_context, + restricted_context_given, is_initial_prefix_config); } } @@ -96,7 +100,7 @@ namespace db0::python m_workspace = std::shared_ptr( new Workspace(root_path, std::move(cache_size), {}, {}, {}, python_fixture_initializer, m_config, default_lock_flags)); m_workspace->setDefaultRestricted(restricted); - setRestrictedContext(restricted_context); + m_restricted_contexts.initDefault(*m_workspace, restricted_context); // register a callback to register bindings between known memo types (language specific objects) // and the corresponding Class instances. Note that types may be prefix agnostic therefore bindings may or @@ -141,8 +145,7 @@ namespace db0::python db0::object_model::InitManager::instance.close(); PyToolkit::getTypeManager().close(timer.get()); m_config = nullptr; - m_restricted_context.reset(); - m_prefix_restricted_contexts.clear(); + m_restricted_contexts.clear(); m_workspace = nullptr; } @@ -169,47 +172,18 @@ namespace db0::python return m_config; } - void PyWorkspace::setRestrictedContext(ObjectPtr restricted_context) - { - if (restricted_context && restricted_context != Py_None) { - db0::Settings::markRestrictedAccessUsed(); - m_restricted_context = Py_BORROW(restricted_context); - } else { - m_restricted_context.reset(); - } - if (m_workspace) { - m_workspace->setDefaultRestrictedCtx(m_restricted_context.get() != nullptr); - } - } - - void PyWorkspace::setPrefixRestrictedContext(const std::string &prefix_name, ObjectPtr restricted_context) - { - if (restricted_context && restricted_context != Py_None) { - db0::Settings::markRestrictedAccessUsed(); - m_prefix_restricted_contexts[prefix_name] = Py_BORROW(restricted_context); - } else { - m_prefix_restricted_contexts.erase(prefix_name); - } - syncRestrictedCtxFlag(prefix_name); - } - PyWorkspace::ObjectPtr PyWorkspace::getEffectiveRestrictedContext(const db0::Fixture &fixture) const { - auto it = m_prefix_restricted_contexts.find(fixture.getPrefix().getName()); - if (it != m_prefix_restricted_contexts.end()) { - return it->second.get(); - } - return m_restricted_context.get(); + return m_restricted_contexts.getEffectiveContext(fixture); } - void PyWorkspace::syncRestrictedCtxFlag(const std::string &prefix_name) + void PyWorkspace::setRestricted(std::optional restricted, ObjectPtr restricted_context, + bool restricted_context_given, const std::optional &prefix_name) { if (!m_workspace) { - return; - } - auto fixture = m_workspace->tryFindFixture(prefix_name); - if (!!fixture) { - db0::python::setFixtureRestrictedContext(fixture, getEffectiveRestrictedContext(*fixture)); + initWorkspace(""); } + m_restricted_contexts.setRestricted(*m_workspace, m_config, restricted, restricted_context, + restricted_context_given, prefix_name); } } diff --git a/src/dbzero/bindings/python/PyWorkspace.hpp b/src/dbzero/bindings/python/PyWorkspace.hpp index c88024c6..cc78a9a3 100644 --- a/src/dbzero/bindings/python/PyWorkspace.hpp +++ b/src/dbzero/bindings/python/PyWorkspace.hpp @@ -4,13 +4,14 @@ #pragma once #include +#include #include #include #include #include -#include #include "PyTypes.hpp" #include "PyWrapper.hpp" +#include "PyRestrictedMethod.hpp" #include #include #include "MemoObject.hpp" @@ -44,7 +45,6 @@ namespace db0::python { public: using ObjectPtr = typename PyTypes::ObjectPtr; - using ObjectSharedPtr = typename PyTypes::ObjectSharedPtr; using TypeObjectPtr = typename PyTypes::TypeObjectPtr; PyWorkspace(); @@ -87,17 +87,16 @@ namespace db0::python const std::shared_ptr &getConfig() const; + void setRestricted(std::optional restricted = {}, ObjectPtr restricted_context = nullptr, + bool restricted_context_given = false, const std::optional &prefix_name = {}); + private: std::shared_ptr m_workspace; // optional DB0 config object std::shared_ptr m_config; - ObjectSharedPtr m_restricted_context; - std::unordered_map m_prefix_restricted_contexts; + RestrictedContextManager m_restricted_contexts; - void setRestrictedContext(ObjectPtr restricted_context); - void setPrefixRestrictedContext(const std::string &prefix_name, ObjectPtr restricted_context); ObjectPtr getEffectiveRestrictedContext(const db0::Fixture &fixture) const; - void syncRestrictedCtxFlag(const std::string &prefix_name); }; } diff --git a/src/dbzero/bindings/python/dbzero.cpp b/src/dbzero/bindings/python/dbzero.cpp index 6c412705..40284c20 100644 --- a/src/dbzero/bindings/python/dbzero.cpp +++ b/src/dbzero/bindings/python/dbzero.cpp @@ -42,6 +42,7 @@ static PyMethodDef dbzero_methods[] = { {"_init", (PyCFunction)&py::PyAPI_init, METH_VARARGS | METH_KEYWORDS, "Initialize dbzero workspace at a specific root path"}, {"open", (PyCFunction)&py::PyAPI_open, METH_VARARGS | METH_KEYWORDS, "Open or create a prefix for read or read/write"}, + {"set_restricted", (PyCFunction)&py::PyAPI_setRestricted, METH_VARARGS | METH_KEYWORDS, "Increase restricted memo access level"}, {"close", &py::PyAPI_close, METH_VARARGS, ""}, {"drop", &py::PyAPI_drop, METH_VARARGS, "Drop prefix (if exists)"}, {"commit", &py::PyAPI_commit, METH_VARARGS, "Commit data to disk / persistent storage"}, diff --git a/src/dbzero/object_model/object/ObjectAnyBase.cpp b/src/dbzero/object_model/object/ObjectAnyBase.cpp index f850d68b..644e9291 100644 --- a/src/dbzero/object_model/object/ObjectAnyBase.cpp +++ b/src/dbzero/object_model/object/ObjectAnyBase.cpp @@ -58,9 +58,6 @@ namespace db0::object_model auto fixture = this->getFixture(); LangToolkit::setError(LangToolkit::getTypeManager().getBadPrefixError(), fixture->getUUID()); } - auto [restricted, restricted_ctx] = new_fixture->getObjectRestrictedFlags(); - this->setRestricted(restricted); - this->setRestrictedCtx(restricted_ctx); } template diff --git a/src/dbzero/object_model/object/ObjectAnyBase.hpp b/src/dbzero/object_model/object/ObjectAnyBase.hpp index 4ca5fdc6..f4f64f02 100644 --- a/src/dbzero/object_model/object/ObjectAnyBase.hpp +++ b/src/dbzero/object_model/object/ObjectAnyBase.hpp @@ -40,11 +40,7 @@ namespace db0::object_model // object is defunct - e.g. due to exception on __init__ DEFUNCT = 0x02, // runtime state: initialization has finalized - INIT_COMPLETE = 0x04, - // runtime state: access to this object is restricted - RESTRICTED = 0x08, - // runtime state: restricted access is dynamically resolved from Python ContextVar - RESTRICTED_CTX = 0x10 + INIT_COMPLETE = 0x04 }; using ObjectFlags = db0::FlagSet; @@ -123,22 +119,6 @@ namespace db0::object_model return m_flags.test(ObjectOptions::INIT_COMPLETE); } - inline void setRestricted(bool enabled) const { - m_flags.set(ObjectOptions::RESTRICTED, enabled); - } - - inline bool isRestricted() const { - return m_flags.test(ObjectOptions::RESTRICTED); - } - - inline void setRestrictedCtx(bool enabled) const { - m_flags.set(ObjectOptions::RESTRICTED_CTX, enabled); - } - - inline bool isRestrictedCtx() const { - return m_flags.test(ObjectOptions::RESTRICTED_CTX); - } - void markDead(); inline bool isDropped() const { @@ -200,4 +180,4 @@ namespace db0::object_model } -DECLARE_ENUM_VALUES(db0::object_model::ObjectOptions, 5) +DECLARE_ENUM_VALUES(db0::object_model::ObjectOptions, 3) diff --git a/src/dbzero/object_model/object/ObjectAnyImpl.cpp b/src/dbzero/object_model/object/ObjectAnyImpl.cpp index 9bfe6855..aee0ca9e 100644 --- a/src/dbzero/object_model/object/ObjectAnyImpl.cpp +++ b/src/dbzero/object_model/object/ObjectAnyImpl.cpp @@ -4,7 +4,7 @@ #include "ObjectAnyImpl.hpp" #include "o_object.hpp" -DEFINE_ENUM_VALUES(db0::object_model::ObjectOptions, "DROPPED", "DEFUNCT", "INIT_COMPLETE", "RESTRICTED", "RESTRICTED_CTX") +DEFINE_ENUM_VALUES(db0::object_model::ObjectOptions, "DROPPED", "DEFUNCT", "INIT_COMPLETE") namespace db0::object_model diff --git a/src/dbzero/object_model/object/ObjectImplBase.cpp b/src/dbzero/object_model/object/ObjectImplBase.cpp index b077d2f3..6dbd8206 100644 --- a/src/dbzero/object_model/object/ObjectImplBase.cpp +++ b/src/dbzero/object_model/object/ObjectImplBase.cpp @@ -76,30 +76,21 @@ namespace db0::object_model { this->m_type = type; this->setInitComplete(true); - auto [restricted, restricted_ctx] = fixture->getObjectRestrictedFlags(); - this->setRestricted(restricted); - this->setRestrictedCtx(restricted_ctx); assert(hasValidClassRef()); } template - ObjectImplBase::ObjectImplBase(std::shared_ptr db0_class, std::pair restricted_flags) + ObjectImplBase::ObjectImplBase(std::shared_ptr db0_class) { // prepare for initialization InitManager::instance.template addInitializerFor(*this, db0_class); - auto [restricted, restricted_ctx] = restricted_flags; - this->setRestricted(restricted); - this->setRestrictedCtx(restricted_ctx); } template - ObjectImplBase::ObjectImplBase(TypeInitializer &&type_initializer, std::pair restricted_flags) + ObjectImplBase::ObjectImplBase(TypeInitializer &&type_initializer) { // prepare for initialization InitManager::instance.template addInitializerFor(*this, std::move(type_initializer)); - auto [restricted, restricted_ctx] = restricted_flags; - this->setRestricted(restricted); - this->setRestrictedCtx(restricted_ctx); } template @@ -111,9 +102,6 @@ namespace db0::object_model { this->m_type = type; this->setInitComplete(true); - auto [restricted, restricted_ctx] = fixture->getObjectRestrictedFlags(); - this->setRestricted(restricted); - this->setRestrictedCtx(restricted_ctx); } template @@ -121,9 +109,6 @@ namespace db0::object_model : super_t(typename super_t::tag_from_address(), fixture, address, access_mode) { this->setInitComplete(true); - auto [restricted, restricted_ctx] = fixture->getObjectRestrictedFlags(); - this->setRestricted(restricted); - this->setRestrictedCtx(restricted_ctx); } template @@ -132,9 +117,6 @@ namespace db0::object_model { this->m_type = type; this->setInitComplete(true); - auto [restricted, restricted_ctx] = fixture->getObjectRestrictedFlags(); - this->setRestricted(restricted); - this->setRestrictedCtx(restricted_ctx); assert(hasValidClassRef()); } diff --git a/src/dbzero/object_model/object/ObjectImplBase.hpp b/src/dbzero/object_model/object/ObjectImplBase.hpp index 14f038e7..9e57ebe1 100644 --- a/src/dbzero/object_model/object/ObjectImplBase.hpp +++ b/src/dbzero/object_model/object/ObjectImplBase.hpp @@ -67,8 +67,8 @@ namespace db0::object_model /** * Construct new Object (uninitialized, without corresponding dbzero instance yet) */ - ObjectImplBase(std::shared_ptr, std::pair restricted_flags = {}); - ObjectImplBase(TypeInitializer &&, std::pair restricted_flags = {}); + ObjectImplBase(std::shared_ptr); + ObjectImplBase(TypeInitializer &&); // Unload from address with a known type (possibly a base type) // NOTE: unload works faster if type_hint is the exact object's type diff --git a/src/dbzero/object_model/object/ObjectInitializer.cpp b/src/dbzero/object_model/object/ObjectInitializer.cpp index e7e5cb8c..d2e41738 100644 --- a/src/dbzero/object_model/object/ObjectInitializer.cpp +++ b/src/dbzero/object_model/object/ObjectInitializer.cpp @@ -85,7 +85,10 @@ namespace db0::object_model } db0::swine_ptr ObjectInitializer::tryGetFixture() const { - return getClass().tryGetFixture(); + if (m_class) { + return m_class->tryGetFixture(); + } + return m_fixture; } std::pair ObjectInitializer::getData(PosVT::Data &data, unsigned int &offset) diff --git a/src/dbzero/workspace/Fixture.hpp b/src/dbzero/workspace/Fixture.hpp index 6f5a848b..f1974630 100644 --- a/src/dbzero/workspace/Fixture.hpp +++ b/src/dbzero/workspace/Fixture.hpp @@ -287,11 +287,6 @@ DB0_PACKED_BEGIN return m_restricted_ctx; } - inline std::pair getObjectRestrictedFlags() const { - auto restricted_ctx = m_restricted_ctx; - return {m_restricted || restricted_ctx, restricted_ctx}; - } - inline void setRestrictedCtx(bool restricted_ctx) { if (restricted_ctx) { Settings::markRestrictedAccessUsed();