diff --git a/src/atomdb/AtomDB.h b/src/atomdb/AtomDB.h index babcc9135..224093896 100644 --- a/src/atomdb/AtomDB.h +++ b/src/atomdb/AtomDB.h @@ -23,6 +23,7 @@ class AtomDB : public HandleDecoder { virtual bool allow_nested_indexing() = 0; virtual bool composite_type_enabled() const = 0; + virtual bool is_protected() const = 0; virtual shared_ptr get_atom(const string& handle) = 0; // HandleDecoder interface virtual shared_ptr get_node(const string& handle) = 0; diff --git a/src/atomdb/AtomDBFactory.cc b/src/atomdb/AtomDBFactory.cc new file mode 100644 index 000000000..337c3e434 --- /dev/null +++ b/src/atomdb/AtomDBFactory.cc @@ -0,0 +1,47 @@ +#include "AtomDBFactory.h" + +#include "InMemoryDB.h" +#include "MorkDB.h" +#include "ProtectedAtomDB.h" +#include "RedisMongoDB.h" +#include "Utils.h" + +using namespace atomdb; +using namespace commons; + +// -------------------------------------------------------------------------------- +// Public methods + +shared_ptr AtomDBFactory::create(const JsonConfig& config, const string& context) { + return wrap_if_protected(create_backend(config, context)); +} + +shared_ptr AtomDBFactory::create_backend(const JsonConfig& config, const string& context) { + auto atomdb_type = config.at_path("type").get_or(""); + + if (atomdb_type == "redismongodb") { + return shared_ptr(new RedisMongoDB(context, false, config)); + } + if (atomdb_type == "morkdb") { + return shared_ptr(new MorkDB(context, config)); + } + if (atomdb_type == "inmemorydb") { + return make_shared(context.empty() ? "inmemorydb_" : context); + } + + RAISE_ERROR("AtomDBFactory: unsupported AtomDB type: " + atomdb_type); + return shared_ptr{}; +} + +shared_ptr AtomDBFactory::wrap_if_protected(shared_ptr backend) { + if (!backend) { + RAISE_ERROR("AtomDBFactory::wrap_if_protected() received null backend"); + } + if (!backend->is_protected()) { + return backend; + } + if (dynamic_pointer_cast(backend)) { + return backend; + } + return shared_ptr(new ProtectedAtomDB(backend)); +} diff --git a/src/atomdb/AtomDBFactory.h b/src/atomdb/AtomDBFactory.h new file mode 100644 index 000000000..f30eeaed3 --- /dev/null +++ b/src/atomdb/AtomDBFactory.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +#include "AtomDB.h" +#include "JsonConfig.h" + +using namespace std; +using namespace commons; + +namespace atomdb { + +/** + * @brief Single entry point to construct concrete AtomDB backends. + * + * Use this instead of calling RedisMongoDB/MorkDB/InMemoryDB constructors directly. + */ +class AtomDBFactory { + public: + /** + * @brief Creates a backend and wraps it with ProtectedAtomDB when is_protected(). + */ + static shared_ptr create(const JsonConfig& config, const string& context = ""); + + /** + * @brief Creates a concrete backend without authorization wrapping. + * + * Supported types: redismongodb, morkdb, inmemorydb. + */ + static shared_ptr create_backend(const JsonConfig& config, const string& context = ""); + + /** + * @brief Wraps backend with ProtectedAtomDB when protected and not already wrapped. + */ + static shared_ptr wrap_if_protected(shared_ptr backend); +}; + +} // namespace atomdb diff --git a/src/atomdb/AtomDBSingleton.cc b/src/atomdb/AtomDBSingleton.cc index 37c85a08a..31fd39c0f 100644 --- a/src/atomdb/AtomDBSingleton.cc +++ b/src/atomdb/AtomDBSingleton.cc @@ -1,8 +1,7 @@ #include "AtomDBSingleton.h" #include "AdapterDB.h" -#include "MorkDB.h" -#include "RedisMongoDB.h" +#include "AtomDBFactory.h" #include "RemoteAtomDB.h" #include "Utils.h" @@ -19,24 +18,25 @@ void AtomDBSingleton::init(const JsonConfig& atomdb_config) { if (AtomDBSingleton::initialized) { RAISE_ERROR( "AtomDBSingleton already initialized. AtomDBSingleton::init() should be called only once."); + } + + shared_ptr atomdb; + auto atomdb_type = atomdb_config.at_path("type").get_or(""); + + if (atomdb_type == "remotedb") { + auto remote_peers_config = + atomdb_config.at_path("remote_peers").get_or(JsonConfig()); + atomdb = shared_ptr(new RemoteAtomDB(remote_peers_config)); + atomdb = AtomDBFactory::wrap_if_protected(atomdb); + } else if (atomdb_type == "adapterdb") { + atomdb = shared_ptr(new AdapterDB(atomdb_config)); + atomdb = AtomDBFactory::wrap_if_protected(atomdb); } else { - auto atomdb_type = atomdb_config.at_path("type").get_or(""); - if (atomdb_type == "morkdb") { - AtomDBSingleton::atom_db = shared_ptr(new MorkDB("", atomdb_config)); - } else if (atomdb_type == "redismongodb") { - AtomDBSingleton::atom_db = shared_ptr(new RedisMongoDB("", false, atomdb_config)); - } else if (atomdb_type == "remotedb") { - auto remote_peers_config = - atomdb_config.at_path("remote_peers").get_or(JsonConfig()); - AtomDBSingleton::atom_db = shared_ptr(new RemoteAtomDB(remote_peers_config)); - } else if (atomdb_type == "adapterdb") { - AtomDBSingleton::atom_db = shared_ptr(new AdapterDB(atomdb_config)); - } else { - RAISE_ERROR("Invalid AtomDB type: " + atomdb_type); - } - - AtomDBSingleton::initialized = true; + atomdb = AtomDBFactory::create(atomdb_config); } + + AtomDBSingleton::atom_db = atomdb; + AtomDBSingleton::initialized = true; } shared_ptr AtomDBSingleton::get_instance() { diff --git a/src/atomdb/BUILD b/src/atomdb/BUILD index 68b602e48..f4c937d43 100644 --- a/src/atomdb/BUILD +++ b/src/atomdb/BUILD @@ -8,9 +8,11 @@ cc_library( deps = [ ":atomdb", ":atomdb_api_types", + ":atomdb_factory", ":atomdb_singleton", ":atomdbutils", "//atomdb/adapterdb:adapterdb_lib", + "//atomdb/auth:protected_atomdb_lib", "//atomdb/inmemorydb:inmemorydb_lib", "//atomdb/morkdb:morkdb_lib", "//atomdb/redis_mongodb:redis_mongodb_lib", @@ -18,6 +20,21 @@ cc_library( ], ) +cc_library( + name = "atomdb_factory", + srcs = ["AtomDBFactory.cc"], + hdrs = ["AtomDBFactory.h"], + includes = ["."], + deps = [ + ":atomdb", + "//atomdb/auth:protected_atomdb_lib", + "//atomdb/inmemorydb", + "//atomdb/morkdb", + "//atomdb/redis_mongodb", + "//commons:commons_lib", + ], +) + cc_library( name = "atomdb", hdrs = ["AtomDB.h"], @@ -56,10 +73,9 @@ cc_library( hdrs = ["AtomDBSingleton.h"], includes = ["."], deps = [ + ":atomdb_factory", "//atomdb:atomdb_api_types", "//atomdb/adapterdb", - "//atomdb/morkdb", - "//atomdb/redis_mongodb", "//atomdb/remotedb:remotedb_lib", "//commons:commons_lib", ], diff --git a/src/atomdb/adapterdb/AdapterDB.cc b/src/atomdb/adapterdb/AdapterDB.cc index 8b8d4916e..4c5855762 100644 --- a/src/atomdb/adapterdb/AdapterDB.cc +++ b/src/atomdb/adapterdb/AdapterDB.cc @@ -3,17 +3,16 @@ #include #include +#include "AtomDBFactory.h" #include "AtomPersister.h" #include "BoundedSharedQueue.h" #include "DatabaseOrchestrator.h" #include "DedicatedThread.h" #include "MongoInitializer.h" -#include "MorkDB.h" #include "MorkMappingStrategy.h" #include "PostgresMappingStrategy.h" #include "PostgresWrapper.h" #include "Processor.h" -#include "RedisMongoDB.h" #include "RemoteAtomDB.h" #include "Utils.h" #include "expression_hasher.h" @@ -72,6 +71,11 @@ bool AdapterDB::composite_type_enabled() const { return this->atomdb_backend->composite_type_enabled(); } +bool AdapterDB::is_protected() const { + this->ensure_backend_ready(); + return this->atomdb_backend->is_protected(); +} + shared_ptr AdapterDB::get_atom(const string& handle) { this->ensure_backend_ready(); return this->atomdb_backend->get_atom(handle); @@ -314,12 +318,12 @@ void AdapterDB::atomdb_backend_setup() { auto atomdb_backend_config = this->config.at_path("adapterdb.atomdb_backend").get_or(JsonConfig()); string atomdb_backend_type = atomdb_backend_config.at_path("type").get_or(""); - if (atomdb_backend_type == "morkdb") { - this->atomdb_backend = shared_ptr(new MorkDB("", atomdb_backend_config)); - } else if (atomdb_backend_type == "redismongodb") { - this->atomdb_backend = shared_ptr(new RedisMongoDB("", false, atomdb_backend_config)); - } else if (atomdb_backend_type == "remotedb") { - this->atomdb_backend = shared_ptr(new RemoteAtomDB(atomdb_backend_config)); + if (atomdb_backend_type == "remotedb") { + this->atomdb_backend = AtomDBFactory::wrap_if_protected( + shared_ptr(new RemoteAtomDB(atomdb_backend_config))); + } else if (atomdb_backend_type == "morkdb" || atomdb_backend_type == "redismongodb" || + atomdb_backend_type == "inmemorydb") { + this->atomdb_backend = AtomDBFactory::create(atomdb_backend_config); } else { RAISE_ERROR("Invalid AtomDB type: " + atomdb_backend_type); } diff --git a/src/atomdb/adapterdb/AdapterDB.h b/src/atomdb/adapterdb/AdapterDB.h index e5dc43104..b23ae8733 100644 --- a/src/atomdb/adapterdb/AdapterDB.h +++ b/src/atomdb/adapterdb/AdapterDB.h @@ -62,6 +62,8 @@ class AdapterDB : public AtomDB { */ bool composite_type_enabled() const override; + bool is_protected() const override; + shared_ptr get_atom(const string& handle) override; shared_ptr get_node(const string& handle) override; shared_ptr get_link(const string& handle) override; diff --git a/src/atomdb/adapterdb/BUILD b/src/atomdb/adapterdb/BUILD index 39534f411..ccce32157 100644 --- a/src/atomdb/adapterdb/BUILD +++ b/src/atomdb/adapterdb/BUILD @@ -17,8 +17,7 @@ cc_library( includes = ["."], deps = [ "//atomdb", - "//atomdb/morkdb", - "//atomdb/redis_mongodb", + "//atomdb:atomdb_factory", "//atomdb/remotedb:remotedb_lib", "//commons:commons_lib", "//commons/atoms:atoms_lib", diff --git a/src/atomdb/auth/BUILD b/src/atomdb/auth/BUILD new file mode 100644 index 000000000..38928d78c --- /dev/null +++ b/src/atomdb/auth/BUILD @@ -0,0 +1,24 @@ +load("@rules_cc//cc:cc_library.bzl", "cc_library") + +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "protected_atomdb_lib", + includes = ["."], + deps = [ + ":protected_atomdb", + ], +) + +cc_library( + name = "protected_atomdb", + srcs = ["ProtectedAtomDB.cc"], + hdrs = ["ProtectedAtomDB.h"], + includes = ["."], + deps = [ + "//atomdb", + "//atomdb:atomdb_api_types", + "//commons:commons_lib", + "//commons/atoms:atoms_lib", + ], +) diff --git a/src/atomdb/auth/ProtectedAtomDB.cc b/src/atomdb/auth/ProtectedAtomDB.cc new file mode 100644 index 000000000..e82c220fc --- /dev/null +++ b/src/atomdb/auth/ProtectedAtomDB.cc @@ -0,0 +1,294 @@ +#include "ProtectedAtomDB.h" + +#include "Utils.h" + +using namespace atomdb; +using namespace commons; + +// -------------------------------------------------------------------------------- +// Constructors and destructors + +ProtectedAtomDB::ProtectedAtomDB(shared_ptr backend) : backend(std::move(backend)) { + if (this->backend == nullptr) { + RAISE_ERROR("ProtectedAtomDB requires a non-null backend AtomDB"); + } +} + +// -------------------------------------------------------------------------------- +// Public methods + +shared_ptr ProtectedAtomDB::get_atom(const string& handle, const string& public_key) { + RAISE_ERROR("ProtectedAtomDB::get_atom(handle, public_key) is not implemented yet"); +} + +shared_ptr ProtectedAtomDB::get_node(const string& handle, const string& public_key) { + RAISE_ERROR("ProtectedAtomDB::get_node(handle, public_key) is not implemented yet"); +} + +shared_ptr ProtectedAtomDB::get_link(const string& handle, const string& public_key) { + RAISE_ERROR("ProtectedAtomDB::get_link(handle, public_key) is not implemented yet"); +} + +vector> ProtectedAtomDB::get_matching_atoms(bool is_toplevel, + Atom& key, + const string& public_key) { + RAISE_ERROR("ProtectedAtomDB::get_matching_atoms(..., public_key) is not implemented yet"); +} + +shared_ptr ProtectedAtomDB::query_for_pattern(const LinkSchema& link_schema, + const string& public_key) { + RAISE_ERROR("ProtectedAtomDB::query_for_pattern(link_schema, public_key) is not implemented yet"); +} + +shared_ptr ProtectedAtomDB::query_for_targets(const string& handle, + const string& public_key) { + RAISE_ERROR("ProtectedAtomDB::query_for_targets(handle, public_key) is not implemented yet"); +} + +shared_ptr ProtectedAtomDB::query_for_incoming_set( + const string& handle, const string& public_key) { + RAISE_ERROR("ProtectedAtomDB::query_for_incoming_set(handle, public_key) is not implemented yet"); +} + +bool ProtectedAtomDB::atom_exists(const string& handle, const string& public_key) { + RAISE_ERROR("ProtectedAtomDB::atom_exists(handle, public_key) is not implemented yet"); +} + +bool ProtectedAtomDB::node_exists(const string& handle, const string& public_key) { + RAISE_ERROR("ProtectedAtomDB::node_exists(handle, public_key) is not implemented yet"); +} + +bool ProtectedAtomDB::link_exists(const string& handle, const string& public_key) { + RAISE_ERROR("ProtectedAtomDB::link_exists(handle, public_key) is not implemented yet"); +} + +set ProtectedAtomDB::atoms_exist(const vector& handles, const string& public_key) { + RAISE_ERROR("ProtectedAtomDB::atoms_exist(handles, public_key) is not implemented yet"); +} + +set ProtectedAtomDB::nodes_exist(const vector& handles, const string& public_key) { + RAISE_ERROR("ProtectedAtomDB::nodes_exist(handles, public_key) is not implemented yet"); +} + +set ProtectedAtomDB::links_exist(const vector& handles, const string& public_key) { + RAISE_ERROR("ProtectedAtomDB::links_exist(handles, public_key) is not implemented yet"); +} + +string ProtectedAtomDB::add_atom(const atoms::Atom* atom, + const string& public_key, + const atoms::Merger* merger) { + RAISE_ERROR("ProtectedAtomDB::add_atom(atom, public_key) is not implemented yet"); +} + +string ProtectedAtomDB::add_node(const atoms::Node* node, + const string& public_key, + const atoms::Merger* merger) { + RAISE_ERROR("ProtectedAtomDB::add_node(node, public_key) is not implemented yet"); +} + +string ProtectedAtomDB::add_link(const atoms::Link* link, + const string& public_key, + const atoms::Merger* merger) { + RAISE_ERROR("ProtectedAtomDB::add_link(link, public_key) is not implemented yet"); +} + +vector ProtectedAtomDB::add_atoms(const vector& atom_list, + const string& public_key, + bool is_transactional, + const atoms::Merger* merger) { + RAISE_ERROR("ProtectedAtomDB::add_atoms(atom_list, public_key) is not implemented yet"); +} + +vector ProtectedAtomDB::add_nodes(const vector& nodes, + const string& public_key, + bool is_transactional, + const atoms::Merger* merger) { + RAISE_ERROR("ProtectedAtomDB::add_nodes(nodes, public_key) is not implemented yet"); +} + +vector ProtectedAtomDB::add_links(const vector& links, + const string& public_key, + bool is_transactional, + const atoms::Merger* merger) { + RAISE_ERROR("ProtectedAtomDB::add_links(links, public_key) is not implemented yet"); +} + +bool ProtectedAtomDB::delete_atom(const string& handle, + const string& public_key, + bool delete_link_targets) { + RAISE_ERROR("ProtectedAtomDB::delete_atom(handle, public_key) is not implemented yet"); +} + +bool ProtectedAtomDB::delete_node(const string& handle, + const string& public_key, + bool delete_link_targets) { + RAISE_ERROR("ProtectedAtomDB::delete_node(handle, public_key) is not implemented yet"); +} + +bool ProtectedAtomDB::delete_link(const string& handle, + const string& public_key, + bool delete_link_targets) { + RAISE_ERROR("ProtectedAtomDB::delete_link(handle, public_key) is not implemented yet"); +} + +uint ProtectedAtomDB::delete_atoms(const vector& handles, + const string& public_key, + bool delete_link_targets) { + RAISE_ERROR("ProtectedAtomDB::delete_atoms(handles, public_key) is not implemented yet"); +} + +uint ProtectedAtomDB::delete_nodes(const vector& handles, + const string& public_key, + bool delete_link_targets) { + RAISE_ERROR("ProtectedAtomDB::delete_nodes(handles, public_key) is not implemented yet"); +} + +uint ProtectedAtomDB::delete_links(const vector& handles, + const string& public_key, + bool delete_link_targets) { + RAISE_ERROR("ProtectedAtomDB::delete_links(handles, public_key) is not implemented yet"); +} + +void ProtectedAtomDB::re_index_patterns(const string& public_key, bool flush_patterns) { + RAISE_ERROR("ProtectedAtomDB::re_index_patterns(public_key) is not implemented yet"); +} + +size_t ProtectedAtomDB::node_count(const string& public_key) const { + RAISE_ERROR("ProtectedAtomDB::node_count(public_key) is not implemented yet"); +} + +size_t ProtectedAtomDB::link_count(const string& public_key) const { + RAISE_ERROR("ProtectedAtomDB::link_count(public_key) is not implemented yet"); +} + +size_t ProtectedAtomDB::atom_count(const string& public_key) const { + RAISE_ERROR("ProtectedAtomDB::atom_count(public_key) is not implemented yet"); +} + +bool ProtectedAtomDB::allow_nested_indexing() { return this->backend->allow_nested_indexing(); } + +bool ProtectedAtomDB::composite_type_enabled() const { return this->backend->composite_type_enabled(); } + +bool ProtectedAtomDB::is_protected() const { return true; } + +// -------------------------------------------------------------------------------- +// Public methods (without public_key - reject the call) + +shared_ptr ProtectedAtomDB::get_atom(const string& handle) { + raise_public_key_required("get_atom"); +} + +shared_ptr ProtectedAtomDB::get_node(const string& handle) { + raise_public_key_required("get_node"); +} + +shared_ptr ProtectedAtomDB::get_link(const string& handle) { + raise_public_key_required("get_link"); +} + +vector> ProtectedAtomDB::get_matching_atoms(bool is_toplevel, Atom& key) { + raise_public_key_required("get_matching_atoms"); +} + +shared_ptr ProtectedAtomDB::query_for_pattern( + const LinkSchema& link_schema) { + raise_public_key_required("query_for_pattern"); +} + +shared_ptr ProtectedAtomDB::query_for_targets(const string& handle) { + raise_public_key_required("query_for_targets"); +} + +shared_ptr ProtectedAtomDB::query_for_incoming_set(const string& handle) { + raise_public_key_required("query_for_incoming_set"); +} + +bool ProtectedAtomDB::atom_exists(const string& handle) { raise_public_key_required("atom_exists"); } + +bool ProtectedAtomDB::node_exists(const string& handle) { raise_public_key_required("node_exists"); } + +bool ProtectedAtomDB::link_exists(const string& handle) { raise_public_key_required("link_exists"); } + +set ProtectedAtomDB::atoms_exist(const vector& handles) { + raise_public_key_required("atoms_exist"); +} + +set ProtectedAtomDB::nodes_exist(const vector& handles) { + raise_public_key_required("nodes_exist"); +} + +set ProtectedAtomDB::links_exist(const vector& handles) { + raise_public_key_required("links_exist"); +} + +string ProtectedAtomDB::add_atom(const atoms::Atom* atom, const atoms::Merger* merger) { + raise_public_key_required("add_atom"); +} + +string ProtectedAtomDB::add_node(const atoms::Node* node, const atoms::Merger* merger) { + raise_public_key_required("add_node"); +} + +string ProtectedAtomDB::add_link(const atoms::Link* link, const atoms::Merger* merger) { + raise_public_key_required("add_link"); +} + +vector ProtectedAtomDB::add_atoms(const vector& atom_list, + bool is_transactional, + const atoms::Merger* merger) { + raise_public_key_required("add_atoms"); +} + +vector ProtectedAtomDB::add_nodes(const vector& nodes, + bool is_transactional, + const atoms::Merger* merger) { + raise_public_key_required("add_nodes"); +} + +vector ProtectedAtomDB::add_links(const vector& links, + bool is_transactional, + const atoms::Merger* merger) { + raise_public_key_required("add_links"); +} + +bool ProtectedAtomDB::delete_atom(const string& handle, bool delete_link_targets) { + raise_public_key_required("delete_atom"); +} + +bool ProtectedAtomDB::delete_node(const string& handle, bool delete_link_targets) { + raise_public_key_required("delete_node"); +} + +bool ProtectedAtomDB::delete_link(const string& handle, bool delete_link_targets) { + raise_public_key_required("delete_link"); +} + +uint ProtectedAtomDB::delete_atoms(const vector& handles, bool delete_link_targets) { + raise_public_key_required("delete_atoms"); +} + +uint ProtectedAtomDB::delete_nodes(const vector& handles, bool delete_link_targets) { + raise_public_key_required("delete_nodes"); +} + +uint ProtectedAtomDB::delete_links(const vector& handles, bool delete_link_targets) { + raise_public_key_required("delete_links"); +} + +void ProtectedAtomDB::re_index_patterns(bool flush_patterns) { + raise_public_key_required("re_index_patterns"); +} + +size_t ProtectedAtomDB::node_count() const { raise_public_key_required("node_count"); } + +size_t ProtectedAtomDB::link_count() const { raise_public_key_required("link_count"); } + +size_t ProtectedAtomDB::atom_count() const { raise_public_key_required("atom_count"); } + +// -------------------------------------------------------------------------------- +// Private methods + +void ProtectedAtomDB::raise_public_key_required(const string& method_name) { + RAISE_ERROR("ProtectedAtomDB::" + method_name + + "() is unavailable without a public_key; use the overload that accepts a public_key"); +} diff --git a/src/atomdb/auth/ProtectedAtomDB.h b/src/atomdb/auth/ProtectedAtomDB.h new file mode 100644 index 000000000..456ecb64b --- /dev/null +++ b/src/atomdb/auth/ProtectedAtomDB.h @@ -0,0 +1,157 @@ +#pragma once + +#include +#include +#include +#include + +#include "AtomDB.h" + +using namespace std; +using namespace atoms; + +namespace atomdb { + +/** + * @brief Authorization wrapper around any AtomDB backend for protected databases. + * + * Data-access methods expose two forms: + * - overloads without public_key: reject the call (protected access requires a key) + * - overloads with public_key: authorize and delegate to the backend + * + */ +class ProtectedAtomDB : public AtomDB { + public: + /** + * @param backend Shared concrete AtomDB to wrap. + */ + explicit ProtectedAtomDB(shared_ptr backend); + + bool allow_nested_indexing() override; + bool composite_type_enabled() const override; + bool is_protected() const override; + + shared_ptr get_atom(const string& handle) override; + shared_ptr get_atom(const string& handle, const string& public_key); + + shared_ptr get_node(const string& handle) override; + shared_ptr get_node(const string& handle, const string& public_key); + + shared_ptr get_link(const string& handle) override; + shared_ptr get_link(const string& handle, const string& public_key); + + vector> get_matching_atoms(bool is_toplevel, Atom& key) override; + vector> get_matching_atoms(bool is_toplevel, Atom& key, const string& public_key); + + shared_ptr query_for_pattern(const LinkSchema& link_schema) override; + shared_ptr query_for_pattern(const LinkSchema& link_schema, + const string& public_key); + + shared_ptr query_for_targets(const string& handle) override; + shared_ptr query_for_targets(const string& handle, + const string& public_key); + + shared_ptr query_for_incoming_set(const string& handle) override; + shared_ptr query_for_incoming_set(const string& handle, + const string& public_key); + + bool atom_exists(const string& handle) override; + bool atom_exists(const string& handle, const string& public_key); + + bool node_exists(const string& handle) override; + bool node_exists(const string& handle, const string& public_key); + + bool link_exists(const string& handle) override; + bool link_exists(const string& handle, const string& public_key); + + set atoms_exist(const vector& handles) override; + set atoms_exist(const vector& handles, const string& public_key); + + set nodes_exist(const vector& handles) override; + set nodes_exist(const vector& handles, const string& public_key); + + set links_exist(const vector& handles) override; + set links_exist(const vector& handles, const string& public_key); + + string add_atom(const atoms::Atom* atom, const atoms::Merger* merger = NULL) override; + string add_atom(const atoms::Atom* atom, + const string& public_key, + const atoms::Merger* merger = NULL); + + string add_node(const atoms::Node* node, const atoms::Merger* merger = NULL) override; + string add_node(const atoms::Node* node, + const string& public_key, + const atoms::Merger* merger = NULL); + + string add_link(const atoms::Link* link, const atoms::Merger* merger = NULL) override; + string add_link(const atoms::Link* link, + const string& public_key, + const atoms::Merger* merger = NULL); + + vector add_atoms(const vector& atom_list, + bool is_transactional = false, + const atoms::Merger* merger = NULL) override; + vector add_atoms(const vector& atom_list, + const string& public_key, + bool is_transactional = false, + const atoms::Merger* merger = NULL); + + vector add_nodes(const vector& nodes, + bool is_transactional = false, + const atoms::Merger* merger = NULL) override; + vector add_nodes(const vector& nodes, + const string& public_key, + bool is_transactional = false, + const atoms::Merger* merger = NULL); + + vector add_links(const vector& links, + bool is_transactional = false, + const atoms::Merger* merger = NULL) override; + vector add_links(const vector& links, + const string& public_key, + bool is_transactional = false, + const atoms::Merger* merger = NULL); + + bool delete_atom(const string& handle, bool delete_link_targets = false) override; + bool delete_atom(const string& handle, const string& public_key, bool delete_link_targets = false); + + bool delete_node(const string& handle, bool delete_link_targets = false) override; + bool delete_node(const string& handle, const string& public_key, bool delete_link_targets = false); + + bool delete_link(const string& handle, bool delete_link_targets = false) override; + bool delete_link(const string& handle, const string& public_key, bool delete_link_targets = false); + + uint delete_atoms(const vector& handles, bool delete_link_targets = false) override; + uint delete_atoms(const vector& handles, + const string& public_key, + bool delete_link_targets = false); + + uint delete_nodes(const vector& handles, bool delete_link_targets = false) override; + uint delete_nodes(const vector& handles, + const string& public_key, + bool delete_link_targets = false); + + uint delete_links(const vector& handles, bool delete_link_targets = false) override; + uint delete_links(const vector& handles, + const string& public_key, + bool delete_link_targets = false); + + void re_index_patterns(bool flush_patterns = true) override; + void re_index_patterns(const string& public_key, bool flush_patterns = true); + + size_t node_count() const override; + size_t node_count(const string& public_key) const; + + size_t link_count() const override; + size_t link_count(const string& public_key) const; + + size_t atom_count() const override; + size_t atom_count(const string& public_key) const; + + private: + shared_ptr backend; + + [[noreturn]] static void raise_public_key_required(const string& method_name); +}; + +} // namespace atomdb diff --git a/src/atomdb/inmemorydb/InMemoryDB.h b/src/atomdb/inmemorydb/InMemoryDB.h index 174734432..4563605cc 100644 --- a/src/atomdb/inmemorydb/InMemoryDB.h +++ b/src/atomdb/inmemorydb/InMemoryDB.h @@ -23,6 +23,7 @@ class InMemoryDB : public AtomDB { bool allow_nested_indexing() override; bool composite_type_enabled() const override { return false; } + bool is_protected() const override { return false; } shared_ptr get_atom(const string& handle) override; shared_ptr get_node(const string& handle) override; diff --git a/src/atomdb/redis_mongodb/RedisMongoDB.cc b/src/atomdb/redis_mongodb/RedisMongoDB.cc index 80e653ea0..f2ce4c6b4 100644 --- a/src/atomdb/redis_mongodb/RedisMongoDB.cc +++ b/src/atomdb/redis_mongodb/RedisMongoDB.cc @@ -31,6 +31,7 @@ uint RedisMongoDB::REDIS_CHUNK_SIZE; string RedisMongoDB::MONGODB_DB_NAME; string RedisMongoDB::MONGODB_NODES_COLLECTION_NAME; string RedisMongoDB::MONGODB_LINKS_COLLECTION_NAME; +string RedisMongoDB::MONGODB_CONFIG_COLLECTION_NAME; string RedisMongoDB::MONGODB_PATTERN_INDEX_SCHEMA_COLLECTION_NAME; string RedisMongoDB::MONGODB_FIELD_NAME[MONGODB_FIELD::size]; uint RedisMongoDB::MONGODB_CHUNK_SIZE; @@ -55,6 +56,8 @@ RedisMongoDB::~RedisMongoDB() { bool RedisMongoDB::allow_nested_indexing() { return false; } +bool RedisMongoDB::is_protected() const { return this->protected_flag; } + void RedisMongoDB::redis_setup(const JsonConfig& config) { if (skip_redis_) return; @@ -105,6 +108,7 @@ void RedisMongoDB::mongodb_setup(const JsonConfig& config) { bsoncxx::builder::basic::make_document(bsoncxx::builder::basic::kvp("ping", 1)); mongodb.run_command(ping_cmd.view()); LOG_INFO("Connected to MongoDB at " << address); + load_protected_flag(); } catch (const std::exception& e) { RAISE_ERROR(e.what()); } @@ -1230,6 +1234,14 @@ void RedisMongoDB::add_pattern_index_schema(const string& tokens, this->pattern_index_schema_next_priority++; } +void RedisMongoDB::load_protected_flag() { + auto conn = this->mongodb_pool->acquire(); + auto config_collection = (*conn)[MONGODB_DB_NAME][MONGODB_CONFIG_COLLECTION_NAME]; + auto config_doc = config_collection.find_one( + bsoncxx::builder::basic::make_document(bsoncxx::builder::basic::kvp("protected", true))); + this->protected_flag = static_cast(config_doc); +} + void RedisMongoDB::load_pattern_index_schema() { this->pattern_index_schema_map.clear(); auto conn = this->mongodb_pool->acquire(); diff --git a/src/atomdb/redis_mongodb/RedisMongoDB.h b/src/atomdb/redis_mongodb/RedisMongoDB.h index b90664bf3..03cc68ecc 100644 --- a/src/atomdb/redis_mongodb/RedisMongoDB.h +++ b/src/atomdb/redis_mongodb/RedisMongoDB.h @@ -28,11 +28,11 @@ enum MONGODB_FIELD { ID = 0, NAME, TARGETS, NAMED_TYPE, size }; class RedisMongoDB : public AtomDB { public: - RedisMongoDB(const string& context, bool skip_redis, const JsonConfig& config); ~RedisMongoDB(); bool allow_nested_indexing() override; bool composite_type_enabled() const override { return this->composite_type_enabled_; } + bool is_protected() const override; static string REDIS_PATTERNS_PREFIX; static string REDIS_OUTGOING_PREFIX; @@ -41,6 +41,7 @@ class RedisMongoDB : public AtomDB { static string MONGODB_DB_NAME; static string MONGODB_NODES_COLLECTION_NAME; static string MONGODB_LINKS_COLLECTION_NAME; + static string MONGODB_CONFIG_COLLECTION_NAME; static string MONGODB_PATTERN_INDEX_SCHEMA_COLLECTION_NAME; static string MONGODB_FIELD_NAME[MONGODB_FIELD::size]; static uint MONGODB_CHUNK_SIZE; @@ -53,6 +54,7 @@ class RedisMongoDB : public AtomDB { MONGODB_DB_NAME = context + "das"; MONGODB_NODES_COLLECTION_NAME = context + "nodes"; MONGODB_LINKS_COLLECTION_NAME = context + "links"; + MONGODB_CONFIG_COLLECTION_NAME = context + "config"; MONGODB_PATTERN_INDEX_SCHEMA_COLLECTION_NAME = context + "pattern_index_schema"; MONGODB_FIELD_NAME[MONGODB_FIELD::ID] = "_id"; MONGODB_FIELD_NAME[MONGODB_FIELD::TARGETS] = "targets"; @@ -146,10 +148,16 @@ class RedisMongoDB : public AtomDB { map>& composite_type_entries_map); private: + friend class AtomDBFactory; + friend class MorkDB; + + RedisMongoDB(const string& context, bool skip_redis, const JsonConfig& config); + string context; bool skip_redis_; bool composite_type_enabled_; bool cluster_flag; + bool protected_flag; RedisContextPool* redis_pool; mongocxx::pool* mongodb_pool; atomic patterns_next_score{0}; @@ -192,6 +200,7 @@ class RedisMongoDB : public AtomDB { void update_incoming_set(const string& key, const string& value); void load_pattern_index_schema(); + void load_protected_flag(); vector match_pattern_index_schema(const Link* link); vector> index_entries_combinations(unsigned int arity); diff --git a/src/atomdb/remotedb/BUILD b/src/atomdb/remotedb/BUILD index b556984b1..1addcd6e9 100644 --- a/src/atomdb/remotedb/BUILD +++ b/src/atomdb/remotedb/BUILD @@ -24,10 +24,8 @@ cc_library( deps = [ "//atomdb", "//atomdb:atomdb_api_types", - "//atomdb/inmemorydb", + "//atomdb:atomdb_factory", "//atomdb/inmemorydb:inmemorydb_api_types", - "//atomdb/morkdb:morkdb_lib", - "//atomdb/redis_mongodb:redis_mongodb_lib", "//commons:commons_lib", "//commons/atoms:atoms_lib", "//commons/processor:processor_lib", diff --git a/src/atomdb/remotedb/RemoteAtomDB.cc b/src/atomdb/remotedb/RemoteAtomDB.cc index 1e4806613..7ca59f09f 100644 --- a/src/atomdb/remotedb/RemoteAtomDB.cc +++ b/src/atomdb/remotedb/RemoteAtomDB.cc @@ -7,11 +7,9 @@ #include #include -#include "InMemoryDB.h" +#include "AtomDBFactory.h" #include "InMemoryDBAPITypes.h" #include "Logger.h" -#include "MorkDB.h" -#include "RedisMongoDB.h" #include "Utils.h" using namespace atomdb; @@ -20,48 +18,29 @@ using namespace commons; using json = nlohmann::json; -namespace { - -shared_ptr create_atomdb_from_config(const JsonConfig& config) { - string uid = config.at_path("uid").get_or(""); - string type = config.at_path("type").get_or(""); - string context = config.at_path("context").get_or(""); - - if (type == "inmemorydb") { - return make_shared(context.empty() ? "remotedb_" : context); - } - - if (type == "redismongodb") { - RedisMongoDB::initialize_statics(context); - auto atomdb = make_shared(context, false, config); - return atomdb; - } - - if (type == "morkdb") { - auto atomdb = make_shared(context, config); - return atomdb; - } - - RAISE_ERROR("Unknown AtomDB type for peer " + uid + ": " + type); - return nullptr; -} - -} // namespace - RemoteAtomDB::RemoteAtomDB(const JsonConfig& peers_config) { for (auto& entry : peers_config) { auto peer_config = JsonConfig(entry); string uid = peer_config.at_path("uid").get_or(""); if (uid.empty()) continue; + string context = peer_config.at_path("context").get_or(""); + if (context.empty()) { + context = "remotedb_"; + } + shared_ptr local_persistence = nullptr; auto local_persistence_config = peer_config.at_path("local_persistence").get_or(JsonConfig()); if (!local_persistence_config.empty()) { - local_persistence = create_atomdb_from_config(local_persistence_config); + string local_context = local_persistence_config.at_path("context").get_or(context); + if (local_context.empty()) { + local_context = context; + } + local_persistence = AtomDBFactory::create(local_persistence_config, local_context); } remote_db_[uid] = make_shared( - create_atomdb_from_config(peer_config), local_persistence, uid); + AtomDBFactory::create(peer_config, context), local_persistence, uid); } LOG_INFO("RemoteAtomDB initialized with " << remote_db_.size() << " remote peers"); @@ -87,6 +66,15 @@ bool RemoteAtomDB::composite_type_enabled() const { return false; } +bool RemoteAtomDB::is_protected() const { + for (auto& [uid, peer] : remote_db_) { + if (peer->is_protected()) { + return true; + } + } + return false; +} + void RemoteAtomDB::derive_nested_indexing() { // Derive the aggregated nested-indexing capability from the peers. A single global boolean // cannot describe a heterogeneous result set, so mixed configurations are normalized to the diff --git a/src/atomdb/remotedb/RemoteAtomDB.h b/src/atomdb/remotedb/RemoteAtomDB.h index d1de2218f..e2912c933 100644 --- a/src/atomdb/remotedb/RemoteAtomDB.h +++ b/src/atomdb/remotedb/RemoteAtomDB.h @@ -29,6 +29,7 @@ class RemoteAtomDB : public AtomDB { bool allow_nested_indexing() override; bool composite_type_enabled() const override; + bool is_protected() const override; shared_ptr get_atom(const string& handle) override; shared_ptr get_node(const string& handle) override; diff --git a/src/atomdb/remotedb/RemoteAtomDBPeer.cc b/src/atomdb/remotedb/RemoteAtomDBPeer.cc index 669d8f272..05a041007 100644 --- a/src/atomdb/remotedb/RemoteAtomDBPeer.cc +++ b/src/atomdb/remotedb/RemoteAtomDBPeer.cc @@ -40,6 +40,10 @@ bool RemoteAtomDBPeer::composite_type_enabled() const { return local_persistence_ && local_persistence_->composite_type_enabled(); } +bool RemoteAtomDBPeer::is_protected() const { + return (local_persistence_ && local_persistence_->is_protected()) || + (atomdb_ && atomdb_->is_protected()); +} shared_ptr RemoteAtomDBPeer::get_atom(const string& handle) { auto atom = cache_.get_atom(handle); if (atom) return atom; diff --git a/src/atomdb/remotedb/RemoteAtomDBPeer.h b/src/atomdb/remotedb/RemoteAtomDBPeer.h index 713c498dc..9305329a6 100644 --- a/src/atomdb/remotedb/RemoteAtomDBPeer.h +++ b/src/atomdb/remotedb/RemoteAtomDBPeer.h @@ -30,6 +30,7 @@ class RemoteAtomDBPeer : public AtomDB, public processor::ThreadMethod { bool allow_nested_indexing() override; bool composite_type_enabled() const override; + bool is_protected() const override; shared_ptr get_atom(const string& handle) override; shared_ptr get_node(const string& handle) override; diff --git a/src/main/BUILD b/src/main/BUILD index 028948672..a67e259f4 100644 --- a/src/main/BUILD +++ b/src/main/BUILD @@ -58,9 +58,9 @@ cc_library( srcs = ["db_loader.cc"], deps = [ "//atomdb:atomdb_api_types", + "//atomdb:atomdb_factory", "//atomdb:atomdb_singleton", "//atomdb/adapterdb:adapterdb_lib", - "//atomdb/morkdb:morkdb_lib", "//atomdb/redis_mongodb:redis_mongodb_lib", "//atomdb/remotedb:remotedb_lib", "//commons:commons_lib", diff --git a/src/main/db_loader.cc b/src/main/db_loader.cc index 761cc3135..9c5fb9295 100644 --- a/src/main/db_loader.cc +++ b/src/main/db_loader.cc @@ -8,12 +8,12 @@ #include #include "AdapterDB.h" +#include "AtomDBFactory.h" #include "AtomDBSingleton.h" #include "JsonConfig.h" #include "JsonConfigParser.h" #include "MettaParser.h" #include "MettaParserActions.h" -#include "MorkDB.h" #include "RedisMongoDB.h" #include "RemoteAtomDB.h" #include "Utils.h" @@ -73,18 +73,16 @@ int main(int argc, char* argv[]) { auto atomdb_config = json_config.at_path("atomdb").get_or(JsonConfig()); auto atomdb_type = atomdb_config.at_path("type").get_or(""); - if (atomdb_type == "redismongodb") { - AtomDBSingleton::provide(make_shared(context, false, atomdb_config)); - } else if (atomdb_type == "morkdb") { - AtomDBSingleton::provide(make_shared(context, atomdb_config)); - } else if (atomdb_type == "remotedb") { + if (atomdb_type == "remotedb") { auto remote_peers_config = atomdb_config.at_path("remote_peers").get_or(JsonConfig()); - AtomDBSingleton::provide(make_shared(remote_peers_config)); + AtomDBSingleton::provide( + AtomDBFactory::wrap_if_protected(shared_ptr(new RemoteAtomDB(remote_peers_config)))); } else if (atomdb_type == "adapterdb") { - AtomDBSingleton::provide(make_shared(atomdb_config)); + AtomDBSingleton::provide( + AtomDBFactory::wrap_if_protected(shared_ptr(new AdapterDB(atomdb_config)))); } else { - RAISE_ERROR("Invalid AtomDB type: " + atomdb_type); + AtomDBSingleton::provide(AtomDBFactory::create(atomdb_config, context)); } signal(SIGINT, &ctrl_c_handler); diff --git a/src/tests/benchmark/atomdb/atomdb_main.cc b/src/tests/benchmark/atomdb/atomdb_main.cc index 4d5de5a86..2a8d66278 100644 --- a/src/tests/benchmark/atomdb/atomdb_main.cc +++ b/src/tests/benchmark/atomdb/atomdb_main.cc @@ -12,8 +12,8 @@ #include #include "AtomDB.h" +#include "AtomDBFactory.h" #include "JsonConfig.h" -#include "MorkDB.h" #include "RedisMongoDB.h" #include "Utils.h" #include "atomdb_operations.h" @@ -52,13 +52,9 @@ map global_metrics; void setup() { RedisMongoDB::initialize_statics(); } shared_ptr factory_create_atomdb(string type, const JsonConfig& atomdb_config) { - if (type == "redismongodb") { - return make_shared("", false, atomdb_config); - } else if (type == "morkdb") { - return make_shared("", atomdb_config); - } else { - RAISE_ERROR("Unknown AtomDB type: " + type); - } + JsonConfig config = atomdb_config; + config["type"] = type; + return AtomDBFactory::create_backend(config); } int main(int argc, char** argv) { diff --git a/src/tests/cpp/BUILD b/src/tests/cpp/BUILD index 8d7a3ae74..e94cab3ff 100644 --- a/src/tests/cpp/BUILD +++ b/src/tests/cpp/BUILD @@ -785,7 +785,9 @@ cc_test( ], linkstatic = 1, deps = [ + "//atomdb:atomdb_factory", "//atomdb:atomdb_singleton", + "//atomdb/redis_mongodb", "//tests/cpp/test_commons:mock_animals_data_lib", "//tests/cpp/test_commons:test_atomdb_json_config", "@com_github_google_googletest//:gtest_main", @@ -814,7 +816,9 @@ cc_test( ], linkstatic = 1, deps = [ + "//atomdb:atomdb_factory", "//atomdb:atomdb_singleton", + "//atomdb/redis_mongodb", "//tests/cpp/test_commons:mock_animals_data_lib", "//tests/cpp/test_commons:test_atomdb_json_config", "@com_github_google_googletest//:gtest_main", @@ -863,6 +867,59 @@ cc_test( ], ) +cc_test( + name = "atomdb_factory_test", + size = "small", + srcs = ["atomdb_factory_test.cc"], + copts = [ + "-Iexternal/gtest/googletest/include", + "-Iexternal/gtest/googletest", + ], + linkopts = [ + "-L/usr/local/lib", + "-lhiredis_cluster", + "-lhiredis", + "-lmongocxx", + "-lbsoncxx", + ], + linkstatic = 1, + deps = [ + "//atomdb:atomdb_factory", + "//atomdb/auth:protected_atomdb_lib", + "//atomdb/inmemorydb:inmemorydb_lib", + "//tests/cpp/test_commons/mocks:mock_atom_db_lib", + "@com_github_google_googletest//:gtest_main", + "@mbedtls", + ], +) + +cc_test( + name = "atomdb_protection_test", + size = "small", + srcs = ["atomdb_protection_test.cc"], + copts = [ + "-Iexternal/gtest/googletest/include", + "-Iexternal/gtest/googletest", + ], + linkopts = [ + "-L/usr/local/lib", + "-lhiredis_cluster", + "-lhiredis", + "-lmongocxx", + "-lbsoncxx", + ], + linkstatic = 1, + deps = [ + "//atomdb:atomdb_factory", + "//atomdb:atomdb_singleton", + "//atomdb/auth:protected_atomdb_lib", + "//atomdb/inmemorydb:inmemorydb_lib", + "//tests/cpp/test_commons/mocks:mock_atom_db_lib", + "@com_github_google_googletest//:gtest_main", + "@mbedtls", + ], +) + cc_test( name = "atomdbutils_test", size = "small", @@ -908,9 +965,13 @@ cc_test( ], linkstatic = 1, deps = [ + "//atomdb:atomdb_factory", "//atomdb/inmemorydb:inmemorydb_lib", + "//atomdb/redis_mongodb", "//atomdb/remotedb:remotedb_lib", "//commons/atoms:atoms_lib", + "//tests/cpp/test_commons:test_atomdb_json_config", + "//tests/cpp/test_commons/mocks:mock_atom_db_lib", "@com_github_google_googletest//:gtest_main", ], ) @@ -1035,9 +1096,12 @@ cc_test( ], linkstatic = 1, deps = [ + "//atomdb:atomdb_factory", "//atomdb:atomdb_singleton", + "//atomdb/redis_mongodb", "//db_adapter:db_adapter_lib", "//tests/cpp/test_commons:test_atomdb_json_config", + "//tests/cpp/test_commons/mocks:mock_atom_db_lib", "@com_github_google_googletest//:gtest_main", "@mbedtls", ], diff --git a/src/tests/cpp/adapterdb_test.cc b/src/tests/cpp/adapterdb_test.cc index c1c1f97e1..0cea2c3a9 100644 --- a/src/tests/cpp/adapterdb_test.cc +++ b/src/tests/cpp/adapterdb_test.cc @@ -10,9 +10,11 @@ #include #include +#include "AtomDBFactory.h" #include "AtomDBSingleton.h" #include "Link.h" #include "Merger.h" +#include "MockAtomDB.h" #include "MorkDB.h" #include "Node.h" #include "RedisMongoDB.h" @@ -25,6 +27,7 @@ using namespace std; using namespace atomdb; using namespace atoms; using namespace commons; +using ::testing::Return; struct AdapterTestParams { string adapter_type; @@ -56,7 +59,9 @@ class AdapterDBTestBase : public ::testing::Test { shared_ptr backend; void SetUpBackend() { - backend = make_shared("adapter_test", false, test_atomdb_json_config()); + backend = dynamic_pointer_cast( + AtomDBFactory::create_backend(test_atomdb_json_config(), "adapter_test")); + ASSERT_NE(backend, nullptr); } JsonConfig build_adapter_config(const string& mapping_path, @@ -213,6 +218,19 @@ TEST_P(AdapterDBTest, ConstructorSucceedsWithValidConfig) { EXPECT_GT(db->atom_count(), 0); } +TEST_P(AdapterDBTest, IsProtectedDelegatesToBackend) { + ASSERT_NE(create_current_adapter(), nullptr); + + auto mock_backend = make_shared(); + EXPECT_CALL(*mock_backend, is_protected()).WillRepeatedly(Return(true)); + + const AdapterTestParams& p = GetParam(); + auto config = build_adapter_config(mapping_file_path, p.adapter_type, p.db_credentials); + auto db = make_shared(config, mock_backend); + ASSERT_NE(db, nullptr); + EXPECT_TRUE(db->is_protected()); +} + TEST_P(AdapterDBTest, ConstructorLoadsDataIntoBackendOnFirstRun) { auto db = create_current_adapter(); ASSERT_NE(db, nullptr); diff --git a/src/tests/cpp/atomdb_factory_test.cc b/src/tests/cpp/atomdb_factory_test.cc new file mode 100644 index 000000000..fcb118a7e --- /dev/null +++ b/src/tests/cpp/atomdb_factory_test.cc @@ -0,0 +1,79 @@ +#include + +#include +#include + +#include "AtomDBFactory.h" +#include "InMemoryDB.h" +#include "JsonConfig.h" +#include "MockAtomDB.h" +#include "ProtectedAtomDB.h" + +using namespace atomdb; +using namespace commons; +using namespace std; +using ::testing::Return; + +namespace { + +JsonConfig config_with_type(const string& type) { + JsonConfig config; + config["type"] = type; + return config; +} + +} // namespace + +TEST(AtomDBFactoryTest, CreateBackendInMemoryDB) { + auto backend = AtomDBFactory::create_backend(config_with_type("inmemorydb"), "factory_test_"); + ASSERT_NE(backend, nullptr); + EXPECT_NE(dynamic_pointer_cast(backend), nullptr); + EXPECT_EQ(dynamic_pointer_cast(backend), nullptr); + EXPECT_FALSE(backend->is_protected()); +} + +TEST(AtomDBFactoryTest, CreateInMemoryDBDoesNotWrap) { + auto db = AtomDBFactory::create(config_with_type("inmemorydb"), "factory_test_"); + ASSERT_NE(db, nullptr); + EXPECT_NE(dynamic_pointer_cast(db), nullptr); + EXPECT_EQ(dynamic_pointer_cast(db), nullptr); +} + +TEST(AtomDBFactoryTest, CreateBackendRejectsMissingAndUnknownTypes) { + EXPECT_THROW(AtomDBFactory::create_backend(JsonConfig()), runtime_error); + EXPECT_THROW(AtomDBFactory::create_backend(config_with_type("")), runtime_error); + EXPECT_THROW(AtomDBFactory::create_backend(config_with_type("unknown")), runtime_error); + EXPECT_THROW(AtomDBFactory::create_backend(config_with_type("remotedb")), runtime_error); + EXPECT_THROW(AtomDBFactory::create_backend(config_with_type("adapterdb")), runtime_error); +} + +TEST(AtomDBFactoryTest, WrapIfProtectedNullThrows) { + EXPECT_THROW(AtomDBFactory::wrap_if_protected(nullptr), runtime_error); +} + +TEST(AtomDBFactoryTest, WrapIfProtectedUnprotectedReturnsSameInstance) { + auto backend = make_shared(); + EXPECT_CALL(*backend, is_protected()).WillRepeatedly(Return(false)); + + auto wrapped = AtomDBFactory::wrap_if_protected(backend); + EXPECT_EQ(wrapped.get(), backend.get()); +} + +TEST(AtomDBFactoryTest, WrapIfProtectedWrapsOnce) { + auto backend = make_shared(); + EXPECT_CALL(*backend, is_protected()).WillRepeatedly(Return(true)); + EXPECT_CALL(*backend, allow_nested_indexing()).WillRepeatedly(Return(true)); + EXPECT_CALL(*backend, composite_type_enabled()).WillRepeatedly(Return(false)); + + auto wrapped = AtomDBFactory::wrap_if_protected(backend); + ASSERT_NE(wrapped, nullptr); + EXPECT_NE(wrapped.get(), backend.get()); + EXPECT_NE(dynamic_pointer_cast(wrapped), nullptr); + EXPECT_TRUE(wrapped->is_protected()); + // ProtectedAtomDB must keep advertising the backend's capabilities. + EXPECT_TRUE(wrapped->allow_nested_indexing()); + EXPECT_FALSE(wrapped->composite_type_enabled()); + + auto wrapped_again = AtomDBFactory::wrap_if_protected(wrapped); + EXPECT_EQ(wrapped_again.get(), wrapped.get()); +} diff --git a/src/tests/cpp/atomdb_protection_test.cc b/src/tests/cpp/atomdb_protection_test.cc new file mode 100644 index 000000000..d874c87f7 --- /dev/null +++ b/src/tests/cpp/atomdb_protection_test.cc @@ -0,0 +1,62 @@ +#include + +#include + +#include "AtomDBFactory.h" +#include "AtomDBSingleton.h" +#include "InMemoryDB.h" +#include "MockAtomDB.h" +#include "ProtectedAtomDB.h" + +using namespace atomdb; +using namespace std; +using ::testing::Return; + +namespace { + +void reset_singleton() { AtomDBSingleton::provide(make_shared("protection_test_")); } + +} // namespace + +class AtomDBSingletonProtectionTest : public ::testing::Test { + protected: + void TearDown() override { reset_singleton(); } +}; + +TEST_F(AtomDBSingletonProtectionTest, WrapIfProtectedThenProvide) { + auto backend = make_shared(); + EXPECT_CALL(*backend, is_protected()).WillRepeatedly(Return(true)); + + AtomDBSingleton::provide(AtomDBFactory::wrap_if_protected(backend)); + auto instance = AtomDBSingleton::get_instance(); + ASSERT_NE(dynamic_pointer_cast(instance), nullptr); + EXPECT_TRUE(instance->is_protected()); +} + +TEST_F(AtomDBSingletonProtectionTest, ProvideUnprotectedKeepsBackendUnwrapped) { + auto backend = make_shared(); + EXPECT_CALL(*backend, is_protected()).WillRepeatedly(Return(false)); + + AtomDBSingleton::provide(AtomDBFactory::wrap_if_protected(backend)); + auto instance = AtomDBSingleton::get_instance(); + EXPECT_EQ(instance.get(), backend.get()); + EXPECT_EQ(dynamic_pointer_cast(instance), nullptr); +} + +TEST(ProtectedAtomDBAccess, KeylessAccessRequiresPublicKey) { + auto backend = make_shared(); + auto db = make_shared(backend); + + EXPECT_THROW(db->get_atom("handle"), runtime_error); + EXPECT_THROW(db->atom_exists("handle"), runtime_error); + EXPECT_THROW(db->node_count(), runtime_error); +} + +TEST(ProtectedAtomDBAccess, AuthorizedOverloadsNotImplementedYet) { + auto backend = make_shared(); + auto db = make_shared(backend); + + EXPECT_THROW(db->get_atom("handle", "public_key"), runtime_error); + EXPECT_THROW(db->atom_exists("handle", "public_key"), runtime_error); + EXPECT_THROW(db->node_count("public_key"), runtime_error); +} diff --git a/src/tests/cpp/inmemorydb_test.cc b/src/tests/cpp/inmemorydb_test.cc index 99c865c8e..8ffaa82bd 100644 --- a/src/tests/cpp/inmemorydb_test.cc +++ b/src/tests/cpp/inmemorydb_test.cc @@ -31,6 +31,8 @@ class InMemoryDBTest : public ::testing::Test { shared_ptr db; }; +TEST_F(InMemoryDBTest, IsProtectedAlwaysFalse) { EXPECT_FALSE(db->is_protected()); } + TEST_F(InMemoryDBTest, AddNodesAndLinks) { auto human = new Node("Symbol", "\"human\""); auto monkey = new Node("Symbol", "\"monkey\""); diff --git a/src/tests/cpp/redis_mongodb_test.cc b/src/tests/cpp/redis_mongodb_test.cc index dc6170bb3..e8417e02b 100644 --- a/src/tests/cpp/redis_mongodb_test.cc +++ b/src/tests/cpp/redis_mongodb_test.cc @@ -1,6 +1,8 @@ #include #include +#include +#include #include #include #include @@ -8,6 +10,7 @@ #include #include "Atom.h" +#include "AtomDBFactory.h" #include "AtomDBSingleton.h" #include "Hasher.h" #include "Link.h" @@ -37,8 +40,8 @@ class MockDecoder : public HandleDecoder { class RedisMongoDBTestEnvironment : public ::testing::Environment { public: void SetUp() override { - auto atomdb = new RedisMongoDB("test_", false, test_atomdb_json_config()); - AtomDBSingleton::provide(shared_ptr(atomdb)); + auto atomdb = AtomDBFactory::create_backend(test_atomdb_json_config(), "test_"); + AtomDBSingleton::provide(atomdb); load_animals_data(); } @@ -1190,7 +1193,9 @@ TEST_F(RedisMongoDBTest, CompositeTypeEnabledFlag) { auto config_default = test_atomdb_json_config(); config_default.erase("composite_type_enabled"); - auto db_default = make_shared("test_", false, config_default); + auto db_default = + dynamic_pointer_cast(AtomDBFactory::create_backend(config_default, "test_")); + ASSERT_NE(db_default, nullptr); EXPECT_TRUE(db_default->composite_type_enabled()); vector enabled_nodes = {new Node("Symbol", "CompositeTypeEnabled-A"), @@ -1212,7 +1217,9 @@ TEST_F(RedisMongoDBTest, CompositeTypeEnabledFlag) { auto config_disabled = test_atomdb_json_config(); config_disabled["composite_type_enabled"] = false; - auto db_disabled = make_shared("test_", false, config_disabled); + auto db_disabled = + dynamic_pointer_cast(AtomDBFactory::create_backend(config_disabled, "test_")); + ASSERT_NE(db_disabled, nullptr); EXPECT_FALSE(db_disabled->composite_type_enabled()); vector disabled_nodes = {new Node("Symbol", "CompositeTypeDisabled-A"), @@ -1347,6 +1354,32 @@ TEST_F(RedisMongoDBTest, TransactionalRejectedMergeStillBooksCompositeType) { delete nested; } +TEST_F(RedisMongoDBTest, LoadProtectedFlagFromConfigCollection) { + using bsoncxx::builder::basic::kvp; + using bsoncxx::builder::basic::make_document; + + auto conn = db->get_mongo_pool()->acquire(); + auto config_collection = + (*conn)[RedisMongoDB::MONGODB_DB_NAME][RedisMongoDB::MONGODB_CONFIG_COLLECTION_NAME]; + config_collection.delete_many({}); + + auto recreate = []() { + return dynamic_pointer_cast( + AtomDBFactory::create_backend(test_atomdb_json_config(), "test_")); + }; + + EXPECT_FALSE(recreate()->is_protected()); + + config_collection.insert_one(make_document(kvp("protected", true))); + EXPECT_TRUE(recreate()->is_protected()); + + config_collection.delete_many({}); + config_collection.insert_one(make_document(kvp("protected", false))); + EXPECT_FALSE(recreate()->is_protected()); + + config_collection.delete_many({}); +} + int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); ::testing::AddGlobalTestEnvironment(new RedisMongoDBTestEnvironment()); diff --git a/src/tests/cpp/redis_mongodb_test_2.cc b/src/tests/cpp/redis_mongodb_test_2.cc index 75f5abee4..95893120b 100644 --- a/src/tests/cpp/redis_mongodb_test_2.cc +++ b/src/tests/cpp/redis_mongodb_test_2.cc @@ -7,6 +7,7 @@ #include #include +#include "AtomDBFactory.h" #include "AtomDBSingleton.h" #include "Hasher.h" #include "Link.h" @@ -22,9 +23,11 @@ using namespace std; class RedisMongoDBTestEnvironment : public ::testing::Environment { public: void SetUp() override { - auto atomdb = new RedisMongoDB("test2_", false, test_atomdb_json_config()); - atomdb->drop_all(); - AtomDBSingleton::provide(shared_ptr(atomdb)); + auto atomdb = AtomDBFactory::create_backend(test_atomdb_json_config(), "test2_"); + auto db = dynamic_pointer_cast(atomdb); + ASSERT_NE(db, nullptr); + db->drop_all(); + AtomDBSingleton::provide(atomdb); } void TearDown() override { diff --git a/src/tests/cpp/remote_atomdb_test.cc b/src/tests/cpp/remote_atomdb_test.cc index 454ee8c15..601a38019 100644 --- a/src/tests/cpp/remote_atomdb_test.cc +++ b/src/tests/cpp/remote_atomdb_test.cc @@ -1,6 +1,8 @@ #include #include +#include +#include #include #include #include @@ -9,20 +11,25 @@ #include #include "Assignment.h" +#include "AtomDBFactory.h" #include "InMemoryDB.h" #include "InMemoryDBAPITypes.h" #include "JsonConfig.h" #include "Link.h" #include "LinkSchema.h" +#include "MockAtomDB.h" #include "Node.h" +#include "RedisMongoDB.h" #include "RemoteAtomDB.h" #include "RemoteAtomDBPeer.h" +#include "TestAtomDBJsonConfig.h" using namespace atomdb; using namespace atomdb::atomdb_api_types; using namespace atoms; using namespace commons; using namespace std; +using ::testing::Return; // ============================================================================= // RemoteAtomDBPeer tests - peer with InMemoryDB as both remote and local @@ -712,6 +719,148 @@ TEST(RemoteAtomDBFederationTest, CacheFirstProbingAcrossPeers) { EXPECT_EQ(db->get_atom("ffffffffffffffffffffffffffffffff"), nullptr); } +namespace { + +shared_ptr make_protection_mock(bool protected_flag) { + auto mock = make_shared(); + EXPECT_CALL(*mock, is_protected()).WillRepeatedly(Return(protected_flag)); + EXPECT_CALL(*mock, allow_nested_indexing()).WillRepeatedly(Return(false)); + EXPECT_CALL(*mock, composite_type_enabled()).WillRepeatedly(Return(false)); + return mock; +} + +nlohmann::json redis_mongodb_fields() { + return { + {"type", "redismongodb"}, + {"redis", {{"endpoint", "localhost:40020"}, {"cluster", false}}}, + {"mongodb", {{"endpoint", "localhost:40021"}, {"username", "admin"}, {"password", "admin"}}}}; +} + +void seed_protected_flag(const string& context, bool protected_value) { + using bsoncxx::builder::basic::kvp; + using bsoncxx::builder::basic::make_document; + + auto seeder = dynamic_pointer_cast( + AtomDBFactory::create_backend(test_atomdb_json_config(), context)); + ASSERT_NE(seeder, nullptr); + auto conn = seeder->get_mongo_pool()->acquire(); + auto config_collection = + (*conn)[RedisMongoDB::MONGODB_DB_NAME][RedisMongoDB::MONGODB_CONFIG_COLLECTION_NAME]; + config_collection.delete_many({}); + if (protected_value) { + config_collection.insert_one(make_document(kvp("protected", true))); + } +} + +string seed_redis_node(const string& context, const string& node_name) { + auto seeder = dynamic_pointer_cast( + AtomDBFactory::create_backend(test_atomdb_json_config(), context)); + EXPECT_NE(seeder, nullptr); + if (seeder == nullptr) { + return ""; + } + auto node = new Node("Symbol", node_name); + string handle = seeder->add_node(node); + delete node; + return handle; +} + +void drop_redis_context(const string& context) { + auto cleanup = dynamic_pointer_cast( + AtomDBFactory::create_backend(test_atomdb_json_config(), context)); + ASSERT_NE(cleanup, nullptr); + cleanup->drop_all(); +} + +} // namespace + +TEST(RemoteAtomDBPeerIsProtected, OrAcrossRemoteAndLocal) { + EXPECT_FALSE( + RemoteAtomDBPeer(make_protection_mock(false), make_protection_mock(false), "p").is_protected()); + EXPECT_TRUE( + RemoteAtomDBPeer(make_protection_mock(true), make_protection_mock(false), "p").is_protected()); + EXPECT_TRUE( + RemoteAtomDBPeer(make_protection_mock(false), make_protection_mock(true), "p").is_protected()); +} + +TEST(RemoteAtomDBIsProtected, AggregatesPeers) { + EXPECT_FALSE(RemoteAtomDB(map>{}).is_protected()); + + map> unprotected; + unprotected["p1"] = + make_shared(make_protection_mock(false), make_protection_mock(false), "p1"); + EXPECT_FALSE(RemoteAtomDB(unprotected).is_protected()); + + map> mixed = unprotected; + mixed["p2"] = + make_shared(make_protection_mock(true), make_protection_mock(false), "p2"); + EXPECT_TRUE(RemoteAtomDB(mixed).is_protected()); +} + +TEST(RemoteAtomDBFactoryConstruction, EmptyLocalPersistenceContextFallsBackToPeerContext) { + const string peer_context = "remote_fallback_ctx_"; + string handle = seed_redis_node(peer_context, "\"fallback_routed\""); + ASSERT_FALSE(handle.empty()); + + nlohmann::json peer_json = {{"uid", "fallback_peer"}, + {"type", "inmemorydb"}, + {"context", peer_context}, + {"local_persistence", redis_mongodb_fields()}}; + peer_json["local_persistence"]["context"] = ""; + + RemoteAtomDB db(JsonConfig(nlohmann::json::array({peer_json}))); + auto* peer = db.get_peer("fallback_peer"); + ASSERT_NE(peer, nullptr); + EXPECT_FALSE(peer->is_readonly()); + EXPECT_EQ(RedisMongoDB::MONGODB_DB_NAME, peer_context + "das"); + EXPECT_EQ(RedisMongoDB::MONGODB_CONFIG_COLLECTION_NAME, peer_context + "config"); + + EXPECT_EQ(peer->get_cached_atom(handle), nullptr); + auto got = db.get_atom(handle); + ASSERT_NE(got, nullptr); + EXPECT_EQ(got->handle(), handle); + + drop_redis_context(peer_context); +} + +TEST(RemoteAtomDBFactoryConstruction, ProtectedPeerRoutesThroughLocalPersistence) { + const string peer_context = "remote_factory_prot_"; + seed_protected_flag(peer_context, true); + + auto peer_json = redis_mongodb_fields(); + peer_json["uid"] = "protected_peer"; + peer_json["context"] = peer_context; + peer_json["local_persistence"] = {{"type", "inmemorydb"}, {"context", ""}}; + + RemoteAtomDB factory_db(JsonConfig(nlohmann::json::array({peer_json}))); + auto* factory_peer = factory_db.get_peer("protected_peer"); + ASSERT_NE(factory_peer, nullptr); + EXPECT_FALSE(factory_peer->is_readonly()); + EXPECT_TRUE(factory_peer->is_protected()); + EXPECT_TRUE(factory_db.is_protected()); + + auto remote = make_protection_mock(true); + EXPECT_CALL(*remote, get_atom(testing::_)).Times(0); + auto local = make_shared("prot_route_local_"); + auto node = new Node("Symbol", "\"factory_routed\""); + string handle = local->add_node(node); + delete node; + ASSERT_FALSE(handle.empty()); + + map> peers; + peers["protected_peer"] = + make_shared(AtomDBFactory::wrap_if_protected(remote), local, "protected_peer"); + RemoteAtomDB db(peers); + auto* peer = db.get_peer("protected_peer"); + ASSERT_NE(peer, nullptr); + EXPECT_EQ(peer->get_cached_atom(handle), nullptr); + auto got = db.get_atom(handle); + ASSERT_NE(got, nullptr); + EXPECT_EQ(got->handle(), handle); + + drop_redis_context(peer_context); +} + int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); diff --git a/src/tests/cpp/test_commons/mocks/MockAtomDB.h b/src/tests/cpp/test_commons/mocks/MockAtomDB.h index 85c82d1c8..10dfd0d27 100644 --- a/src/tests/cpp/test_commons/mocks/MockAtomDB.h +++ b/src/tests/cpp/test_commons/mocks/MockAtomDB.h @@ -25,6 +25,7 @@ class AtomDBMock : public AtomDB { public: MOCK_METHOD(bool, allow_nested_indexing, (), (override)); MOCK_METHOD(bool, composite_type_enabled, (), (const, override)); + MOCK_METHOD(bool, is_protected, (), (const, override)); MOCK_METHOD(shared_ptr, get_atom, (const string& handle), (override)); MOCK_METHOD(shared_ptr, get_node, (const string& handle), (override)); MOCK_METHOD(shared_ptr, get_link, (const string& handle), (override));