From 31181e18ba0ee537194879477e8bd3e35be34940 Mon Sep 17 00:00:00 2001 From: marcocapozzoli Date: Wed, 12 Aug 2026 09:49:42 -0300 Subject: [PATCH 01/16] WIP --- src/atomdb/auth/AuthorizationEntry.cc | 41 +++++ src/atomdb/auth/AuthorizationEntry.h | 50 ++++++ src/atomdb/auth/AuthorizationManagement.cc | 121 ++++++++++++++ src/atomdb/auth/AuthorizationManagement.h | 88 ++++++++++ src/atomdb/auth/AuthorizationManifest.cc | 94 +++++++++++ src/atomdb/auth/AuthorizationManifest.h | 59 +++++++ src/atomdb/auth/AuthorizationPersistence.h | 26 +++ src/atomdb/auth/BUILD | 54 +++++++ .../auth/MongoAuthorizationPersistence.cc | 153 ++++++++++++++++++ .../auth/MongoAuthorizationPersistence.h | 40 +++++ 10 files changed, 726 insertions(+) create mode 100644 src/atomdb/auth/AuthorizationEntry.cc create mode 100644 src/atomdb/auth/AuthorizationEntry.h create mode 100644 src/atomdb/auth/AuthorizationManagement.cc create mode 100644 src/atomdb/auth/AuthorizationManagement.h create mode 100644 src/atomdb/auth/AuthorizationManifest.cc create mode 100644 src/atomdb/auth/AuthorizationManifest.h create mode 100644 src/atomdb/auth/AuthorizationPersistence.h create mode 100644 src/atomdb/auth/BUILD create mode 100644 src/atomdb/auth/MongoAuthorizationPersistence.cc create mode 100644 src/atomdb/auth/MongoAuthorizationPersistence.h diff --git a/src/atomdb/auth/AuthorizationEntry.cc b/src/atomdb/auth/AuthorizationEntry.cc new file mode 100644 index 00000000..44bd6775 --- /dev/null +++ b/src/atomdb/auth/AuthorizationEntry.cc @@ -0,0 +1,41 @@ +#include "AuthorizationEntry.h" + +using namespace atomdb; + +// -------------------------------------------------------------------------------- +// Constructors + +AuthorizationEntry::AuthorizationEntry(const LinkSchema& schema, bool read, bool write) + : schema(schema), read(read), write(write) {} + +AuthorizationEntry::AuthorizationEntry(const vector& tokens, bool read, bool write) + : schema(tokens), read(read), write(write) {} + +// -------------------------------------------------------------------------------- +// Public methods + +string AuthorizationEntry::handle() const { return this->schema.handle(); } + +const LinkSchema& atomdb::AuthorizationEntry::schema() const { return this->schema; } + +bool AuthorizationEntry::allows(AuthorizationOperation operation) const { + switch (operation) { + case AuthorizationOperation::READ: + return this->read; + case AuthorizationOperation::WRITE: + return this->write; + } + return false; +} + +string AuthorizationEntry::to_string() const { + return "AuthorizationEntry(handle: '" + this->handle() + + "', read: " + (this->read ? "true" : "false") + + ", write: " + (this->write ? "true" : "false") + ", schema: " + this->schema.to_string() + + ")"; +} + +vector AuthorizationEntry::tokenize() const { + LinkSchema schema_copy = this->schema; + return schema_copy.tokenize(); +} diff --git a/src/atomdb/auth/AuthorizationEntry.h b/src/atomdb/auth/AuthorizationEntry.h new file mode 100644 index 00000000..ab8a3225 --- /dev/null +++ b/src/atomdb/auth/AuthorizationEntry.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include + +#include "LinkSchema.h" + +using namespace std; +using namespace atoms; + +namespace atomdb { + +enum class AuthorizationOperation { READ, WRITE }; + +/** + * @brief One LinkSchema entry with independent read/write flags. + * + * Maps to one MongoDB allowed_schemas item. handle() is the LinkSchema handle, computed the same + * way an Atom handle is, and it is what identifies this entry in AuthorizationManagement::revoke(). + */ +class AuthorizationEntry { + public: + /** @brief Builds an entry from a LinkSchema. */ + AuthorizationEntry(const LinkSchema& schema, bool read, bool write); + + /** @brief Builds an entry from the tokens stored in MongoDB. */ + AuthorizationEntry(const vector& tokens, bool read, bool write); + + /** @brief Handle of the underlying LinkSchema. */ + string handle() const; + + /** @brief Returns the underlying LinkSchema. */ + const LinkSchema& schema() const; + + /** @brief Returns true if this entry allows the given operation. */ + bool allows(AuthorizationOperation operation) const; + + /** @brief Returns a string representation of the entry. */ + string to_string() const; + + /** @brief Returns the tokens of the underlying LinkSchema. */ + vector tokenize() const; + + private: + LinkSchema schema; + bool read; + bool write; +}; + +} // namespace atomdb diff --git a/src/atomdb/auth/AuthorizationManagement.cc b/src/atomdb/auth/AuthorizationManagement.cc new file mode 100644 index 00000000..bc541e31 --- /dev/null +++ b/src/atomdb/auth/AuthorizationManagement.cc @@ -0,0 +1,121 @@ +#include "AuthorizationManagement.h" + +#include "Assignment.h" +#include "AtomDB.h" +#include "Link.h" +#include "Utils.h" + +using namespace atomdb; +using namespace atoms; +using namespace commons; + +// -------------------------------------------------------------------------------- +// Constructors + +AuthorizationManagement::AuthorizationManagement(shared_ptr atomdb, + shared_ptr persistence) + : atomdb(std::move(atomdb)), persistence(std::move(persistence)) { + if (this->atomdb == nullptr) { + RAISE_ERROR("AuthorizationManagement requires a non-null atomdb AtomDB"); + } + // Bootstrap from AtomDB::get_access_permissions() once that API exists on AtomDB. + // Until then the in-RAM manifest starts empty and is filled only by authorize(). +} + +// -------------------------------------------------------------------------------- +// Public methods + +bool AuthorizationManagement::has_full_access(const string& public_key) { + return this->manifest.is_registered(public_key) && this->manifest.full_access(public_key); +} + +bool AuthorizationManagement::is_authorized(const Atom& atom, + const string& public_key, + AuthorizationOperation operation) { + if (!this->manifest.is_registered(public_key)) { + return false; + } + if (this->manifest.full_access(public_key)) { + return true; + } + + HandleDecoder& decoder = *this->atomdb; + for (const auto& entry : this->manifest.entries(public_key)) { + if (entry.allows(operation) && this->matches_entry(entry, atom, decoder)) { + return true; + } + } + return false; +} + +bool AuthorizationManagement::is_authorized(const string& handle, + const string& public_key, + AuthorizationOperation operation, + HandleDecoder& decoder) { + if (!this->manifest.is_registered(public_key)) { + return false; + } + if (this->manifest.full_access(public_key)) { + return true; + } + + for (const auto& entry : this->manifest.entries(public_key)) { + if (entry.allows(operation) && this->matches_entry(entry, handle, decoder)) { + return true; + } + } + return false; +} + +void AuthorizationManagement::authorize(const string& public_key, const AuthorizationEntry& entry) { + if (this->persistence == nullptr) { + RAISE_ERROR( + "AuthorizationManagement::authorize() requires AuthorizationPersistence; " + "this atomdb has no authorization storage"); + } + this->persistence->save(public_key, entry); + this->manifest.add(public_key, entry); +} + +void AuthorizationManagement::revoke(const string& public_key, const string& handle) { + if (this->persistence == nullptr) { + RAISE_ERROR( + "AuthorizationManagement::revoke() requires AuthorizationPersistence; " + "this atomdb has no authorization storage"); + } + this->persistence->remove(public_key, handle); + this->manifest.remove(public_key, handle); +} + +void AuthorizationManagement::revoke_all(const string& public_key) { + if (this->persistence == nullptr) { + RAISE_ERROR( + "AuthorizationManagement::revoke_all() requires AuthorizationPersistence; " + "this atomdb has no authorization storage"); + } + this->persistence->remove_all(public_key); + this->manifest.remove_all(public_key); +} + +// -------------------------------------------------------------------------------- +// Private methods + +bool AuthorizationManagement::matches_entry(const AuthorizationEntry& entry, + const Atom& atom, + HandleDecoder& decoder) const { + Assignment assignment; + LinkSchema schema = entry.schema(); + // Prefer matching against the in-memory atom so WRITE checks work for atoms not yet stored. + if (Atom::is_link(atom)) { + return schema.match(const_cast(static_cast(atom)), assignment, decoder); + } + return schema.match(atom.handle(), assignment, decoder); +} + +bool AuthorizationManagement::matches_entry(const AuthorizationEntry& entry, + const string& handle, + HandleDecoder& decoder) const { + Assignment assignment; + LinkSchema schema = entry.schema(); + return schema.match(handle, assignment, decoder); +} diff --git a/src/atomdb/auth/AuthorizationManagement.h b/src/atomdb/auth/AuthorizationManagement.h new file mode 100644 index 00000000..4e085c8e --- /dev/null +++ b/src/atomdb/auth/AuthorizationManagement.h @@ -0,0 +1,88 @@ +#pragma once + +#include +#include + +#include "Atom.h" +#include "AuthorizationEntry.h" +#include "AuthorizationManifest.h" +#include "AuthorizationPersistence.h" +#include "HandleDecoder.h" + +using namespace std; +using namespace atoms; + +namespace atomdb { + +class AtomDB; + +/** + * @brief Authorization queries and administration. + * + * The manifest is loaded once in the constructor and kept in RAM; authorize() and revoke*() update both + * the storage (through AuthorizationPersistence) and the in-RAM manifest, so no lookup ever hits the + * database. + */ +class AuthorizationManagement { + public: + /** + * @brief Builds the in-RAM AuthorizationManifest. + * + * @param atomdb AtomDB used to read access_permissions documents once + * AtomDB::get_access_permissions() is available. Until then the manifest starts empty. + * @param persistence Storage used by authorize() and revoke*(). May be null when the atomdb + * has no authorization storage; administration then fails. + */ + AuthorizationManagement(shared_ptr atomdb, shared_ptr persistence); + + /** + * @brief Returns true when public_key is registered with full_access. + */ + bool has_full_access(const string& public_key); + + /** + * @brief Checks whether public_key may perform operation on atom. + * + * @return true if full_access or the atom matches at least one entry allowing operation. + */ + bool is_authorized(const Atom& atom, const string& public_key, AuthorizationOperation operation); + + /** + * @brief Checks whether public_key may perform operation on handle. + * + * @param decoder HandleDecoder from the atomdb (required by LinkSchema::match). + */ + bool is_authorized(const string& handle, + const string& public_key, + AuthorizationOperation operation, + HandleDecoder& decoder); + + /** + * @brief Grants one entry to public_key. Updates storage and the in-RAM manifest. + */ + void authorize(const string& public_key, const AuthorizationEntry& entry); + + /** + * @brief Revokes one entry from public_key. + * + * @param handle AuthorizationEntry::handle(), i.e. the LinkSchema handle. + */ + void revoke(const string& public_key, const string& handle); + + /** + * @brief Revokes every entry of public_key. Updates storage and the in-RAM manifest. + */ + void revoke_all(const string& public_key); + + private: + shared_ptr atomdb; + shared_ptr persistence; + AuthorizationManifest manifest; + + bool matches_entry(const AuthorizationEntry& entry, const Atom& atom, HandleDecoder& decoder) const; + bool matches_entry(const AuthorizationEntry& entry, + const string& handle, + HandleDecoder& decoder) const; +}; + +} // namespace atomdb diff --git a/src/atomdb/auth/AuthorizationManifest.cc b/src/atomdb/auth/AuthorizationManifest.cc new file mode 100644 index 00000000..65cfc883 --- /dev/null +++ b/src/atomdb/auth/AuthorizationManifest.cc @@ -0,0 +1,94 @@ +#include "AuthorizationManifest.h" + +#include + +using namespace atomdb; + +const vector AuthorizationManifest::EMPTY_ENTRIES; + +// -------------------------------------------------------------------------------- +// Public methods + +void AuthorizationManifest::set(const Document& document) { + this->documents[document.public_key] = document; +} + +void AuthorizationManifest::add(const string& public_key, const AuthorizationEntry& entry) { + Document* document = this->find_document(public_key, "add"); + + if (document == nullptr) { + this->create_document(public_key, entry); + return; + } + + string entry_handle = entry.handle(); + vector& entries = document->entries; + + for (auto& existing : entries) { + if (existing.handle() == entry_handle) { + existing = entry; + return; + } + } + + entries.push_back(entry); +} + +void AuthorizationManifest::remove(const string& public_key, const string& handle) { + Document* document = this->find_document(public_key, "remove"); + + if (document == nullptr) return; + + vector& entries = document->entries; + + for (auto it = entries.begin(); it != entries.end(); ++it) { + if (it->handle() == handle) { + entries.erase(it); + return; + } + } +} + +void AuthorizationManifest::remove_all(const string& public_key) { this->documents.erase(public_key); } + +bool AuthorizationManifest::is_registered(const string& public_key) const { + return this->documents.find(public_key) != this->documents.end(); +} + +bool AuthorizationManifest::full_access(const string& public_key) { + Document* document = this->find_document(public_key, "full_access"); + if (document == nullptr) { + return false; + } + return document->full_access; +} + +const vector& AuthorizationManifest::entries(const string& public_key) { + Document* document = this->find_document(public_key, "entries"); + if (document == nullptr) { + return EMPTY_ENTRIES; + } + return document->entries; +} + +// -------------------------------------------------------------------------------- +// Private methods + +void AuthorizationManifest::create_document(const string& public_key, const AuthorizationEntry& entry) { + Document document; + document.public_key = public_key; + document.full_access = false; + document.entries.push_back(entry); + this->documents[public_key] = document; +} + +AuthorizationManifest::Document* AuthorizationManifest::find_document(const string& public_key, + const string& caller) { + auto it = this->documents.find(public_key); + if (it == this->documents.end()) { + LOG_INFO("AuthorizationManifest::" + caller + + "() called for unregistered public_key: " + public_key); + return nullptr; + } + return &it->second; +} diff --git a/src/atomdb/auth/AuthorizationManifest.h b/src/atomdb/auth/AuthorizationManifest.h new file mode 100644 index 00000000..d4730b53 --- /dev/null +++ b/src/atomdb/auth/AuthorizationManifest.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include + +#include "AuthorizationEntry.h" + +using namespace std; + +namespace atomdb { + +/** + * @brief In-RAM image of the whole access_permissions collection. + * + * AuthorizationManagement keeps exactly one of these. Document maps 1:1 to one MongoDB document. + */ +class AuthorizationManifest { + public: + /** @brief One access_permissions document. */ + class Document { + public: + string public_key; + bool full_access = false; + vector entries; + }; + + AuthorizationManifest() = default; + + /** @brief Replaces (or inserts) the document for document.public_key. */ + void set(const Document& document); + + /** @brief Adds one entry to public_key, creating the document if needed. */ + void add(const string& public_key, const AuthorizationEntry& entry); + + /** @brief Removes the entry whose handle() == handle from public_key. No-op if absent. */ + void remove(const string& public_key, const string& handle); + + /** @brief Removes the whole document for public_key. No-op if not registered. */ + void remove_all(const string& public_key); + + /** @brief Returns true if public_key is registered. */ + bool is_registered(const string& public_key) const; + + /** @brief Returns true if public_key is registered with full_access. */ + bool full_access(const string& public_key); + + /** @brief Returns the entries for public_key, or an empty vector if not registered. */ + const vector& entries(const string& public_key); + + private: + map documents; + static const vector EMPTY_ENTRIES; + + void create_document(const string& public_key, const AuthorizationEntry& entry); + Document* find_document(const string& public_key, const string& caller); +}; + +} // namespace atomdb diff --git a/src/atomdb/auth/AuthorizationPersistence.h b/src/atomdb/auth/AuthorizationPersistence.h new file mode 100644 index 00000000..e84bd5a6 --- /dev/null +++ b/src/atomdb/auth/AuthorizationPersistence.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include "AuthorizationEntry.h" + +using namespace std; + +namespace atomdb { + +/** @brief Storage interface for authorization writes. */ +class AuthorizationPersistence { + public: + virtual ~AuthorizationPersistence() = default; + + /** @brief Persists one entry under public_key (creating the document if needed). */ + virtual void save(const string& public_key, const AuthorizationEntry& entry) = 0; + + /** @brief Removes the entry identified by handle from public_key's document. */ + virtual void remove(const string& public_key, const string& handle) = 0; + + /** @brief Removes the whole document for public_key. */ + virtual void remove_all(const string& public_key) = 0; +}; + +} // namespace atomdb diff --git a/src/atomdb/auth/BUILD b/src/atomdb/auth/BUILD new file mode 100644 index 00000000..1e20ce49 --- /dev/null +++ b/src/atomdb/auth/BUILD @@ -0,0 +1,54 @@ +load("@rules_cc//cc:cc_library.bzl", "cc_library") + +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "authorization_types", + srcs = [ + "AuthorizationEntry.cc", + "AuthorizationManifest.cc", + ], + hdrs = [ + "AuthorizationEntry.h", + "AuthorizationManifest.h", + ], + includes = ["."], + deps = [ + "//commons/atoms:atoms_lib", + ], +) + +cc_library( + name = "authorization_persistence", + hdrs = ["AuthorizationPersistence.h"], + includes = ["."], + deps = [ + ":authorization_types", + ], +) + +cc_library( + name = "mongo_authorization_persistence", + srcs = ["MongoAuthorizationPersistence.cc"], + hdrs = ["MongoAuthorizationPersistence.h"], + includes = ["."], + deps = [ + ":authorization_persistence", + ":authorization_types", + "//commons:commons_lib", + ], +) + +cc_library( + name = "authorization_management", + srcs = ["AuthorizationManagement.cc"], + hdrs = ["AuthorizationManagement.h"], + includes = ["."], + deps = [ + ":authorization_persistence", + ":authorization_types", + "//atomdb", + "//commons:commons_lib", + "//commons/atoms:atoms_lib", + ], +) diff --git a/src/atomdb/auth/MongoAuthorizationPersistence.cc b/src/atomdb/auth/MongoAuthorizationPersistence.cc new file mode 100644 index 00000000..af17dfb4 --- /dev/null +++ b/src/atomdb/auth/MongoAuthorizationPersistence.cc @@ -0,0 +1,153 @@ +#include "MongoAuthorizationPersistence.h" + +#include "Utils.h" + +using namespace atomdb; +using namespace commons; +using bsoncxx::builder::basic::kvp; +using bsoncxx::builder::basic::make_document; + +// -------------------------------------------------------------------------------- +// Constructors + +MongoAuthorizationPersistence::MongoAuthorizationPersistence(mongocxx::pool* pool, + const string& database_name, + const string& collection_name) + : pool(pool), database_name(database_name), collection_name(collection_name) { + if (this->pool == nullptr) { + RAISE_ERROR("MongoAuthorizationPersistence requires a non-null MongoDB pool"); + } + if (this->database_name.empty()) { + RAISE_ERROR("MongoAuthorizationPersistence requires a non-empty database name"); + } + if (this->collection_name.empty()) { + RAISE_ERROR("MongoAuthorizationPersistence requires a non-empty collection name"); + } +} + +// -------------------------------------------------------------------------------- +// Public methods + +void MongoAuthorizationPersistence::save(const string& public_key, const AuthorizationEntry& entry) { + auto conn = this->pool->acquire(); + auto collection = (*conn)[this->database_name][this->collection_name]; + + auto filter = make_document(kvp("public_key", public_key)); + auto existing = collection.find_one(filter.view()); + + bsoncxx::builder::basic::array schemas; + bool full_access = false; + + if (existing) { + auto view = existing->view(); + if (view["full_access"] && view["full_access"].type() == bsoncxx::type::k_bool) { + full_access = view["full_access"].get_bool().value; + } + if (view["allowed_schemas"] && view["allowed_schemas"].type() == bsoncxx::type::k_array) { + string entry_handle = entry.handle(); + for (const auto& item : view["allowed_schemas"].get_array().value) { + if (item.type() != bsoncxx::type::k_document) { + continue; + } + auto item_view = item.get_document().view(); + if (item_view["handle"] && item_view["handle"].type() == bsoncxx::type::k_string && + string(item_view["handle"].get_string().value) == entry_handle) { + continue; + } + schemas.append(item_view); + } + } + } + + schemas.append(make_schema_item(entry)); + + auto document = make_document(kvp("_id", public_key), + kvp("public_key", public_key), + kvp("full_access", full_access), + kvp("allowed_schemas", schemas)); + + mongocxx::options::replace opts; + opts.upsert(true); + auto reply = collection.replace_one(filter.view(), document.view(), opts); + if (!reply) { + RAISE_ERROR("Failed to save authorization entry for public_key in MongoDB"); + } +} + +void MongoAuthorizationPersistence::remove(const string& public_key, const string& handle) { + auto conn = this->pool->acquire(); + auto collection = (*conn)[this->database_name][this->collection_name]; + + auto filter = make_document(kvp("public_key", public_key)); + auto existing = collection.find_one(filter.view()); + if (!existing) { + return; + } + + auto view = existing->view(); + bool full_access = false; + if (view["full_access"] && view["full_access"].type() == bsoncxx::type::k_bool) { + full_access = view["full_access"].get_bool().value; + } + + bsoncxx::builder::basic::array schemas; + if (view["allowed_schemas"] && view["allowed_schemas"].type() == bsoncxx::type::k_array) { + for (const auto& item : view["allowed_schemas"].get_array().value) { + if (item.type() != bsoncxx::type::k_document) { + continue; + } + auto item_view = item.get_document().view(); + if (item_view["handle"] && item_view["handle"].type() == bsoncxx::type::k_string && + string(item_view["handle"].get_string().value) == handle) { + continue; + } + // Fallback: recompute handle from tokens when the stored document has no handle field. + if ((!item_view["handle"] || item_view["handle"].type() != bsoncxx::type::k_string) && + item_view["tokens"] && item_view["tokens"].type() == bsoncxx::type::k_array) { + vector tokens; + for (const auto& token : item_view["tokens"].get_array().value) { + if (token.type() == bsoncxx::type::k_string) { + tokens.push_back(string(token.get_string().value)); + } + } + if (!tokens.empty() && AuthorizationEntry(tokens, false, false).handle() == handle) { + continue; + } + } + schemas.append(item_view); + } + } + + auto document = make_document(kvp("_id", public_key), + kvp("public_key", public_key), + kvp("full_access", full_access), + kvp("allowed_schemas", schemas)); + + mongocxx::options::replace opts; + opts.upsert(false); + auto reply = collection.replace_one(filter.view(), document.view(), opts); + if (!reply) { + RAISE_ERROR("Failed to remove authorization entry for public_key in MongoDB"); + } +} + +void MongoAuthorizationPersistence::remove_all(const string& public_key) { + auto conn = this->pool->acquire(); + auto collection = (*conn)[this->database_name][this->collection_name]; + collection.delete_one(make_document(kvp("public_key", public_key))); +} + +// -------------------------------------------------------------------------------- +// Private methods + +bsoncxx::document::value MongoAuthorizationPersistence::make_schema_item( + const AuthorizationEntry& entry) { + auto tokens_array = bsoncxx::builder::basic::array{}; + for (const auto& token : entry.tokenize()) { + tokens_array.append(token); + } + return make_document(kvp("handle", entry.handle()), + kvp("tokens", tokens_array), + kvp("read", entry.allows(AuthorizationOperation::READ)), + kvp("write", entry.allows(AuthorizationOperation::WRITE))); +} \ No newline at end of file diff --git a/src/atomdb/auth/MongoAuthorizationPersistence.h b/src/atomdb/auth/MongoAuthorizationPersistence.h new file mode 100644 index 00000000..a61f68a7 --- /dev/null +++ b/src/atomdb/auth/MongoAuthorizationPersistence.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "AuthorizationPersistence.h" + +using namespace std; + +namespace atomdb { + +/** @brief MongoDB-backed persistence, built by AtomDBFactory for RedisMongoDB backends. */ +class MongoAuthorizationPersistence : public AuthorizationPersistence { + public: + /** + * @param pool Mongo pool owned by the backend. + * @param database_name Mongo database name. + * @param collection_name access_permissions collection name (hardcoded static on the backend). + */ + MongoAuthorizationPersistence(mongocxx::pool* pool, + const string& database_name, + const string& collection_name); + + void save(const string& public_key, const AuthorizationEntry& entry) override; + void remove(const string& public_key, const string& handle) override; + void remove_all(const string& public_key) override; + + private: + mongocxx::pool* pool; + string database_name; + string collection_name; + + bsoncxx::document::value make_schema_item(const AuthorizationEntry& entry); +}; + +} // namespace atomdb From c18e3d83784636ad340073a7c5a1938e0a30c62c Mon Sep 17 00:00:00 2001 From: marcocapozzoli Date: Wed, 12 Aug 2026 10:12:06 -0300 Subject: [PATCH 02/16] WIP --- src/atomdb/auth/AuthorizationEntry.cc | 18 ++++----- src/atomdb/auth/AuthorizationEntry.h | 6 +-- src/atomdb/auth/AuthorizationManagement.cc | 47 +++++++++++++++++++++- src/atomdb/auth/AuthorizationManagement.h | 8 ++-- src/atomdb/auth/BUILD | 1 + 5 files changed, 63 insertions(+), 17 deletions(-) diff --git a/src/atomdb/auth/AuthorizationEntry.cc b/src/atomdb/auth/AuthorizationEntry.cc index 44bd6775..bf8bf832 100644 --- a/src/atomdb/auth/AuthorizationEntry.cc +++ b/src/atomdb/auth/AuthorizationEntry.cc @@ -6,36 +6,36 @@ using namespace atomdb; // Constructors AuthorizationEntry::AuthorizationEntry(const LinkSchema& schema, bool read, bool write) - : schema(schema), read(read), write(write) {} + : _schema(schema), _read(read), _write(write) {} AuthorizationEntry::AuthorizationEntry(const vector& tokens, bool read, bool write) - : schema(tokens), read(read), write(write) {} + : _schema(tokens), _read(read), _write(write) {} // -------------------------------------------------------------------------------- // Public methods -string AuthorizationEntry::handle() const { return this->schema.handle(); } +string AuthorizationEntry::handle() const { return this->_schema.handle(); } -const LinkSchema& atomdb::AuthorizationEntry::schema() const { return this->schema; } +const LinkSchema& AuthorizationEntry::schema() const { return this->_schema; } bool AuthorizationEntry::allows(AuthorizationOperation operation) const { switch (operation) { case AuthorizationOperation::READ: - return this->read; + return this->_read; case AuthorizationOperation::WRITE: - return this->write; + return this->_write; } return false; } string AuthorizationEntry::to_string() const { return "AuthorizationEntry(handle: '" + this->handle() + - "', read: " + (this->read ? "true" : "false") + - ", write: " + (this->write ? "true" : "false") + ", schema: " + this->schema.to_string() + + "', read: " + (this->_read ? "true" : "false") + + ", write: " + (this->_write ? "true" : "false") + ", schema: " + this->_schema.to_string() + ")"; } vector AuthorizationEntry::tokenize() const { - LinkSchema schema_copy = this->schema; + LinkSchema schema_copy = this->_schema; return schema_copy.tokenize(); } diff --git a/src/atomdb/auth/AuthorizationEntry.h b/src/atomdb/auth/AuthorizationEntry.h index ab8a3225..b7caf7cc 100644 --- a/src/atomdb/auth/AuthorizationEntry.h +++ b/src/atomdb/auth/AuthorizationEntry.h @@ -42,9 +42,9 @@ class AuthorizationEntry { vector tokenize() const; private: - LinkSchema schema; - bool read; - bool write; + LinkSchema _schema; + bool _read; + bool _write; }; } // namespace atomdb diff --git a/src/atomdb/auth/AuthorizationManagement.cc b/src/atomdb/auth/AuthorizationManagement.cc index bc541e31..b2f1fc02 100644 --- a/src/atomdb/auth/AuthorizationManagement.cc +++ b/src/atomdb/auth/AuthorizationManagement.cc @@ -1,5 +1,7 @@ #include "AuthorizationManagement.h" +#include + #include "Assignment.h" #include "AtomDB.h" #include "Link.h" @@ -8,6 +10,7 @@ using namespace atomdb; using namespace atoms; using namespace commons; +using json = nlohmann::json; // -------------------------------------------------------------------------------- // Constructors @@ -18,8 +21,9 @@ AuthorizationManagement::AuthorizationManagement(shared_ptr atomdb, if (this->atomdb == nullptr) { RAISE_ERROR("AuthorizationManagement requires a non-null atomdb AtomDB"); } - // Bootstrap from AtomDB::get_access_permissions() once that API exists on AtomDB. - // Until then the in-RAM manifest starts empty and is filled only by authorize(). + for (const auto& document_json : this->atomdb->get_access_permissions()) { + this->manifest.set(this->parse_access_permissions_document(document_json)); + } } // -------------------------------------------------------------------------------- @@ -119,3 +123,42 @@ bool AuthorizationManagement::matches_entry(const AuthorizationEntry& entry, LinkSchema schema = entry.schema(); return schema.match(handle, assignment, decoder); } + +AuthorizationManifest::Document AuthorizationManagement::parse_access_permissions_document( + const string& document_json) { + json j = json::parse(document_json); + AuthorizationManifest::Document document; + + if (j.contains("public_key") && j["public_key"].is_string()) { + document.public_key = j["public_key"].get(); + } else if (j.contains("_id") && j["_id"].is_string()) { + document.public_key = j["_id"].get(); + } + + if (j.contains("full_access") && j["full_access"].is_boolean()) { + document.full_access = j["full_access"].get(); + } + + if (j.contains("allowed_schemas") && j["allowed_schemas"].is_array()) { + for (const auto& item : j["allowed_schemas"]) { + if (!item.is_object() || !item.contains("tokens") || !item["tokens"].is_array()) { + continue; + } + vector tokens; + for (const auto& token : item["tokens"]) { + if (token.is_string()) { + tokens.push_back(token.get()); + } + } + if (tokens.empty()) { + continue; + } + bool read = item.contains("read") && item["read"].is_boolean() && item["read"].get(); + bool write = + item.contains("write") && item["write"].is_boolean() && item["write"].get(); + document.entries.emplace_back(tokens, read, write); + } + } + + return document; +} diff --git a/src/atomdb/auth/AuthorizationManagement.h b/src/atomdb/auth/AuthorizationManagement.h index 4e085c8e..e2221118 100644 --- a/src/atomdb/auth/AuthorizationManagement.h +++ b/src/atomdb/auth/AuthorizationManagement.h @@ -26,10 +26,9 @@ class AtomDB; class AuthorizationManagement { public: /** - * @brief Builds the in-RAM AuthorizationManifest. + * @brief Builds the in-RAM AuthorizationManifest from atomdb->get_access_permissions(). * - * @param atomdb AtomDB used to read access_permissions documents once - * AtomDB::get_access_permissions() is available. Until then the manifest starts empty. + * @param atomdb AtomDB used to read access_permissions JSON documents and as HandleDecoder. * @param persistence Storage used by authorize() and revoke*(). May be null when the atomdb * has no authorization storage; administration then fails. */ @@ -83,6 +82,9 @@ class AuthorizationManagement { bool matches_entry(const AuthorizationEntry& entry, const string& handle, HandleDecoder& decoder) const; + + /** @brief Parses one JSON document from AtomDB::get_access_permissions() into a Document. */ + static AuthorizationManifest::Document parse_access_permissions_document(const string& json); }; } // namespace atomdb diff --git a/src/atomdb/auth/BUILD b/src/atomdb/auth/BUILD index 1e20ce49..af2c9ae5 100644 --- a/src/atomdb/auth/BUILD +++ b/src/atomdb/auth/BUILD @@ -50,5 +50,6 @@ cc_library( "//atomdb", "//commons:commons_lib", "//commons/atoms:atoms_lib", + "@nlohmann_json//:json", ], ) From 2a830690d39a5257f39fae5c1297f5e9d0b31ddc Mon Sep 17 00:00:00 2001 From: marcocapozzoli Date: Mon, 17 Aug 2026 11:59:44 -0300 Subject: [PATCH 03/16] WIP --- src/atomdb/auth/AuthorizationEntry.cc | 41 ----------- src/atomdb/auth/AuthorizationEntry.h | 50 ------------- src/atomdb/auth/AuthorizationManagement.cc | 71 +++++-------------- src/atomdb/auth/AuthorizationManagement.h | 27 +++---- src/atomdb/auth/AuthorizationManifest.cc | 50 +++++++------ src/atomdb/auth/AuthorizationManifest.h | 31 ++++---- src/atomdb/auth/AuthorizationPersistence.h | 5 +- src/atomdb/auth/BUILD | 9 ++- .../auth/MongoAuthorizationPersistence.cc | 22 +++--- .../auth/MongoAuthorizationPersistence.h | 6 +- 10 files changed, 101 insertions(+), 211 deletions(-) delete mode 100644 src/atomdb/auth/AuthorizationEntry.cc delete mode 100644 src/atomdb/auth/AuthorizationEntry.h diff --git a/src/atomdb/auth/AuthorizationEntry.cc b/src/atomdb/auth/AuthorizationEntry.cc deleted file mode 100644 index bf8bf832..00000000 --- a/src/atomdb/auth/AuthorizationEntry.cc +++ /dev/null @@ -1,41 +0,0 @@ -#include "AuthorizationEntry.h" - -using namespace atomdb; - -// -------------------------------------------------------------------------------- -// Constructors - -AuthorizationEntry::AuthorizationEntry(const LinkSchema& schema, bool read, bool write) - : _schema(schema), _read(read), _write(write) {} - -AuthorizationEntry::AuthorizationEntry(const vector& tokens, bool read, bool write) - : _schema(tokens), _read(read), _write(write) {} - -// -------------------------------------------------------------------------------- -// Public methods - -string AuthorizationEntry::handle() const { return this->_schema.handle(); } - -const LinkSchema& AuthorizationEntry::schema() const { return this->_schema; } - -bool AuthorizationEntry::allows(AuthorizationOperation operation) const { - switch (operation) { - case AuthorizationOperation::READ: - return this->_read; - case AuthorizationOperation::WRITE: - return this->_write; - } - return false; -} - -string AuthorizationEntry::to_string() const { - return "AuthorizationEntry(handle: '" + this->handle() + - "', read: " + (this->_read ? "true" : "false") + - ", write: " + (this->_write ? "true" : "false") + ", schema: " + this->_schema.to_string() + - ")"; -} - -vector AuthorizationEntry::tokenize() const { - LinkSchema schema_copy = this->_schema; - return schema_copy.tokenize(); -} diff --git a/src/atomdb/auth/AuthorizationEntry.h b/src/atomdb/auth/AuthorizationEntry.h deleted file mode 100644 index b7caf7cc..00000000 --- a/src/atomdb/auth/AuthorizationEntry.h +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once - -#include -#include - -#include "LinkSchema.h" - -using namespace std; -using namespace atoms; - -namespace atomdb { - -enum class AuthorizationOperation { READ, WRITE }; - -/** - * @brief One LinkSchema entry with independent read/write flags. - * - * Maps to one MongoDB allowed_schemas item. handle() is the LinkSchema handle, computed the same - * way an Atom handle is, and it is what identifies this entry in AuthorizationManagement::revoke(). - */ -class AuthorizationEntry { - public: - /** @brief Builds an entry from a LinkSchema. */ - AuthorizationEntry(const LinkSchema& schema, bool read, bool write); - - /** @brief Builds an entry from the tokens stored in MongoDB. */ - AuthorizationEntry(const vector& tokens, bool read, bool write); - - /** @brief Handle of the underlying LinkSchema. */ - string handle() const; - - /** @brief Returns the underlying LinkSchema. */ - const LinkSchema& schema() const; - - /** @brief Returns true if this entry allows the given operation. */ - bool allows(AuthorizationOperation operation) const; - - /** @brief Returns a string representation of the entry. */ - string to_string() const; - - /** @brief Returns the tokens of the underlying LinkSchema. */ - vector tokenize() const; - - private: - LinkSchema _schema; - bool _read; - bool _write; -}; - -} // namespace atomdb diff --git a/src/atomdb/auth/AuthorizationManagement.cc b/src/atomdb/auth/AuthorizationManagement.cc index b2f1fc02..09b18df5 100644 --- a/src/atomdb/auth/AuthorizationManagement.cc +++ b/src/atomdb/auth/AuthorizationManagement.cc @@ -1,7 +1,5 @@ #include "AuthorizationManagement.h" -#include - #include "Assignment.h" #include "AtomDB.h" #include "Link.h" @@ -10,7 +8,6 @@ using namespace atomdb; using namespace atoms; using namespace commons; -using json = nlohmann::json; // -------------------------------------------------------------------------------- // Constructors @@ -21,9 +18,6 @@ AuthorizationManagement::AuthorizationManagement(shared_ptr atomdb, if (this->atomdb == nullptr) { RAISE_ERROR("AuthorizationManagement requires a non-null atomdb AtomDB"); } - for (const auto& document_json : this->atomdb->get_access_permissions()) { - this->manifest.set(this->parse_access_permissions_document(document_json)); - } } // -------------------------------------------------------------------------------- @@ -45,7 +39,7 @@ bool AuthorizationManagement::is_authorized(const Atom& atom, HandleDecoder& decoder = *this->atomdb; for (const auto& entry : this->manifest.entries(public_key)) { - if (entry.allows(operation) && this->matches_entry(entry, atom, decoder)) { + if (allows(entry, operation) && this->matches_entry(entry, atom, decoder)) { return true; } } @@ -64,14 +58,15 @@ bool AuthorizationManagement::is_authorized(const string& handle, } for (const auto& entry : this->manifest.entries(public_key)) { - if (entry.allows(operation) && this->matches_entry(entry, handle, decoder)) { + if (allows(entry, operation) && this->matches_entry(entry, handle, decoder)) { return true; } } return false; } -void AuthorizationManagement::authorize(const string& public_key, const AuthorizationEntry& entry) { +void AuthorizationManagement::authorize(const string& public_key, + const atomdb_api_types::AccessPermissionEntry& entry) { if (this->persistence == nullptr) { RAISE_ERROR( "AuthorizationManagement::authorize() requires AuthorizationPersistence; " @@ -104,11 +99,22 @@ void AuthorizationManagement::revoke_all(const string& public_key) { // -------------------------------------------------------------------------------- // Private methods -bool AuthorizationManagement::matches_entry(const AuthorizationEntry& entry, +bool AuthorizationManagement::allows(const atomdb_api_types::AccessPermissionEntry& entry, + AuthorizationOperation operation) { + switch (operation) { + case AuthorizationOperation::READ: + return entry.read; + case AuthorizationOperation::WRITE: + return entry.write; + } + return false; +} + +bool AuthorizationManagement::matches_entry(const atomdb_api_types::AccessPermissionEntry& entry, const Atom& atom, HandleDecoder& decoder) const { Assignment assignment; - LinkSchema schema = entry.schema(); + LinkSchema schema = entry.schema; // Prefer matching against the in-memory atom so WRITE checks work for atoms not yet stored. if (Atom::is_link(atom)) { return schema.match(const_cast(static_cast(atom)), assignment, decoder); @@ -116,49 +122,10 @@ bool AuthorizationManagement::matches_entry(const AuthorizationEntry& entry, return schema.match(atom.handle(), assignment, decoder); } -bool AuthorizationManagement::matches_entry(const AuthorizationEntry& entry, +bool AuthorizationManagement::matches_entry(const atomdb_api_types::AccessPermissionEntry& entry, const string& handle, HandleDecoder& decoder) const { Assignment assignment; - LinkSchema schema = entry.schema(); + LinkSchema schema = entry.schema; return schema.match(handle, assignment, decoder); } - -AuthorizationManifest::Document AuthorizationManagement::parse_access_permissions_document( - const string& document_json) { - json j = json::parse(document_json); - AuthorizationManifest::Document document; - - if (j.contains("public_key") && j["public_key"].is_string()) { - document.public_key = j["public_key"].get(); - } else if (j.contains("_id") && j["_id"].is_string()) { - document.public_key = j["_id"].get(); - } - - if (j.contains("full_access") && j["full_access"].is_boolean()) { - document.full_access = j["full_access"].get(); - } - - if (j.contains("allowed_schemas") && j["allowed_schemas"].is_array()) { - for (const auto& item : j["allowed_schemas"]) { - if (!item.is_object() || !item.contains("tokens") || !item["tokens"].is_array()) { - continue; - } - vector tokens; - for (const auto& token : item["tokens"]) { - if (token.is_string()) { - tokens.push_back(token.get()); - } - } - if (tokens.empty()) { - continue; - } - bool read = item.contains("read") && item["read"].is_boolean() && item["read"].get(); - bool write = - item.contains("write") && item["write"].is_boolean() && item["write"].get(); - document.entries.emplace_back(tokens, read, write); - } - } - - return document; -} diff --git a/src/atomdb/auth/AuthorizationManagement.h b/src/atomdb/auth/AuthorizationManagement.h index e2221118..64cfc2c1 100644 --- a/src/atomdb/auth/AuthorizationManagement.h +++ b/src/atomdb/auth/AuthorizationManagement.h @@ -4,7 +4,7 @@ #include #include "Atom.h" -#include "AuthorizationEntry.h" +#include "AtomDBAPITypes.h" #include "AuthorizationManifest.h" #include "AuthorizationPersistence.h" #include "HandleDecoder.h" @@ -16,19 +16,18 @@ namespace atomdb { class AtomDB; +enum class AuthorizationOperation { READ, WRITE }; + /** * @brief Authorization queries and administration. * - * The manifest is loaded once in the constructor and kept in RAM; authorize() and revoke*() update both - * the storage (through AuthorizationPersistence) and the in-RAM manifest, so no lookup ever hits the - * database. + * The manifest is kept in RAM; authorize() and revoke*() update both the storage (through + * AuthorizationPersistence) and the in-RAM manifest. */ class AuthorizationManagement { public: /** - * @brief Builds the in-RAM AuthorizationManifest from atomdb->get_access_permissions(). - * - * @param atomdb AtomDB used to read access_permissions JSON documents and as HandleDecoder. + * @param atomdb AtomDB used as HandleDecoder. * @param persistence Storage used by authorize() and revoke*(). May be null when the atomdb * has no authorization storage; administration then fails. */ @@ -59,12 +58,12 @@ class AuthorizationManagement { /** * @brief Grants one entry to public_key. Updates storage and the in-RAM manifest. */ - void authorize(const string& public_key, const AuthorizationEntry& entry); + void authorize(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry); /** * @brief Revokes one entry from public_key. * - * @param handle AuthorizationEntry::handle(), i.e. the LinkSchema handle. + * @param handle AccessPermissionEntry::schema.handle(), i.e. the LinkSchema handle. */ void revoke(const string& public_key, const string& handle); @@ -78,13 +77,15 @@ class AuthorizationManagement { shared_ptr persistence; AuthorizationManifest manifest; - bool matches_entry(const AuthorizationEntry& entry, const Atom& atom, HandleDecoder& decoder) const; - bool matches_entry(const AuthorizationEntry& entry, + bool matches_entry(const atomdb_api_types::AccessPermissionEntry& entry, + const Atom& atom, + HandleDecoder& decoder) const; + bool matches_entry(const atomdb_api_types::AccessPermissionEntry& entry, const string& handle, HandleDecoder& decoder) const; - /** @brief Parses one JSON document from AtomDB::get_access_permissions() into a Document. */ - static AuthorizationManifest::Document parse_access_permissions_document(const string& json); + static bool allows(const atomdb_api_types::AccessPermissionEntry& entry, + AuthorizationOperation operation); }; } // namespace atomdb diff --git a/src/atomdb/auth/AuthorizationManifest.cc b/src/atomdb/auth/AuthorizationManifest.cc index 65cfc883..f0f95d24 100644 --- a/src/atomdb/auth/AuthorizationManifest.cc +++ b/src/atomdb/auth/AuthorizationManifest.cc @@ -4,28 +4,34 @@ using namespace atomdb; -const vector AuthorizationManifest::EMPTY_ENTRIES; +const vector AuthorizationManifest::EMPTY_ENTRIES; // -------------------------------------------------------------------------------- // Public methods -void AuthorizationManifest::set(const Document& document) { - this->documents[document.public_key] = document; +void AuthorizationManifest::set(const atomdb_api_types::AccessPermissionDocument& document) { + auto it = this->documents.find(document.public_key); + if (it == this->documents.end()) { + this->documents.emplace(document.public_key, document); + } else { + it->second = document; + } } -void AuthorizationManifest::add(const string& public_key, const AuthorizationEntry& entry) { - Document* document = this->find_document(public_key, "add"); +void AuthorizationManifest::add(const string& public_key, + const atomdb_api_types::AccessPermissionEntry& entry) { + atomdb_api_types::AccessPermissionDocument* document = this->find_document(public_key, "add"); if (document == nullptr) { this->create_document(public_key, entry); return; } - string entry_handle = entry.handle(); - vector& entries = document->entries; + string entry_handle = entry.schema.handle(); + vector& entries = document->entries; for (auto& existing : entries) { - if (existing.handle() == entry_handle) { + if (existing.schema.handle() == entry_handle) { existing = entry; return; } @@ -35,14 +41,14 @@ void AuthorizationManifest::add(const string& public_key, const AuthorizationEnt } void AuthorizationManifest::remove(const string& public_key, const string& handle) { - Document* document = this->find_document(public_key, "remove"); + atomdb_api_types::AccessPermissionDocument* document = this->find_document(public_key, "remove"); if (document == nullptr) return; - vector& entries = document->entries; + vector& entries = document->entries; for (auto it = entries.begin(); it != entries.end(); ++it) { - if (it->handle() == handle) { + if (it->schema.handle() == handle) { entries.erase(it); return; } @@ -56,15 +62,17 @@ bool AuthorizationManifest::is_registered(const string& public_key) const { } bool AuthorizationManifest::full_access(const string& public_key) { - Document* document = this->find_document(public_key, "full_access"); + atomdb_api_types::AccessPermissionDocument* document = + this->find_document(public_key, "full_access"); if (document == nullptr) { return false; } return document->full_access; } -const vector& AuthorizationManifest::entries(const string& public_key) { - Document* document = this->find_document(public_key, "entries"); +const vector& AuthorizationManifest::entries( + const string& public_key) { + atomdb_api_types::AccessPermissionDocument* document = this->find_document(public_key, "entries"); if (document == nullptr) { return EMPTY_ENTRIES; } @@ -74,16 +82,14 @@ const vector& AuthorizationManifest::entries(const string& p // -------------------------------------------------------------------------------- // Private methods -void AuthorizationManifest::create_document(const string& public_key, const AuthorizationEntry& entry) { - Document document; - document.public_key = public_key; - document.full_access = false; - document.entries.push_back(entry); - this->documents[public_key] = document; +void AuthorizationManifest::create_document(const string& public_key, + const atomdb_api_types::AccessPermissionEntry& entry) { + this->documents.emplace(public_key, + atomdb_api_types::AccessPermissionDocument(public_key, false, {entry})); } -AuthorizationManifest::Document* AuthorizationManifest::find_document(const string& public_key, - const string& caller) { +atomdb_api_types::AccessPermissionDocument* AuthorizationManifest::find_document( + const string& public_key, const string& caller) { auto it = this->documents.find(public_key); if (it == this->documents.end()) { LOG_INFO("AuthorizationManifest::" + caller + diff --git a/src/atomdb/auth/AuthorizationManifest.h b/src/atomdb/auth/AuthorizationManifest.h index d4730b53..4c894bb3 100644 --- a/src/atomdb/auth/AuthorizationManifest.h +++ b/src/atomdb/auth/AuthorizationManifest.h @@ -4,7 +4,7 @@ #include #include -#include "AuthorizationEntry.h" +#include "AtomDBAPITypes.h" using namespace std; @@ -13,27 +13,20 @@ namespace atomdb { /** * @brief In-RAM image of the whole access_permissions collection. * - * AuthorizationManagement keeps exactly one of these. Document maps 1:1 to one MongoDB document. + * AuthorizationManagement keeps exactly one of these. Each AccessPermissionDocument maps 1:1 to one + * MongoDB document. */ class AuthorizationManifest { public: - /** @brief One access_permissions document. */ - class Document { - public: - string public_key; - bool full_access = false; - vector entries; - }; - AuthorizationManifest() = default; /** @brief Replaces (or inserts) the document for document.public_key. */ - void set(const Document& document); + void set(const atomdb_api_types::AccessPermissionDocument& document); /** @brief Adds one entry to public_key, creating the document if needed. */ - void add(const string& public_key, const AuthorizationEntry& entry); + void add(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry); - /** @brief Removes the entry whose handle() == handle from public_key. No-op if absent. */ + /** @brief Removes the entry whose schema.handle() == handle from public_key. No-op if absent. */ void remove(const string& public_key, const string& handle); /** @brief Removes the whole document for public_key. No-op if not registered. */ @@ -46,14 +39,16 @@ class AuthorizationManifest { bool full_access(const string& public_key); /** @brief Returns the entries for public_key, or an empty vector if not registered. */ - const vector& entries(const string& public_key); + const vector& entries(const string& public_key); private: - map documents; - static const vector EMPTY_ENTRIES; + map documents; + static const vector EMPTY_ENTRIES; - void create_document(const string& public_key, const AuthorizationEntry& entry); - Document* find_document(const string& public_key, const string& caller); + void create_document(const string& public_key, + const atomdb_api_types::AccessPermissionEntry& entry); + atomdb_api_types::AccessPermissionDocument* find_document(const string& public_key, + const string& caller); }; } // namespace atomdb diff --git a/src/atomdb/auth/AuthorizationPersistence.h b/src/atomdb/auth/AuthorizationPersistence.h index e84bd5a6..fb745e92 100644 --- a/src/atomdb/auth/AuthorizationPersistence.h +++ b/src/atomdb/auth/AuthorizationPersistence.h @@ -2,7 +2,7 @@ #include -#include "AuthorizationEntry.h" +#include "AtomDBAPITypes.h" using namespace std; @@ -14,7 +14,8 @@ class AuthorizationPersistence { virtual ~AuthorizationPersistence() = default; /** @brief Persists one entry under public_key (creating the document if needed). */ - virtual void save(const string& public_key, const AuthorizationEntry& entry) = 0; + virtual void save(const string& public_key, + const atomdb_api_types::AccessPermissionEntry& entry) = 0; /** @brief Removes the entry identified by handle from public_key's document. */ virtual void remove(const string& public_key, const string& handle) = 0; diff --git a/src/atomdb/auth/BUILD b/src/atomdb/auth/BUILD index af2c9ae5..749fabca 100644 --- a/src/atomdb/auth/BUILD +++ b/src/atomdb/auth/BUILD @@ -5,15 +5,15 @@ package(default_visibility = ["//visibility:public"]) cc_library( name = "authorization_types", srcs = [ - "AuthorizationEntry.cc", "AuthorizationManifest.cc", ], hdrs = [ - "AuthorizationEntry.h", "AuthorizationManifest.h", ], includes = ["."], deps = [ + "//atomdb:atomdb_api_types", + "//commons:commons_lib", "//commons/atoms:atoms_lib", ], ) @@ -24,6 +24,7 @@ cc_library( includes = ["."], deps = [ ":authorization_types", + "//atomdb:atomdb_api_types", ], ) @@ -35,7 +36,9 @@ cc_library( deps = [ ":authorization_persistence", ":authorization_types", + "//atomdb:atomdb_api_types", "//commons:commons_lib", + "//commons/atoms:atoms_lib", ], ) @@ -48,8 +51,8 @@ cc_library( ":authorization_persistence", ":authorization_types", "//atomdb", + "//atomdb:atomdb_api_types", "//commons:commons_lib", "//commons/atoms:atoms_lib", - "@nlohmann_json//:json", ], ) diff --git a/src/atomdb/auth/MongoAuthorizationPersistence.cc b/src/atomdb/auth/MongoAuthorizationPersistence.cc index af17dfb4..aabc0656 100644 --- a/src/atomdb/auth/MongoAuthorizationPersistence.cc +++ b/src/atomdb/auth/MongoAuthorizationPersistence.cc @@ -1,8 +1,10 @@ #include "MongoAuthorizationPersistence.h" +#include "LinkSchema.h" #include "Utils.h" using namespace atomdb; +using namespace atoms; using namespace commons; using bsoncxx::builder::basic::kvp; using bsoncxx::builder::basic::make_document; @@ -28,7 +30,8 @@ MongoAuthorizationPersistence::MongoAuthorizationPersistence(mongocxx::pool* poo // -------------------------------------------------------------------------------- // Public methods -void MongoAuthorizationPersistence::save(const string& public_key, const AuthorizationEntry& entry) { +void MongoAuthorizationPersistence::save(const string& public_key, + const atomdb_api_types::AccessPermissionEntry& entry) { auto conn = this->pool->acquire(); auto collection = (*conn)[this->database_name][this->collection_name]; @@ -44,7 +47,7 @@ void MongoAuthorizationPersistence::save(const string& public_key, const Authori full_access = view["full_access"].get_bool().value; } if (view["allowed_schemas"] && view["allowed_schemas"].type() == bsoncxx::type::k_array) { - string entry_handle = entry.handle(); + string entry_handle = entry.schema.handle(); for (const auto& item : view["allowed_schemas"].get_array().value) { if (item.type() != bsoncxx::type::k_document) { continue; @@ -110,7 +113,9 @@ void MongoAuthorizationPersistence::remove(const string& public_key, const strin tokens.push_back(string(token.get_string().value)); } } - if (!tokens.empty() && AuthorizationEntry(tokens, false, false).handle() == handle) { + if (!tokens.empty() && + atomdb_api_types::AccessPermissionEntry(tokens, false, false).schema.handle() == + handle) { continue; } } @@ -141,13 +146,14 @@ void MongoAuthorizationPersistence::remove_all(const string& public_key) { // Private methods bsoncxx::document::value MongoAuthorizationPersistence::make_schema_item( - const AuthorizationEntry& entry) { + const atomdb_api_types::AccessPermissionEntry& entry) { auto tokens_array = bsoncxx::builder::basic::array{}; - for (const auto& token : entry.tokenize()) { + LinkSchema schema = entry.schema; + for (const auto& token : schema.tokenize()) { tokens_array.append(token); } - return make_document(kvp("handle", entry.handle()), + return make_document(kvp("handle", entry.schema.handle()), kvp("tokens", tokens_array), - kvp("read", entry.allows(AuthorizationOperation::READ)), - kvp("write", entry.allows(AuthorizationOperation::WRITE))); + kvp("read", entry.read), + kvp("write", entry.write)); } \ No newline at end of file diff --git a/src/atomdb/auth/MongoAuthorizationPersistence.h b/src/atomdb/auth/MongoAuthorizationPersistence.h index a61f68a7..32acff28 100644 --- a/src/atomdb/auth/MongoAuthorizationPersistence.h +++ b/src/atomdb/auth/MongoAuthorizationPersistence.h @@ -25,7 +25,8 @@ class MongoAuthorizationPersistence : public AuthorizationPersistence { const string& database_name, const string& collection_name); - void save(const string& public_key, const AuthorizationEntry& entry) override; + void save(const string& public_key, + const atomdb_api_types::AccessPermissionEntry& entry) override; void remove(const string& public_key, const string& handle) override; void remove_all(const string& public_key) override; @@ -34,7 +35,8 @@ class MongoAuthorizationPersistence : public AuthorizationPersistence { string database_name; string collection_name; - bsoncxx::document::value make_schema_item(const AuthorizationEntry& entry); + bsoncxx::document::value make_schema_item( + const atomdb_api_types::AccessPermissionEntry& entry); }; } // namespace atomdb From f96661f7272ca1b01509271e815eb8657a212cf8 Mon Sep 17 00:00:00 2001 From: marcocapozzoli Date: Mon, 17 Aug 2026 13:15:57 -0300 Subject: [PATCH 04/16] WIP --- src/tests/cpp/BUILD | 19 +++ src/tests/cpp/authorization_test.cc | 198 +++++++++++++++++++++++++ src/tests/cpp/protected_atomdb_test.cc | 6 + 3 files changed, 223 insertions(+) create mode 100644 src/tests/cpp/authorization_test.cc diff --git a/src/tests/cpp/BUILD b/src/tests/cpp/BUILD index e026ea3e..70815464 100644 --- a/src/tests/cpp/BUILD +++ b/src/tests/cpp/BUILD @@ -867,6 +867,25 @@ cc_test( ], ) +cc_test( + name = "authorization_test", + size = "small", + srcs = ["authorization_test.cc"], + copts = [ + "-Iexternal/gtest/googletest/include", + "-Iexternal/gtest/googletest", + ], + linkstatic = 1, + deps = [ + "//atomdb/auth:authorization_management", + "//atomdb/auth:authorization_persistence", + "//atomdb/auth:authorization_types", + "//atomdb/inmemorydb:inmemorydb_lib", + "//commons/atoms:atoms_lib", + "@com_github_google_googletest//:gtest_main", + ], +) + cc_test( name = "atomdb_factory_test", size = "medium", diff --git a/src/tests/cpp/authorization_test.cc b/src/tests/cpp/authorization_test.cc new file mode 100644 index 00000000..0b3f055e --- /dev/null +++ b/src/tests/cpp/authorization_test.cc @@ -0,0 +1,198 @@ +#include + +#include +#include +#include + +#include "AuthorizationManagement.h" +#include "AuthorizationManifest.h" +#include "AuthorizationPersistence.h" +#include "InMemoryDB.h" +#include "Link.h" +#include "LinkSchema.h" +#include "Node.h" + +using namespace atomdb; +using namespace atomdb_api_types; +using namespace atoms; +using namespace std; + +namespace { + +vector inheritance_mammal_tokens() { + return {"LINK_TEMPLATE", + "Expression", + "3", + "NODE", + "Symbol", + "Inheritance", + "VARIABLE", + "x", + "NODE", + "Symbol", + "\"mammal\""}; +} + +AccessPermissionEntry read_only_inheritance_entry() { + return AccessPermissionEntry(inheritance_mammal_tokens(), true, false); +} + +class FakePersistence : public AuthorizationPersistence { + public: + int save_count = 0; + int remove_count = 0; + int remove_all_count = 0; + string last_key; + string last_handle; + + void save(const string& public_key, const AccessPermissionEntry& entry) override { + this->save_count++; + this->last_key = public_key; + this->last_handle = entry.schema.handle(); + } + + void remove(const string& public_key, const string& handle) override { + this->remove_count++; + this->last_key = public_key; + this->last_handle = handle; + } + + void remove_all(const string& public_key) override { + this->remove_all_count++; + this->last_key = public_key; + } +}; + +shared_ptr db_with_inheritance_link(string* link_handle) { + auto db = make_shared("auth_test_"); + auto human = new Node("Symbol", "\"human\""); + auto mammal = new Node("Symbol", "\"mammal\""); + auto inheritance = new Node("Symbol", "Inheritance"); + string human_handle = db->add_node(human); + string mammal_handle = db->add_node(mammal); + string inheritance_handle = db->add_node(inheritance); + auto link = new Link("Expression", {inheritance_handle, human_handle, mammal_handle}); + *link_handle = db->add_link(link); + delete human; + delete mammal; + delete inheritance; + delete link; + return db; +} + +} // namespace + +TEST(AuthorizationManifestTest, SetAddRemoveAndFullAccess) { + AuthorizationManifest manifest; + string key = "pk1"; + EXPECT_FALSE(manifest.is_registered(key)); + EXPECT_FALSE(manifest.full_access(key)); + EXPECT_TRUE(manifest.entries(key).empty()); + + AccessPermissionEntry entry = read_only_inheritance_entry(); + manifest.add(key, entry); + ASSERT_TRUE(manifest.is_registered(key)); + ASSERT_EQ(manifest.entries(key).size(), 1u); + EXPECT_TRUE(manifest.entries(key)[0].read); + EXPECT_FALSE(manifest.entries(key)[0].write); + EXPECT_EQ(manifest.entries(key)[0].schema.handle(), entry.schema.handle()); + + manifest.remove(key, entry.schema.handle()); + EXPECT_TRUE(manifest.is_registered(key)); + EXPECT_TRUE(manifest.entries(key).empty()); + + manifest.set(AccessPermissionDocument(key, true, {})); + EXPECT_TRUE(manifest.full_access(key)); + + manifest.remove_all(key); + EXPECT_FALSE(manifest.is_registered(key)); +} + +TEST(AuthorizationManifestTest, AddReplacesEntryWithSameSchemaHandle) { + AuthorizationManifest manifest; + string key = "pk1"; + manifest.add(key, AccessPermissionEntry(inheritance_mammal_tokens(), true, false)); + manifest.add(key, AccessPermissionEntry(inheritance_mammal_tokens(), false, true)); + + ASSERT_EQ(manifest.entries(key).size(), 1u); + EXPECT_FALSE(manifest.entries(key)[0].read); + EXPECT_TRUE(manifest.entries(key)[0].write); +} + +TEST(AuthorizationManagementTest, RejectsNullAtomDB) { + EXPECT_THROW(AuthorizationManagement(nullptr, nullptr), runtime_error); +} + +TEST(AuthorizationManagementTest, AdministrationRequiresPersistence) { + auto db = make_shared("auth_admin_"); + AuthorizationManagement management(db, nullptr); + AccessPermissionEntry entry = read_only_inheritance_entry(); + EXPECT_THROW(management.authorize("pk", entry), runtime_error); + EXPECT_THROW(management.revoke("pk", entry.schema.handle()), runtime_error); + EXPECT_THROW(management.revoke_all("pk"), runtime_error); +} + +TEST(AuthorizationManagementTest, UnregisteredKeyIsDenied) { + string link_handle; + auto db = db_with_inheritance_link(&link_handle); + auto persistence = make_shared(); + AuthorizationManagement management(db, persistence); + + auto link = db->get_link(link_handle); + ASSERT_NE(link, nullptr); + EXPECT_FALSE(management.is_authorized(*link, "unknown", AuthorizationOperation::READ)); + EXPECT_FALSE( + management.is_authorized(link_handle, "unknown", AuthorizationOperation::READ, *db)); +} + +TEST(AuthorizationManagementTest, AuthorizeThenReadAndWriteFlags) { + string link_handle; + auto db = db_with_inheritance_link(&link_handle); + auto persistence = make_shared(); + AuthorizationManagement management(db, persistence); + AccessPermissionEntry entry = read_only_inheritance_entry(); + + management.authorize("pk", entry); + EXPECT_EQ(persistence->save_count, 1); + EXPECT_EQ(persistence->last_key, "pk"); + + auto link = db->get_link(link_handle); + ASSERT_NE(link, nullptr); + EXPECT_TRUE(management.is_authorized(*link, "pk", AuthorizationOperation::READ)); + EXPECT_FALSE(management.is_authorized(*link, "pk", AuthorizationOperation::WRITE)); + EXPECT_TRUE(management.is_authorized(link_handle, "pk", AuthorizationOperation::READ, *db)); + EXPECT_FALSE(management.is_authorized(link_handle, "pk", AuthorizationOperation::WRITE, *db)); +} + +TEST(AuthorizationManagementTest, RevokeRemovesAccess) { + string link_handle; + auto db = db_with_inheritance_link(&link_handle); + auto persistence = make_shared(); + AuthorizationManagement management(db, persistence); + AccessPermissionEntry entry = read_only_inheritance_entry(); + + management.authorize("pk", entry); + management.revoke("pk", entry.schema.handle()); + EXPECT_EQ(persistence->remove_count, 1); + + auto link = db->get_link(link_handle); + ASSERT_NE(link, nullptr); + EXPECT_FALSE(management.is_authorized(*link, "pk", AuthorizationOperation::READ)); +} + +TEST(AuthorizationManagementTest, DoesNotLoadPermissionsFromAtomDB) { + class PermissionsInMemoryDB : public InMemoryDB { + public: + explicit PermissionsInMemoryDB(const string& context) : InMemoryDB(context) {} + vector get_access_permissions( + const PublicKey& public_key) const override { + return {AccessPermissionDocument("pk", true, {})}; + } + }; + + auto db = make_shared("auth_noload_"); + auto persistence = make_shared(); + AuthorizationManagement management(db, persistence); + + EXPECT_FALSE(management.has_full_access("pk")); +} diff --git a/src/tests/cpp/protected_atomdb_test.cc b/src/tests/cpp/protected_atomdb_test.cc index bf148e82..897fa9bc 100644 --- a/src/tests/cpp/protected_atomdb_test.cc +++ b/src/tests/cpp/protected_atomdb_test.cc @@ -103,3 +103,9 @@ TEST(ProtectedAtomDBTest, PublicKeyOverloadsAreNotImplementedYet) { EXPECT_THROW(db->delete_atom("handle", key), runtime_error); EXPECT_THROW(db->atom_count(key), runtime_error); } + +TEST(ProtectedAtomDBTest, GetAccessPermissionsWithKeyDoesNotThrow) { + auto db = make_protected_db("protected_perms_key_"); + PublicKey key("public_key"); + EXPECT_TRUE(db->get_access_permissions(key).empty()); +} From 1bc9afbd107c9b4e11d9953367a0d38f7ec5375a Mon Sep 17 00:00:00 2001 From: marcocapozzoli Date: Mon, 17 Aug 2026 19:49:42 -0300 Subject: [PATCH 05/16] WIP --- src/atomdb/auth/AuthorizationManifest.cc | 24 ++- src/atomdb/auth/AuthorizationManifest.h | 20 +- src/atomdb/auth/AuthorizationPersistence.h | 6 +- src/atomdb/auth/BUILD | 1 + .../auth/MongoAuthorizationPersistence.cc | 179 +++++++++--------- .../auth/MongoAuthorizationPersistence.h | 21 +- src/tests/cpp/BUILD | 1 + src/tests/cpp/authorization_test.cc | 3 +- src/tests/cpp/redis_mongodb_test.cc | 53 ++++++ 9 files changed, 186 insertions(+), 122 deletions(-) diff --git a/src/atomdb/auth/AuthorizationManifest.cc b/src/atomdb/auth/AuthorizationManifest.cc index f0f95d24..42f16cac 100644 --- a/src/atomdb/auth/AuthorizationManifest.cc +++ b/src/atomdb/auth/AuthorizationManifest.cc @@ -2,7 +2,12 @@ #include +#define LOG_LEVEL INFO_LEVEL +#include "Logger.h" +#include "Utils.h" + using namespace atomdb; +using namespace auth; const vector AuthorizationManifest::EMPTY_ENTRIES; @@ -18,7 +23,7 @@ void AuthorizationManifest::set(const atomdb_api_types::AccessPermissionDocument } } -void AuthorizationManifest::add(const string& public_key, +void AuthorizationManifest::add(const atomdb_api_types::PublicKey& public_key, const atomdb_api_types::AccessPermissionEntry& entry) { atomdb_api_types::AccessPermissionDocument* document = this->find_document(public_key, "add"); @@ -40,7 +45,7 @@ void AuthorizationManifest::add(const string& public_key, entries.push_back(entry); } -void AuthorizationManifest::remove(const string& public_key, const string& handle) { +void AuthorizationManifest::remove(const atomdb_api_types::PublicKey& public_key, const string& handle) { atomdb_api_types::AccessPermissionDocument* document = this->find_document(public_key, "remove"); if (document == nullptr) return; @@ -55,13 +60,13 @@ void AuthorizationManifest::remove(const string& public_key, const string& handl } } -void AuthorizationManifest::remove_all(const string& public_key) { this->documents.erase(public_key); } +void AuthorizationManifest::remove_all(const atomdb_api_types::PublicKey& public_key) { this->documents.erase(public_key); } -bool AuthorizationManifest::is_registered(const string& public_key) const { +bool AuthorizationManifest::is_registered(const atomdb_api_types::PublicKey& public_key) const { return this->documents.find(public_key) != this->documents.end(); } -bool AuthorizationManifest::full_access(const string& public_key) { +bool AuthorizationManifest::full_access(const atomdb_api_types::PublicKey& public_key) { atomdb_api_types::AccessPermissionDocument* document = this->find_document(public_key, "full_access"); if (document == nullptr) { @@ -71,7 +76,7 @@ bool AuthorizationManifest::full_access(const string& public_key) { } const vector& AuthorizationManifest::entries( - const string& public_key) { + const atomdb_api_types::PublicKey& public_key) { atomdb_api_types::AccessPermissionDocument* document = this->find_document(public_key, "entries"); if (document == nullptr) { return EMPTY_ENTRIES; @@ -82,18 +87,17 @@ const vector& AuthorizationManifest::en // -------------------------------------------------------------------------------- // Private methods -void AuthorizationManifest::create_document(const string& public_key, +void AuthorizationManifest::create_document(const atomdb_api_types::PublicKey& public_key, const atomdb_api_types::AccessPermissionEntry& entry) { this->documents.emplace(public_key, atomdb_api_types::AccessPermissionDocument(public_key, false, {entry})); } atomdb_api_types::AccessPermissionDocument* AuthorizationManifest::find_document( - const string& public_key, const string& caller) { + const atomdb_api_types::PublicKey& public_key, const string& caller) { auto it = this->documents.find(public_key); if (it == this->documents.end()) { - LOG_INFO("AuthorizationManifest::" + caller + - "() called for unregistered public_key: " + public_key); + LOG_INFO("AuthorizationManifest::" << caller << "() called for unregistered public_key: " << public_key); return nullptr; } return &it->second; diff --git a/src/atomdb/auth/AuthorizationManifest.h b/src/atomdb/auth/AuthorizationManifest.h index 4c894bb3..a54a12ea 100644 --- a/src/atomdb/auth/AuthorizationManifest.h +++ b/src/atomdb/auth/AuthorizationManifest.h @@ -10,6 +10,8 @@ using namespace std; namespace atomdb { +namespace auth { + /** * @brief In-RAM image of the whole access_permissions collection. * @@ -24,31 +26,31 @@ class AuthorizationManifest { void set(const atomdb_api_types::AccessPermissionDocument& document); /** @brief Adds one entry to public_key, creating the document if needed. */ - void add(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry); + void add(const atomdb_api_types::PublicKey& public_key, const atomdb_api_types::AccessPermissionEntry& entry); /** @brief Removes the entry whose schema.handle() == handle from public_key. No-op if absent. */ - void remove(const string& public_key, const string& handle); + void remove(const atomdb_api_types::PublicKey& public_key, const string& handle); /** @brief Removes the whole document for public_key. No-op if not registered. */ - void remove_all(const string& public_key); + void remove_all(const atomdb_api_types::PublicKey& public_key); /** @brief Returns true if public_key is registered. */ - bool is_registered(const string& public_key) const; + bool is_registered(const atomdb_api_types::PublicKey& public_key) const; /** @brief Returns true if public_key is registered with full_access. */ - bool full_access(const string& public_key); + bool full_access(const atomdb_api_types::PublicKey& public_key); /** @brief Returns the entries for public_key, or an empty vector if not registered. */ - const vector& entries(const string& public_key); + const vector& entries(const atomdb_api_types::PublicKey& public_key); private: map documents; static const vector EMPTY_ENTRIES; - void create_document(const string& public_key, - const atomdb_api_types::AccessPermissionEntry& entry); - atomdb_api_types::AccessPermissionDocument* find_document(const string& public_key, + void create_document(const atomdb_api_types::PublicKey& public_key, const atomdb_api_types::AccessPermissionEntry& entry); + atomdb_api_types::AccessPermissionDocument* find_document(const atomdb_api_types::PublicKey& public_key, const string& caller); }; +} // namespace auth } // namespace atomdb diff --git a/src/atomdb/auth/AuthorizationPersistence.h b/src/atomdb/auth/AuthorizationPersistence.h index fb745e92..df053da3 100644 --- a/src/atomdb/auth/AuthorizationPersistence.h +++ b/src/atomdb/auth/AuthorizationPersistence.h @@ -14,14 +14,14 @@ class AuthorizationPersistence { virtual ~AuthorizationPersistence() = default; /** @brief Persists one entry under public_key (creating the document if needed). */ - virtual void save(const string& public_key, + virtual void save(const atomdb_api_types::PublicKey& public_key, const atomdb_api_types::AccessPermissionEntry& entry) = 0; /** @brief Removes the entry identified by handle from public_key's document. */ - virtual void remove(const string& public_key, const string& handle) = 0; + virtual void remove(const atomdb_api_types::PublicKey& public_key, const string& handle) = 0; /** @brief Removes the whole document for public_key. */ - virtual void remove_all(const string& public_key) = 0; + virtual void remove_all(const atomdb_api_types::PublicKey& public_key) = 0; }; } // namespace atomdb diff --git a/src/atomdb/auth/BUILD b/src/atomdb/auth/BUILD index 749fabca..74ba6686 100644 --- a/src/atomdb/auth/BUILD +++ b/src/atomdb/auth/BUILD @@ -39,6 +39,7 @@ cc_library( "//atomdb:atomdb_api_types", "//commons:commons_lib", "//commons/atoms:atoms_lib", + "//hasher:hasher_lib", ], ) diff --git a/src/atomdb/auth/MongoAuthorizationPersistence.cc b/src/atomdb/auth/MongoAuthorizationPersistence.cc index aabc0656..4e311d0f 100644 --- a/src/atomdb/auth/MongoAuthorizationPersistence.cc +++ b/src/atomdb/auth/MongoAuthorizationPersistence.cc @@ -1,13 +1,10 @@ #include "MongoAuthorizationPersistence.h" -#include "LinkSchema.h" #include "Utils.h" +#include "expression_hasher.h" using namespace atomdb; -using namespace atoms; using namespace commons; -using bsoncxx::builder::basic::kvp; -using bsoncxx::builder::basic::make_document; // -------------------------------------------------------------------------------- // Constructors @@ -19,11 +16,8 @@ MongoAuthorizationPersistence::MongoAuthorizationPersistence(mongocxx::pool* poo if (this->pool == nullptr) { RAISE_ERROR("MongoAuthorizationPersistence requires a non-null MongoDB pool"); } - if (this->database_name.empty()) { - RAISE_ERROR("MongoAuthorizationPersistence requires a non-empty database name"); - } - if (this->collection_name.empty()) { - RAISE_ERROR("MongoAuthorizationPersistence requires a non-empty collection name"); + if (this->database_name.empty() || this->collection_name.empty()) { + RAISE_ERROR("MongoAuthorizationPersistence requires a non-empty database and collection names"); } } @@ -32,48 +26,36 @@ MongoAuthorizationPersistence::MongoAuthorizationPersistence(mongocxx::pool* poo void MongoAuthorizationPersistence::save(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry) { - auto conn = this->pool->acquire(); - auto collection = (*conn)[this->database_name][this->collection_name]; - - auto filter = make_document(kvp("public_key", public_key)); - auto existing = collection.find_one(filter.view()); + string id = this->hashed_id(public_key); + + auto document = this->get_document_by_id(id); bsoncxx::builder::basic::array schemas; + bool full_access = false; - + if (existing) { auto view = existing->view(); - if (view["full_access"] && view["full_access"].type() == bsoncxx::type::k_bool) { - full_access = view["full_access"].get_bool().value; - } - if (view["allowed_schemas"] && view["allowed_schemas"].type() == bsoncxx::type::k_array) { - string entry_handle = entry.schema.handle(); - for (const auto& item : view["allowed_schemas"].get_array().value) { - if (item.type() != bsoncxx::type::k_document) { - continue; - } - auto item_view = item.get_document().view(); - if (item_view["handle"] && item_view["handle"].type() == bsoncxx::type::k_string && - string(item_view["handle"].get_string().value) == entry_handle) { - continue; - } - schemas.append(item_view); - } - } + full_access = this->read_full_access(view); + this->append_schemas_except(schemas, view, entry.schema.handle()); } + + schemas.append(this->make_schema_item(entry)); - schemas.append(make_schema_item(entry)); - - auto document = make_document(kvp("_id", public_key), - kvp("public_key", public_key), - kvp("full_access", full_access), - kvp("allowed_schemas", schemas)); + auto document = bsoncxx::builder::basic::make_document( + bsoncxx::builder::basic::kvp("_id", id), + bsoncxx::builder::basic::kvp("full_access", full_access), + bsoncxx::builder::basic::kvp("allowed_schemas", schemas) + ); mongocxx::options::replace opts; + opts.upsert(true); + auto reply = collection.replace_one(filter.view(), document.view(), opts); + if (!reply) { - RAISE_ERROR("Failed to save authorization entry for public_key in MongoDB"); + RAISE_ERROR("Failed to save authorization entry in MongoDB"); } } @@ -81,65 +63,33 @@ void MongoAuthorizationPersistence::remove(const string& public_key, const strin auto conn = this->pool->acquire(); auto collection = (*conn)[this->database_name][this->collection_name]; - auto filter = make_document(kvp("public_key", public_key)); + string id = hashed_id(public_key); + auto filter = bsoncxx::builder::basic::make_document(bsoncxx::builder::basic::kvp("_id", id)); auto existing = collection.find_one(filter.view()); - if (!existing) { - return; - } + if (!existing) return; auto view = existing->view(); - bool full_access = false; - if (view["full_access"] && view["full_access"].type() == bsoncxx::type::k_bool) { - full_access = view["full_access"].get_bool().value; - } - bsoncxx::builder::basic::array schemas; - if (view["allowed_schemas"] && view["allowed_schemas"].type() == bsoncxx::type::k_array) { - for (const auto& item : view["allowed_schemas"].get_array().value) { - if (item.type() != bsoncxx::type::k_document) { - continue; - } - auto item_view = item.get_document().view(); - if (item_view["handle"] && item_view["handle"].type() == bsoncxx::type::k_string && - string(item_view["handle"].get_string().value) == handle) { - continue; - } - // Fallback: recompute handle from tokens when the stored document has no handle field. - if ((!item_view["handle"] || item_view["handle"].type() != bsoncxx::type::k_string) && - item_view["tokens"] && item_view["tokens"].type() == bsoncxx::type::k_array) { - vector tokens; - for (const auto& token : item_view["tokens"].get_array().value) { - if (token.type() == bsoncxx::type::k_string) { - tokens.push_back(string(token.get_string().value)); - } - } - if (!tokens.empty() && - atomdb_api_types::AccessPermissionEntry(tokens, false, false).schema.handle() == - handle) { - continue; - } - } - schemas.append(item_view); - } - } + append_schemas_except(schemas, view, handle); - auto document = make_document(kvp("_id", public_key), - kvp("public_key", public_key), - kvp("full_access", full_access), - kvp("allowed_schemas", schemas)); + auto document = bsoncxx::builder::basic::make_document( + bsoncxx::builder::basic::kvp("_id", id), + bsoncxx::builder::basic::kvp("full_access", read_full_access(view)), + bsoncxx::builder::basic::kvp("allowed_schemas", schemas)); mongocxx::options::replace opts; opts.upsert(false); auto reply = collection.replace_one(filter.view(), document.view(), opts); if (!reply) { - RAISE_ERROR("Failed to remove authorization entry for public_key in MongoDB"); + RAISE_ERROR("Failed to remove authorization entry in MongoDB"); } } void MongoAuthorizationPersistence::remove_all(const string& public_key) { auto conn = this->pool->acquire(); auto collection = (*conn)[this->database_name][this->collection_name]; - collection.delete_one(make_document(kvp("public_key", public_key))); + collection.delete_one(bsoncxx::builder::basic::make_document( + bsoncxx::builder::basic::kvp("_id", hashed_id(public_key)))); } // -------------------------------------------------------------------------------- @@ -148,12 +98,63 @@ void MongoAuthorizationPersistence::remove_all(const string& public_key) { bsoncxx::document::value MongoAuthorizationPersistence::make_schema_item( const atomdb_api_types::AccessPermissionEntry& entry) { auto tokens_array = bsoncxx::builder::basic::array{}; - LinkSchema schema = entry.schema; - for (const auto& token : schema.tokenize()) { + for (const auto& token : entry.schema.tokenize()) { tokens_array.append(token); } - return make_document(kvp("handle", entry.schema.handle()), - kvp("tokens", tokens_array), - kvp("read", entry.read), - kvp("write", entry.write)); -} \ No newline at end of file + return bsoncxx::builder::basic::make_document(bsoncxx::builder::basic::kvp("tokens", tokens_array), + bsoncxx::builder::basic::kvp("read", entry.read), + bsoncxx::builder::basic::kvp("write", entry.write)); +} + +string MongoAuthorizationPersistence::hashed_id(const string& public_key) { + return compute_hash((char*) public_key.c_str()); +} + +string MongoAuthorizationPersistence::schema_handle_from_item(bsoncxx::document::view item) { + if (!item["tokens"] || item["tokens"].type() != bsoncxx::type::k_array) { + return ""; + } + vector tokens; + for (const auto& token : item["tokens"].get_array().value) { + if (token.type() == bsoncxx::type::k_string) { + tokens.push_back(string(token.get_string().value)); + } + } + if (tokens.empty()) { + return ""; + } + return atomdb_api_types::AccessPermissionEntry(tokens, false, false).schema.handle(); +} + +bool MongoAuthorizationPersistence::read_full_access(bsoncxx::document::view view) { + if (view["full_access"] && view["full_access"].type() == bsoncxx::type::k_bool) { + return view["full_access"].get_bool().value; + } + return false; +} + +void MongoAuthorizationPersistence::append_schemas_except(bsoncxx::builder::basic::array& schemas, + bsoncxx::document::view view, + const string& handle_to_skip) { + if (!view["allowed_schemas"] || view["allowed_schemas"].type() != bsoncxx::type::k_array) { + return; + } + for (const auto& item : view["allowed_schemas"].get_array().value) { + if (item.type() != bsoncxx::type::k_document) { + continue; + } + auto item_view = item.get_document().view(); + if (schema_handle_from_item(item_view) == handle_to_skip) { + continue; + } + schemas.append(item_view); + } +} + +optional MongoAuthorizationPersistence::get_document_by_id( + const string& id) { + auto conn = this->pool->acquire(); + auto collection = (*conn)[this->database_name][this->collection_name]; + auto reply = collection.find_one( + bsoncxx::v_noabi::builder::basic::make_document(bsoncxx::v_noabi::builder::basic::kvp("_id", id))); +} diff --git a/src/atomdb/auth/MongoAuthorizationPersistence.h b/src/atomdb/auth/MongoAuthorizationPersistence.h index 32acff28..b89e9795 100644 --- a/src/atomdb/auth/MongoAuthorizationPersistence.h +++ b/src/atomdb/auth/MongoAuthorizationPersistence.h @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -13,20 +12,18 @@ using namespace std; namespace atomdb { -/** @brief MongoDB-backed persistence, built by AtomDBFactory for RedisMongoDB backends. */ class MongoAuthorizationPersistence : public AuthorizationPersistence { public: /** - * @param pool Mongo pool owned by the backend. - * @param database_name Mongo database name. - * @param collection_name access_permissions collection name (hardcoded static on the backend). + * @param pool Mongo pool + * @param database_name Mongo database name + * @param collection_name access_permissions collection name */ MongoAuthorizationPersistence(mongocxx::pool* pool, const string& database_name, const string& collection_name); - void save(const string& public_key, - const atomdb_api_types::AccessPermissionEntry& entry) override; + void save(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry) override; void remove(const string& public_key, const string& handle) override; void remove_all(const string& public_key) override; @@ -35,8 +32,14 @@ class MongoAuthorizationPersistence : public AuthorizationPersistence { string database_name; string collection_name; - bsoncxx::document::value make_schema_item( - const atomdb_api_types::AccessPermissionEntry& entry); + bsoncxx::document::value make_schema_item(const atomdb_api_types::AccessPermissionEntry& entry); + string hashed_id(const string& public_key); + string schema_handle_from_item(bsoncxx::document::view item); + bool read_full_access(bsoncxx::document::view view); + void append_schemas_except(bsoncxx::builder::basic::array& schemas, + bsoncxx::document::view view, + const string& handle_to_skip); + optional get_document_by_id(const string& id); }; } // namespace atomdb diff --git a/src/tests/cpp/BUILD b/src/tests/cpp/BUILD index 70815464..bc4b1494 100644 --- a/src/tests/cpp/BUILD +++ b/src/tests/cpp/BUILD @@ -773,6 +773,7 @@ cc_test( linkstatic = 1, deps = [ "//atomdb:atomdb_singleton", + "//atomdb/auth:mongo_authorization_persistence", "//tests/cpp/test_commons:mock_animals_data_lib", "//tests/cpp/test_commons:test_atomdb_json_config", "@com_github_google_googletest//:gtest_main", diff --git a/src/tests/cpp/authorization_test.cc b/src/tests/cpp/authorization_test.cc index 0b3f055e..d410b911 100644 --- a/src/tests/cpp/authorization_test.cc +++ b/src/tests/cpp/authorization_test.cc @@ -141,8 +141,7 @@ TEST(AuthorizationManagementTest, UnregisteredKeyIsDenied) { auto link = db->get_link(link_handle); ASSERT_NE(link, nullptr); EXPECT_FALSE(management.is_authorized(*link, "unknown", AuthorizationOperation::READ)); - EXPECT_FALSE( - management.is_authorized(link_handle, "unknown", AuthorizationOperation::READ, *db)); + EXPECT_FALSE(management.is_authorized(link_handle, "unknown", AuthorizationOperation::READ, *db)); } TEST(AuthorizationManagementTest, AuthorizeThenReadAndWriteFlags) { diff --git a/src/tests/cpp/redis_mongodb_test.cc b/src/tests/cpp/redis_mongodb_test.cc index 6b744fb1..6faee3ec 100644 --- a/src/tests/cpp/redis_mongodb_test.cc +++ b/src/tests/cpp/redis_mongodb_test.cc @@ -20,6 +20,7 @@ #include "Merger.h" #include "MettaMapping.h" #include "MockAnimalsData.h" +#include "MongoAuthorizationPersistence.h" #include "Node.h" #include "RedisMongoDB.h" #include "TestAtomDBJsonConfig.h" @@ -1481,6 +1482,58 @@ TEST_F(RedisMongoDBTest, GetAccessPermissionsRejectsInvalidDocument) { collection.delete_many({}); } +TEST_F(RedisMongoDBTest, MongoAuthorizationPersistenceRoundTrip) { + using bsoncxx::builder::basic::kvp; + using bsoncxx::builder::basic::make_document; + + auto pool = db->get_mongo_pool(); + auto conn = pool->acquire(); + auto collection = + (*conn)[RedisMongoDB::MONGODB_DB_NAME][RedisMongoDB::MONGODB_ACCESS_PERMISSIONS_COLLECTION_NAME]; + collection.delete_many({}); + + MongoAuthorizationPersistence persistence( + pool, RedisMongoDB::MONGODB_DB_NAME, RedisMongoDB::MONGODB_ACCESS_PERMISSIONS_COLLECTION_NAME); + + vector tokens = { + "LINK_TEMPLATE", "Expression", "2", "NODE", "Symbol", "Similarity", "VARIABLE", "VARIABLE"}; + AccessPermissionEntry entry(tokens, true, false); + persistence.save("key_reader", entry); + + string id = compute_hash((char*) "key_reader"); + auto stored = collection.find_one(make_document(kvp("_id", id))); + ASSERT_TRUE(stored); + EXPECT_FALSE(stored->view()["public_key"]); + EXPECT_EQ(string(stored->view()["_id"].get_string().value), id); + EXPECT_FALSE(stored->view()["full_access"].get_bool().value); + + auto docs = db->get_access_permissions(PublicKey("key_reader")); + ASSERT_EQ(docs.size(), 1u); + EXPECT_EQ(docs[0].public_key, "key_reader"); + EXPECT_FALSE(docs[0].full_access); + ASSERT_EQ(docs[0].entries.size(), 1u); + EXPECT_TRUE(docs[0].entries[0].read); + EXPECT_FALSE(docs[0].entries[0].write); + EXPECT_EQ(docs[0].entries[0].schema.handle(), entry.schema.handle()); + + persistence.save("key_reader", AccessPermissionEntry(tokens, false, true)); + docs = db->get_access_permissions(PublicKey("key_reader")); + ASSERT_EQ(docs.size(), 1u); + ASSERT_EQ(docs[0].entries.size(), 1u); + EXPECT_FALSE(docs[0].entries[0].read); + EXPECT_TRUE(docs[0].entries[0].write); + + persistence.remove("key_reader", entry.schema.handle()); + docs = db->get_access_permissions(PublicKey("key_reader")); + ASSERT_EQ(docs.size(), 1u); + EXPECT_TRUE(docs[0].entries.empty()); + + persistence.remove_all("key_reader"); + EXPECT_TRUE(db->get_access_permissions(PublicKey("key_reader")).empty()); + + collection.delete_many({}); +} + TEST_F(RedisMongoDBTest, IsProtectedWhenPersistedConfigIsTrue) { using bsoncxx::builder::basic::kvp; using bsoncxx::builder::basic::make_document; From 3309604790e9aef970e52db54320318f58fe2647 Mon Sep 17 00:00:00 2001 From: marcocapozzoli Date: Tue, 18 Aug 2026 11:54:22 -0300 Subject: [PATCH 06/16] WIP --- src/atomdb/auth/AuthorizationManifest.cc | 7 +- src/atomdb/auth/AuthorizationManifest.h | 13 ++- src/atomdb/auth/AuthorizationPersistence.h | 6 +- .../auth/MongoAuthorizationPersistence.cc | 100 +++++++++++------- .../auth/MongoAuthorizationPersistence.h | 10 +- 5 files changed, 81 insertions(+), 55 deletions(-) diff --git a/src/atomdb/auth/AuthorizationManifest.cc b/src/atomdb/auth/AuthorizationManifest.cc index 42f16cac..0c79db52 100644 --- a/src/atomdb/auth/AuthorizationManifest.cc +++ b/src/atomdb/auth/AuthorizationManifest.cc @@ -60,7 +60,9 @@ void AuthorizationManifest::remove(const atomdb_api_types::PublicKey& public_key } } -void AuthorizationManifest::remove_all(const atomdb_api_types::PublicKey& public_key) { this->documents.erase(public_key); } +void AuthorizationManifest::remove_all(const atomdb_api_types::PublicKey& public_key) { + this->documents.erase(public_key); +} bool AuthorizationManifest::is_registered(const atomdb_api_types::PublicKey& public_key) const { return this->documents.find(public_key) != this->documents.end(); @@ -97,7 +99,8 @@ atomdb_api_types::AccessPermissionDocument* AuthorizationManifest::find_document const atomdb_api_types::PublicKey& public_key, const string& caller) { auto it = this->documents.find(public_key); if (it == this->documents.end()) { - LOG_INFO("AuthorizationManifest::" << caller << "() called for unregistered public_key: " << public_key); + LOG_INFO("AuthorizationManifest::" << caller + << "() called for unregistered public_key: " << public_key); return nullptr; } return &it->second; diff --git a/src/atomdb/auth/AuthorizationManifest.h b/src/atomdb/auth/AuthorizationManifest.h index a54a12ea..e54e4142 100644 --- a/src/atomdb/auth/AuthorizationManifest.h +++ b/src/atomdb/auth/AuthorizationManifest.h @@ -26,7 +26,8 @@ class AuthorizationManifest { void set(const atomdb_api_types::AccessPermissionDocument& document); /** @brief Adds one entry to public_key, creating the document if needed. */ - void add(const atomdb_api_types::PublicKey& public_key, const atomdb_api_types::AccessPermissionEntry& entry); + void add(const atomdb_api_types::PublicKey& public_key, + const atomdb_api_types::AccessPermissionEntry& entry); /** @brief Removes the entry whose schema.handle() == handle from public_key. No-op if absent. */ void remove(const atomdb_api_types::PublicKey& public_key, const string& handle); @@ -41,15 +42,17 @@ class AuthorizationManifest { bool full_access(const atomdb_api_types::PublicKey& public_key); /** @brief Returns the entries for public_key, or an empty vector if not registered. */ - const vector& entries(const atomdb_api_types::PublicKey& public_key); + const vector& entries( + const atomdb_api_types::PublicKey& public_key); private: map documents; static const vector EMPTY_ENTRIES; - void create_document(const atomdb_api_types::PublicKey& public_key, const atomdb_api_types::AccessPermissionEntry& entry); - atomdb_api_types::AccessPermissionDocument* find_document(const atomdb_api_types::PublicKey& public_key, - const string& caller); + void create_document(const atomdb_api_types::PublicKey& public_key, + const atomdb_api_types::AccessPermissionEntry& entry); + atomdb_api_types::AccessPermissionDocument* find_document( + const atomdb_api_types::PublicKey& public_key, const string& caller); }; } // namespace auth diff --git a/src/atomdb/auth/AuthorizationPersistence.h b/src/atomdb/auth/AuthorizationPersistence.h index df053da3..c3baa5b0 100644 --- a/src/atomdb/auth/AuthorizationPersistence.h +++ b/src/atomdb/auth/AuthorizationPersistence.h @@ -15,10 +15,10 @@ class AuthorizationPersistence { /** @brief Persists one entry under public_key (creating the document if needed). */ virtual void save(const atomdb_api_types::PublicKey& public_key, - const atomdb_api_types::AccessPermissionEntry& entry) = 0; + const atomdb_api_types::AccessPermissionDocument& entry) = 0; - /** @brief Removes the entry identified by handle from public_key's document. */ - virtual void remove(const atomdb_api_types::PublicKey& public_key, const string& handle) = 0; + /** @brief Removes the entry identified */ + virtual void remove(const atomdb_api_types::PublicKey& public_key, const atomdb_api_types::AccessPermissionEntry& entry) = 0; /** @brief Removes the whole document for public_key. */ virtual void remove_all(const atomdb_api_types::PublicKey& public_key) = 0; diff --git a/src/atomdb/auth/MongoAuthorizationPersistence.cc b/src/atomdb/auth/MongoAuthorizationPersistence.cc index 4e311d0f..6bb4e9b3 100644 --- a/src/atomdb/auth/MongoAuthorizationPersistence.cc +++ b/src/atomdb/auth/MongoAuthorizationPersistence.cc @@ -1,5 +1,6 @@ #include "MongoAuthorizationPersistence.h" +#include "Hasher.h" #include "Utils.h" #include "expression_hasher.h" @@ -24,46 +25,55 @@ MongoAuthorizationPersistence::MongoAuthorizationPersistence(mongocxx::pool* poo // -------------------------------------------------------------------------------- // Public methods -void MongoAuthorizationPersistence::save(const string& public_key, +void MongoAuthorizationPersistence::save(const atomdb_api_types::PublicKey& public_key, const atomdb_api_types::AccessPermissionEntry& entry) { - string id = this->hashed_id(public_key); - - auto document = this->get_document_by_id(id); + auto conn = this->pool->acquire(); + auto collection = (*conn)[this->database_name][this->collection_name]; - bsoncxx::builder::basic::array schemas; - - bool full_access = false; - - if (existing) { - auto view = existing->view(); - full_access = this->read_full_access(view); - this->append_schemas_except(schemas, view, entry.schema.handle()); - } - - schemas.append(this->make_schema_item(entry)); + for (const auto& key : public_key.keys) { + auto access_document = this->get_document(collection, public_key); + + string id; + string public_key_; + bool full_access = false; + auto schemas = bsoncxx::builder::basic::array{}; + + if (access_document) { + id = Hasher::plain_string_hash(access_document->public_key); + public_key_ = access_document->public_key; + full_access = access_document->full_access; + for (const auto& document_entry : access_document->entries) { + if (document_entry.schema.handle() == entry.schema.handle() && + document_entry.read == entry.read && document_entry.write == entry.write) { + schemas.append(this->make_schema_item(document_entry)); + } else { + schemas.append(this->make_schema_item(entry)); + } + } + } else { + id = Hasher::plain_string_hash(public_key); + public_key_ = public_key; + schemas.append(this->make_schema_item(entry)); + } - auto document = bsoncxx::builder::basic::make_document( - bsoncxx::builder::basic::kvp("_id", id), - bsoncxx::builder::basic::kvp("full_access", full_access), - bsoncxx::builder::basic::kvp("allowed_schemas", schemas) - ); + auto reply = collection.replace_one(bsoncxx::builder::basic::make_document( + bsoncxx::builder::basic::kvp("_id", id), + bsoncxx::builder::basic::kvp("public_key", public_key_), + bsoncxx::builder::basic::kvp("full_access", full_access), + bsoncxx::builder::basic::kvp("allowed_schemas", schemas))); - mongocxx::options::replace opts; - - opts.upsert(true); - - auto reply = collection.replace_one(filter.view(), document.view(), opts); - - if (!reply) { - RAISE_ERROR("Failed to save authorization entry in MongoDB"); + if (!reply) { + RAISE_ERROR("Failed to update authorization entry in MongoDB"); + } } + } void MongoAuthorizationPersistence::remove(const string& public_key, const string& handle) { auto conn = this->pool->acquire(); auto collection = (*conn)[this->database_name][this->collection_name]; - string id = hashed_id(public_key); + string id = Hasher::plain_string_hash(public_key); auto filter = bsoncxx::builder::basic::make_document(bsoncxx::builder::basic::kvp("_id", id)); auto existing = collection.find_one(filter.view()); if (!existing) return; @@ -89,7 +99,7 @@ void MongoAuthorizationPersistence::remove_all(const string& public_key) { auto conn = this->pool->acquire(); auto collection = (*conn)[this->database_name][this->collection_name]; collection.delete_one(bsoncxx::builder::basic::make_document( - bsoncxx::builder::basic::kvp("_id", hashed_id(public_key)))); + bsoncxx::builder::basic::kvp("_id", Hasher::plain_string_hash(public_key)))); } // -------------------------------------------------------------------------------- @@ -106,10 +116,6 @@ bsoncxx::document::value MongoAuthorizationPersistence::make_schema_item( bsoncxx::builder::basic::kvp("write", entry.write)); } -string MongoAuthorizationPersistence::hashed_id(const string& public_key) { - return compute_hash((char*) public_key.c_str()); -} - string MongoAuthorizationPersistence::schema_handle_from_item(bsoncxx::document::view item) { if (!item["tokens"] || item["tokens"].type() != bsoncxx::type::k_array) { return ""; @@ -151,10 +157,24 @@ void MongoAuthorizationPersistence::append_schemas_except(bsoncxx::builder::basi } } -optional MongoAuthorizationPersistence::get_document_by_id( - const string& id) { - auto conn = this->pool->acquire(); - auto collection = (*conn)[this->database_name][this->collection_name]; - auto reply = collection.find_one( - bsoncxx::v_noabi::builder::basic::make_document(bsoncxx::v_noabi::builder::basic::kvp("_id", id))); +shared_ptr MongoAuthorizationPersistence::get_document( + mongocxx::collection collection, const string& public_key) { + auto reply = collection.find_one(bsoncxx::v_noabi::builder::basic::make_document( + bsoncxx::v_noabi::builder::basic::kvp("_id", Hasher::plain_string_hash(public_key)))); + + if (!reply) return nullptr; + + auto document_json = nlohmann::json::parse(bsoncxx::to_json(reply->value().view())); + + vector entries; + for (const auto& item : document_json["allowed_schemas"]) { + vector tokens; + for (const auto& token : item["tokens"]) { + tokens.push_back(token.get()); + } + entries.emplace_back(tokens, item["read"].get(), item["write"].get()); + } + + return make_shared( + public_key, document_json["full_access"].get(), entries); } diff --git a/src/atomdb/auth/MongoAuthorizationPersistence.h b/src/atomdb/auth/MongoAuthorizationPersistence.h index b89e9795..4e1c2867 100644 --- a/src/atomdb/auth/MongoAuthorizationPersistence.h +++ b/src/atomdb/auth/MongoAuthorizationPersistence.h @@ -23,9 +23,9 @@ class MongoAuthorizationPersistence : public AuthorizationPersistence { const string& database_name, const string& collection_name); - void save(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry) override; - void remove(const string& public_key, const string& handle) override; - void remove_all(const string& public_key) override; + void save(const atomdb_api_types::PublicKey& public_key, const atomdb_api_types::AccessPermissionEntry& entry) override; + void remove(const atomdb_api_types::PublicKey& public_key, const atomdb_api_types::AccessPermissionEntry& entry) override; + void remove_all(const atomdb_api_types::PublicKey& public_key) override; private: mongocxx::pool* pool; @@ -33,13 +33,13 @@ class MongoAuthorizationPersistence : public AuthorizationPersistence { string collection_name; bsoncxx::document::value make_schema_item(const atomdb_api_types::AccessPermissionEntry& entry); - string hashed_id(const string& public_key); string schema_handle_from_item(bsoncxx::document::view item); bool read_full_access(bsoncxx::document::view view); void append_schemas_except(bsoncxx::builder::basic::array& schemas, bsoncxx::document::view view, const string& handle_to_skip); - optional get_document_by_id(const string& id); + atomdb_api_types::AccessPermissionDocument get_document(mongocxx::collection collection, + const string& public_key); }; } // namespace atomdb From dd4e0f56f254f696b129876fca8fe88fbac9361f Mon Sep 17 00:00:00 2001 From: marcocapozzoli Date: Tue, 18 Aug 2026 13:36:45 -0300 Subject: [PATCH 07/16] Refactor MongoAuthorizationPersistence --- src/atomdb/auth/AuthorizationManifest.cc | 1 - src/atomdb/auth/AuthorizationManifest.h | 3 - src/atomdb/auth/AuthorizationPersistence.h | 3 +- src/atomdb/auth/BUILD | 30 ++-- .../auth/MongoAuthorizationPersistence.cc | 148 ++++++++---------- .../auth/MongoAuthorizationPersistence.h | 19 ++- 6 files changed, 92 insertions(+), 112 deletions(-) diff --git a/src/atomdb/auth/AuthorizationManifest.cc b/src/atomdb/auth/AuthorizationManifest.cc index 0c79db52..9b66d9bc 100644 --- a/src/atomdb/auth/AuthorizationManifest.cc +++ b/src/atomdb/auth/AuthorizationManifest.cc @@ -7,7 +7,6 @@ #include "Utils.h" using namespace atomdb; -using namespace auth; const vector AuthorizationManifest::EMPTY_ENTRIES; diff --git a/src/atomdb/auth/AuthorizationManifest.h b/src/atomdb/auth/AuthorizationManifest.h index e54e4142..76505820 100644 --- a/src/atomdb/auth/AuthorizationManifest.h +++ b/src/atomdb/auth/AuthorizationManifest.h @@ -10,8 +10,6 @@ using namespace std; namespace atomdb { -namespace auth { - /** * @brief In-RAM image of the whole access_permissions collection. * @@ -55,5 +53,4 @@ class AuthorizationManifest { const atomdb_api_types::PublicKey& public_key, const string& caller); }; -} // namespace auth } // namespace atomdb diff --git a/src/atomdb/auth/AuthorizationPersistence.h b/src/atomdb/auth/AuthorizationPersistence.h index c3baa5b0..db7ed7a9 100644 --- a/src/atomdb/auth/AuthorizationPersistence.h +++ b/src/atomdb/auth/AuthorizationPersistence.h @@ -18,7 +18,8 @@ class AuthorizationPersistence { const atomdb_api_types::AccessPermissionDocument& entry) = 0; /** @brief Removes the entry identified */ - virtual void remove(const atomdb_api_types::PublicKey& public_key, const atomdb_api_types::AccessPermissionEntry& entry) = 0; + virtual void remove(const atomdb_api_types::PublicKey& public_key, + const atomdb_api_types::AccessPermissionEntry& entry) = 0; /** @brief Removes the whole document for public_key. */ virtual void remove_all(const atomdb_api_types::PublicKey& public_key) = 0; diff --git a/src/atomdb/auth/BUILD b/src/atomdb/auth/BUILD index 74ba6686..9da9f015 100644 --- a/src/atomdb/auth/BUILD +++ b/src/atomdb/auth/BUILD @@ -2,28 +2,11 @@ load("@rules_cc//cc:cc_library.bzl", "cc_library") package(default_visibility = ["//visibility:public"]) -cc_library( - name = "authorization_types", - srcs = [ - "AuthorizationManifest.cc", - ], - hdrs = [ - "AuthorizationManifest.h", - ], - includes = ["."], - deps = [ - "//atomdb:atomdb_api_types", - "//commons:commons_lib", - "//commons/atoms:atoms_lib", - ], -) - cc_library( name = "authorization_persistence", hdrs = ["AuthorizationPersistence.h"], includes = ["."], deps = [ - ":authorization_types", "//atomdb:atomdb_api_types", ], ) @@ -35,7 +18,6 @@ cc_library( includes = ["."], deps = [ ":authorization_persistence", - ":authorization_types", "//atomdb:atomdb_api_types", "//commons:commons_lib", "//commons/atoms:atoms_lib", @@ -57,3 +39,15 @@ cc_library( "//commons/atoms:atoms_lib", ], ) + +cc_library( + name = "authorization_manifest", + srcs = ["AuthorizationManifest.cc"], + hdrs = ["AuthorizationManifest.h"], + includes = ["."], + deps = [ + "//atomdb:atomdb_api_types", + "//commons:commons_lib", + "//commons/atoms:atoms_lib", + ], +) diff --git a/src/atomdb/auth/MongoAuthorizationPersistence.cc b/src/atomdb/auth/MongoAuthorizationPersistence.cc index 6bb4e9b3..2d3e6efd 100644 --- a/src/atomdb/auth/MongoAuthorizationPersistence.cc +++ b/src/atomdb/auth/MongoAuthorizationPersistence.cc @@ -31,7 +31,7 @@ void MongoAuthorizationPersistence::save(const atomdb_api_types::PublicKey& publ auto collection = (*conn)[this->database_name][this->collection_name]; for (const auto& key : public_key.keys) { - auto access_document = this->get_document(collection, public_key); + auto access_document = this->get_document(collection, key); string id; string public_key_; @@ -39,73 +39,104 @@ void MongoAuthorizationPersistence::save(const atomdb_api_types::PublicKey& publ auto schemas = bsoncxx::builder::basic::array{}; if (access_document) { - id = Hasher::plain_string_hash(access_document->public_key); - public_key_ = access_document->public_key; + id = Hasher::plain_string_hash(access_document->access_key); + public_key_ = access_document->access_key; full_access = access_document->full_access; + + bool entry_exists = false; + for (const auto& document_entry : access_document->entries) { - if (document_entry.schema.handle() == entry.schema.handle() && - document_entry.read == entry.read && document_entry.write == entry.write) { - schemas.append(this->make_schema_item(document_entry)); + if (document_entry.schema.handle() == entry.schema.handle()) { + schemas.append(this->entry_to_document(entry)); + entry_exists = true; } else { - schemas.append(this->make_schema_item(entry)); + schemas.append(this->entry_to_document(document_entry)); } } + + if (!entry_exists) { + schemas.append(this->entry_to_document(entry)); + } } else { - id = Hasher::plain_string_hash(public_key); - public_key_ = public_key; - schemas.append(this->make_schema_item(entry)); + id = Hasher::plain_string_hash(key); + public_key_ = key; + schemas.append(this->entry_to_document(entry)); } - auto reply = collection.replace_one(bsoncxx::builder::basic::make_document( + auto new_access_document = bsoncxx::builder::basic::make_document( bsoncxx::builder::basic::kvp("_id", id), bsoncxx::builder::basic::kvp("public_key", public_key_), bsoncxx::builder::basic::kvp("full_access", full_access), - bsoncxx::builder::basic::kvp("allowed_schemas", schemas))); + bsoncxx::builder::basic::kvp("allowed_schemas", schemas)); + + bsoncxx::builder::stream::document filter; + filter << "_id" << id; + + mongocxx::options::replace opts; + opts.upsert(true); + + auto reply = collection.replace_one(filter, new_access_document, opts); if (!reply) { RAISE_ERROR("Failed to update authorization entry in MongoDB"); } } - } -void MongoAuthorizationPersistence::remove(const string& public_key, const string& handle) { +void MongoAuthorizationPersistence::remove(const atomdb_api_types::PublicKey& public_key, + const atomdb_api_types::AccessPermissionEntry& entry) { auto conn = this->pool->acquire(); auto collection = (*conn)[this->database_name][this->collection_name]; - string id = Hasher::plain_string_hash(public_key); - auto filter = bsoncxx::builder::basic::make_document(bsoncxx::builder::basic::kvp("_id", id)); - auto existing = collection.find_one(filter.view()); - if (!existing) return; - - auto view = existing->view(); - bsoncxx::builder::basic::array schemas; - append_schemas_except(schemas, view, handle); - - auto document = bsoncxx::builder::basic::make_document( - bsoncxx::builder::basic::kvp("_id", id), - bsoncxx::builder::basic::kvp("full_access", read_full_access(view)), - bsoncxx::builder::basic::kvp("allowed_schemas", schemas)); - - mongocxx::options::replace opts; - opts.upsert(false); - auto reply = collection.replace_one(filter.view(), document.view(), opts); - if (!reply) { - RAISE_ERROR("Failed to remove authorization entry in MongoDB"); + for (const auto& key : public_key.keys) { + auto access_document = this->get_document(collection, key); + + if (!access_document) continue; + + auto schemas = bsoncxx::builder::basic::array{}; + for (const auto& document_entry : access_document->entries) { + if (document_entry.schema.handle() == entry.schema.handle()) { + continue; // Skip the entry to remove + } else { + schemas.append(this->entry_to_document(document_entry)); + } + } + + string id = Hasher::plain_string_hash(access_document->access_key); + + auto new_access_document = bsoncxx::builder::basic::make_document( + bsoncxx::builder::basic::kvp("_id", id), + bsoncxx::builder::basic::kvp("public_key", access_document->access_key), + bsoncxx::builder::basic::kvp("full_access", access_document->full_access), + bsoncxx::builder::basic::kvp("allowed_schemas", schemas)); + + bsoncxx::builder::stream::document filter; + filter << "_id" << id; + + auto reply = collection.replace_one(filter, new_access_document); + + if (!reply) { + RAISE_ERROR("Failed to update authorization entry in MongoDB"); + } } } -void MongoAuthorizationPersistence::remove_all(const string& public_key) { +void MongoAuthorizationPersistence::remove_all(const atomdb_api_types::PublicKey& public_key) { auto conn = this->pool->acquire(); auto collection = (*conn)[this->database_name][this->collection_name]; - collection.delete_one(bsoncxx::builder::basic::make_document( - bsoncxx::builder::basic::kvp("_id", Hasher::plain_string_hash(public_key)))); + for (const auto& key : public_key.keys) { + auto reply = collection.delete_one(bsoncxx::builder::basic::make_document( + bsoncxx::builder::basic::kvp("_id", Hasher::plain_string_hash(key)))); + if (!reply) { + RAISE_ERROR("Failed to remove authorization document from MongoDB"); + } + } } // -------------------------------------------------------------------------------- // Private methods -bsoncxx::document::value MongoAuthorizationPersistence::make_schema_item( +bsoncxx::document::value MongoAuthorizationPersistence::entry_to_document( const atomdb_api_types::AccessPermissionEntry& entry) { auto tokens_array = bsoncxx::builder::basic::array{}; for (const auto& token : entry.schema.tokenize()) { @@ -116,49 +147,8 @@ bsoncxx::document::value MongoAuthorizationPersistence::make_schema_item( bsoncxx::builder::basic::kvp("write", entry.write)); } -string MongoAuthorizationPersistence::schema_handle_from_item(bsoncxx::document::view item) { - if (!item["tokens"] || item["tokens"].type() != bsoncxx::type::k_array) { - return ""; - } - vector tokens; - for (const auto& token : item["tokens"].get_array().value) { - if (token.type() == bsoncxx::type::k_string) { - tokens.push_back(string(token.get_string().value)); - } - } - if (tokens.empty()) { - return ""; - } - return atomdb_api_types::AccessPermissionEntry(tokens, false, false).schema.handle(); -} - -bool MongoAuthorizationPersistence::read_full_access(bsoncxx::document::view view) { - if (view["full_access"] && view["full_access"].type() == bsoncxx::type::k_bool) { - return view["full_access"].get_bool().value; - } - return false; -} - -void MongoAuthorizationPersistence::append_schemas_except(bsoncxx::builder::basic::array& schemas, - bsoncxx::document::view view, - const string& handle_to_skip) { - if (!view["allowed_schemas"] || view["allowed_schemas"].type() != bsoncxx::type::k_array) { - return; - } - for (const auto& item : view["allowed_schemas"].get_array().value) { - if (item.type() != bsoncxx::type::k_document) { - continue; - } - auto item_view = item.get_document().view(); - if (schema_handle_from_item(item_view) == handle_to_skip) { - continue; - } - schemas.append(item_view); - } -} - shared_ptr MongoAuthorizationPersistence::get_document( - mongocxx::collection collection, const string& public_key) { + mongocxx::collection& collection, const string& public_key) { auto reply = collection.find_one(bsoncxx::v_noabi::builder::basic::make_document( bsoncxx::v_noabi::builder::basic::kvp("_id", Hasher::plain_string_hash(public_key)))); diff --git a/src/atomdb/auth/MongoAuthorizationPersistence.h b/src/atomdb/auth/MongoAuthorizationPersistence.h index 4e1c2867..9313a340 100644 --- a/src/atomdb/auth/MongoAuthorizationPersistence.h +++ b/src/atomdb/auth/MongoAuthorizationPersistence.h @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include #include @@ -23,8 +25,10 @@ class MongoAuthorizationPersistence : public AuthorizationPersistence { const string& database_name, const string& collection_name); - void save(const atomdb_api_types::PublicKey& public_key, const atomdb_api_types::AccessPermissionEntry& entry) override; - void remove(const atomdb_api_types::PublicKey& public_key, const atomdb_api_types::AccessPermissionEntry& entry) override; + void save(const atomdb_api_types::PublicKey& public_key, + const atomdb_api_types::AccessPermissionEntry& entry) override; + void remove(const atomdb_api_types::PublicKey& public_key, + const atomdb_api_types::AccessPermissionEntry& entry) override; void remove_all(const atomdb_api_types::PublicKey& public_key) override; private: @@ -32,14 +36,9 @@ class MongoAuthorizationPersistence : public AuthorizationPersistence { string database_name; string collection_name; - bsoncxx::document::value make_schema_item(const atomdb_api_types::AccessPermissionEntry& entry); - string schema_handle_from_item(bsoncxx::document::view item); - bool read_full_access(bsoncxx::document::view view); - void append_schemas_except(bsoncxx::builder::basic::array& schemas, - bsoncxx::document::view view, - const string& handle_to_skip); - atomdb_api_types::AccessPermissionDocument get_document(mongocxx::collection collection, - const string& public_key); + bsoncxx::document::value entry_to_document(const atomdb_api_types::AccessPermissionEntry& entry); + shared_ptr get_document(mongocxx::collection& collection, + const string& public_key); }; } // namespace atomdb From 2104e9547ef5c74c9ff14b584319002cc122f1ee Mon Sep 17 00:00:00 2001 From: marcocapozzoli Date: Tue, 18 Aug 2026 15:45:47 -0300 Subject: [PATCH 08/16] Change public_key from object string --- src/atomdb/auth/AuthorizationPersistence.h | 6 +- .../auth/MongoAuthorizationPersistence.cc | 130 +++++++++--------- .../auth/MongoAuthorizationPersistence.h | 8 +- 3 files changed, 68 insertions(+), 76 deletions(-) diff --git a/src/atomdb/auth/AuthorizationPersistence.h b/src/atomdb/auth/AuthorizationPersistence.h index db7ed7a9..ba366505 100644 --- a/src/atomdb/auth/AuthorizationPersistence.h +++ b/src/atomdb/auth/AuthorizationPersistence.h @@ -14,15 +14,15 @@ class AuthorizationPersistence { virtual ~AuthorizationPersistence() = default; /** @brief Persists one entry under public_key (creating the document if needed). */ - virtual void save(const atomdb_api_types::PublicKey& public_key, + virtual void save(const string& public_key, const atomdb_api_types::AccessPermissionDocument& entry) = 0; /** @brief Removes the entry identified */ - virtual void remove(const atomdb_api_types::PublicKey& public_key, + virtual void remove(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry) = 0; /** @brief Removes the whole document for public_key. */ - virtual void remove_all(const atomdb_api_types::PublicKey& public_key) = 0; + virtual void remove_all(const string& public_key) = 0; }; } // namespace atomdb diff --git a/src/atomdb/auth/MongoAuthorizationPersistence.cc b/src/atomdb/auth/MongoAuthorizationPersistence.cc index 2d3e6efd..0d3fe2a1 100644 --- a/src/atomdb/auth/MongoAuthorizationPersistence.cc +++ b/src/atomdb/auth/MongoAuthorizationPersistence.cc @@ -25,111 +25,105 @@ MongoAuthorizationPersistence::MongoAuthorizationPersistence(mongocxx::pool* poo // -------------------------------------------------------------------------------- // Public methods -void MongoAuthorizationPersistence::save(const atomdb_api_types::PublicKey& public_key, +void MongoAuthorizationPersistence::save(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry) { auto conn = this->pool->acquire(); auto collection = (*conn)[this->database_name][this->collection_name]; - for (const auto& key : public_key.keys) { - auto access_document = this->get_document(collection, key); + auto access_document = this->get_document(collection, public_key); - string id; - string public_key_; - bool full_access = false; - auto schemas = bsoncxx::builder::basic::array{}; + string id; + string public_key_; + bool full_access = false; + auto schemas = bsoncxx::builder::basic::array{}; - if (access_document) { - id = Hasher::plain_string_hash(access_document->access_key); - public_key_ = access_document->access_key; - full_access = access_document->full_access; + if (access_document) { + id = Hasher::plain_string_hash(access_document->access_key); + public_key_ = access_document->access_key; + full_access = access_document->full_access; - bool entry_exists = false; + bool entry_exists = false; - for (const auto& document_entry : access_document->entries) { - if (document_entry.schema.handle() == entry.schema.handle()) { - schemas.append(this->entry_to_document(entry)); - entry_exists = true; - } else { - schemas.append(this->entry_to_document(document_entry)); - } - } - - if (!entry_exists) { + for (const auto& document_entry : access_document->entries) { + if (document_entry.schema.handle() == entry.schema.handle()) { schemas.append(this->entry_to_document(entry)); + entry_exists = true; + } else { + schemas.append(this->entry_to_document(document_entry)); } - } else { - id = Hasher::plain_string_hash(key); - public_key_ = key; + } + + if (!entry_exists) { schemas.append(this->entry_to_document(entry)); } + } else { + id = Hasher::plain_string_hash(public_key); + public_key_ = public_key; + schemas.append(this->entry_to_document(entry)); + } - auto new_access_document = bsoncxx::builder::basic::make_document( - bsoncxx::builder::basic::kvp("_id", id), - bsoncxx::builder::basic::kvp("public_key", public_key_), - bsoncxx::builder::basic::kvp("full_access", full_access), - bsoncxx::builder::basic::kvp("allowed_schemas", schemas)); + auto new_access_document = + bsoncxx::builder::basic::make_document(bsoncxx::builder::basic::kvp("_id", id), + bsoncxx::builder::basic::kvp("public_key", public_key_), + bsoncxx::builder::basic::kvp("full_access", full_access), + bsoncxx::builder::basic::kvp("allowed_schemas", schemas)); - bsoncxx::builder::stream::document filter; - filter << "_id" << id; + bsoncxx::builder::stream::document filter; + filter << "_id" << id; - mongocxx::options::replace opts; - opts.upsert(true); + mongocxx::options::replace opts; + opts.upsert(true); - auto reply = collection.replace_one(filter, new_access_document, opts); + auto reply = collection.replace_one(filter, new_access_document, opts); - if (!reply) { - RAISE_ERROR("Failed to update authorization entry in MongoDB"); - } + if (!reply) { + RAISE_ERROR("Failed to update authorization entry in MongoDB"); } } -void MongoAuthorizationPersistence::remove(const atomdb_api_types::PublicKey& public_key, +void MongoAuthorizationPersistence::remove(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry) { auto conn = this->pool->acquire(); auto collection = (*conn)[this->database_name][this->collection_name]; - for (const auto& key : public_key.keys) { - auto access_document = this->get_document(collection, key); + auto access_document = this->get_document(collection, public_key); - if (!access_document) continue; + if (!access_document) continue; - auto schemas = bsoncxx::builder::basic::array{}; - for (const auto& document_entry : access_document->entries) { - if (document_entry.schema.handle() == entry.schema.handle()) { - continue; // Skip the entry to remove - } else { - schemas.append(this->entry_to_document(document_entry)); - } + auto schemas = bsoncxx::builder::basic::array{}; + for (const auto& document_entry : access_document->entries) { + if (document_entry.schema.handle() == entry.schema.handle()) { + continue; // Skip the entry to remove + } else { + schemas.append(this->entry_to_document(document_entry)); } + } - string id = Hasher::plain_string_hash(access_document->access_key); + string id = Hasher::plain_string_hash(access_document->access_key); - auto new_access_document = bsoncxx::builder::basic::make_document( - bsoncxx::builder::basic::kvp("_id", id), - bsoncxx::builder::basic::kvp("public_key", access_document->access_key), - bsoncxx::builder::basic::kvp("full_access", access_document->full_access), - bsoncxx::builder::basic::kvp("allowed_schemas", schemas)); + auto new_access_document = bsoncxx::builder::basic::make_document( + bsoncxx::builder::basic::kvp("_id", id), + bsoncxx::builder::basic::kvp("public_key", access_document->access_key), + bsoncxx::builder::basic::kvp("full_access", access_document->full_access), + bsoncxx::builder::basic::kvp("allowed_schemas", schemas)); - bsoncxx::builder::stream::document filter; - filter << "_id" << id; + bsoncxx::builder::stream::document filter; + filter << "_id" << id; - auto reply = collection.replace_one(filter, new_access_document); + auto reply = collection.replace_one(filter, new_access_document); - if (!reply) { - RAISE_ERROR("Failed to update authorization entry in MongoDB"); - } + if (!reply) { + RAISE_ERROR("Failed to update authorization entry in MongoDB"); } } -void MongoAuthorizationPersistence::remove_all(const atomdb_api_types::PublicKey& public_key) { +void MongoAuthorizationPersistence::remove_all(const string& public_key) { auto conn = this->pool->acquire(); auto collection = (*conn)[this->database_name][this->collection_name]; - for (const auto& key : public_key.keys) { - auto reply = collection.delete_one(bsoncxx::builder::basic::make_document( - bsoncxx::builder::basic::kvp("_id", Hasher::plain_string_hash(key)))); - if (!reply) { - RAISE_ERROR("Failed to remove authorization document from MongoDB"); - } + auto reply = collection.delete_one(bsoncxx::builder::basic::make_document( + bsoncxx::builder::basic::kvp("_id", Hasher::plain_string_hash(public_key)))); + if (!reply) { + RAISE_ERROR("Failed to remove authorization document from MongoDB"); } } diff --git a/src/atomdb/auth/MongoAuthorizationPersistence.h b/src/atomdb/auth/MongoAuthorizationPersistence.h index 9313a340..55253cb9 100644 --- a/src/atomdb/auth/MongoAuthorizationPersistence.h +++ b/src/atomdb/auth/MongoAuthorizationPersistence.h @@ -25,11 +25,9 @@ class MongoAuthorizationPersistence : public AuthorizationPersistence { const string& database_name, const string& collection_name); - void save(const atomdb_api_types::PublicKey& public_key, - const atomdb_api_types::AccessPermissionEntry& entry) override; - void remove(const atomdb_api_types::PublicKey& public_key, - const atomdb_api_types::AccessPermissionEntry& entry) override; - void remove_all(const atomdb_api_types::PublicKey& public_key) override; + void save(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry) override; + void remove(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry) override; + void remove_all(const string& public_key) override; private: mongocxx::pool* pool; From c558e6ac8af7b62818e46e1093bd84edb2c7389d Mon Sep 17 00:00:00 2001 From: marcocapozzoli Date: Tue, 18 Aug 2026 22:51:47 -0300 Subject: [PATCH 09/16] Refactor auth --- src/atomdb/AtomDBFactory.cc | 11 ++- src/atomdb/BUILD | 2 + src/atomdb/ProtectedAtomDB.cc | 3 +- src/atomdb/ProtectedAtomDB.h | 5 +- src/atomdb/auth/AuthorizationManagement.cc | 71 ++++++++----------- src/atomdb/auth/AuthorizationManagement.h | 30 +++----- src/atomdb/auth/AuthorizationManifest.cc | 64 ++++++----------- src/atomdb/auth/AuthorizationManifest.h | 23 ++---- src/atomdb/auth/AuthorizationPersistence.h | 2 +- src/atomdb/auth/BUILD | 15 +++- .../auth/MongoAuthorizationPersistence.cc | 18 ++--- .../auth/MongoAuthorizationPersistence.h | 3 + 12 files changed, 111 insertions(+), 136 deletions(-) diff --git a/src/atomdb/AtomDBFactory.cc b/src/atomdb/AtomDBFactory.cc index dcb16398..35f768fd 100644 --- a/src/atomdb/AtomDBFactory.cc +++ b/src/atomdb/AtomDBFactory.cc @@ -2,6 +2,7 @@ #include "AdapterDB.h" #include "InMemoryDB.h" +#include "MongoAuthorizationPersistence.h" #include "MorkDB.h" #include "ProtectedAtomDB.h" #include "RedisMongoDB.h" @@ -111,5 +112,13 @@ shared_ptr AtomDBFactory::wrap_if_protected(shared_ptr atomdb) { dynamic_pointer_cast(atomdb)) { return atomdb; } - return make_shared(atomdb); + + // TODO: Decide where the MongoDB connection parameters should come from + // auto persistence = + // make_shared(/* parameters for MongoAuthorizationPersistence + // */); + + auto auth = make_shared(nullptr); + + return make_shared(atomdb, auth); } \ No newline at end of file diff --git a/src/atomdb/BUILD b/src/atomdb/BUILD index 95015e91..d3ddcb24 100644 --- a/src/atomdb/BUILD +++ b/src/atomdb/BUILD @@ -13,6 +13,7 @@ cc_library( ":atomdbutils", ":protected_atomdb", "//atomdb/adapterdb:adapterdb_lib", + "//atomdb/auth:auth_lib", "//atomdb/inmemorydb:inmemorydb_lib", "//atomdb/morkdb:morkdb_lib", "//atomdb/redis_mongodb:redis_mongodb_lib", @@ -89,6 +90,7 @@ cc_library( deps = [ ":atomdb", ":atomdb_api_types", + "//atomdb/auth:auth_lib", "//commons:commons_lib", "//commons/atoms:atoms_lib", ], diff --git a/src/atomdb/ProtectedAtomDB.cc b/src/atomdb/ProtectedAtomDB.cc index 5ac645e0..24852dcd 100644 --- a/src/atomdb/ProtectedAtomDB.cc +++ b/src/atomdb/ProtectedAtomDB.cc @@ -9,7 +9,8 @@ using namespace commons; // -------------------------------------------------------------------------------- // Constructors and destructors -ProtectedAtomDB::ProtectedAtomDB(shared_ptr backend) : backend(backend) { +ProtectedAtomDB::ProtectedAtomDB(shared_ptr backend, shared_ptr auth) + : backend(backend), auth(auth) { if (this->backend == nullptr) { RAISE_ERROR("ProtectedAtomDB requires a non-null backend AtomDB"); } diff --git a/src/atomdb/ProtectedAtomDB.h b/src/atomdb/ProtectedAtomDB.h index 67f4489e..c03b5715 100644 --- a/src/atomdb/ProtectedAtomDB.h +++ b/src/atomdb/ProtectedAtomDB.h @@ -6,6 +6,7 @@ #include #include "AtomDB.h" +#include "AuthorizationManagement.h" using namespace std; using namespace atoms; @@ -26,8 +27,9 @@ class ProtectedAtomDB : public AtomDB { public: /** * @param backend Shared concrete AtomDB to wrap. + * @param auth Shared authorization management instance. */ - explicit ProtectedAtomDB(shared_ptr backend); + ProtectedAtomDB(shared_ptr backend, shared_ptr auth); bool allow_nested_indexing() override; bool composite_type_enabled() const override; @@ -163,6 +165,7 @@ class ProtectedAtomDB : public AtomDB { private: shared_ptr backend; + shared_ptr auth; [[noreturn]] static void raise_public_key_required(const string& method_name); }; diff --git a/src/atomdb/auth/AuthorizationManagement.cc b/src/atomdb/auth/AuthorizationManagement.cc index 09b18df5..edb1b018 100644 --- a/src/atomdb/auth/AuthorizationManagement.cc +++ b/src/atomdb/auth/AuthorizationManagement.cc @@ -1,6 +1,7 @@ #include "AuthorizationManagement.h" #include "Assignment.h" +#include "Atom.h" #include "AtomDB.h" #include "Link.h" #include "Utils.h" @@ -12,11 +13,10 @@ using namespace commons; // -------------------------------------------------------------------------------- // Constructors -AuthorizationManagement::AuthorizationManagement(shared_ptr atomdb, - shared_ptr persistence) - : atomdb(std::move(atomdb)), persistence(std::move(persistence)) { - if (this->atomdb == nullptr) { - RAISE_ERROR("AuthorizationManagement requires a non-null atomdb AtomDB"); +AuthorizationManagement::AuthorizationManagement(shared_ptr persistence) + : persistence(persistence) { + if (this->persistence == nullptr) { + RAISE_ERROR("AuthorizationManagement requires a non-null persistence"); } } @@ -29,7 +29,8 @@ bool AuthorizationManagement::has_full_access(const string& public_key) { bool AuthorizationManagement::is_authorized(const Atom& atom, const string& public_key, - AuthorizationOperation operation) { + AuthorizationOperation operation, + HandleDecoder& decoder) { if (!this->manifest.is_registered(public_key)) { return false; } @@ -37,9 +38,9 @@ bool AuthorizationManagement::is_authorized(const Atom& atom, return true; } - HandleDecoder& decoder = *this->atomdb; - for (const auto& entry : this->manifest.entries(public_key)) { - if (allows(entry, operation) && this->matches_entry(entry, atom, decoder)) { + auto document = this->manifest.get_document(public_key); + for (const auto& entry : document->entries) { + if (this->allows(entry, operation) && this->matches_schema(entry.schema, atom, decoder)) { return true; } } @@ -57,8 +58,9 @@ bool AuthorizationManagement::is_authorized(const string& handle, return true; } - for (const auto& entry : this->manifest.entries(public_key)) { - if (allows(entry, operation) && this->matches_entry(entry, handle, decoder)) { + auto document = this->manifest.get_document(public_key); + for (const auto& entry : document->entries) { + if (allows(entry, operation) && this->matches_schema(entry.schema, handle, decoder)) { return true; } } @@ -67,31 +69,17 @@ bool AuthorizationManagement::is_authorized(const string& handle, void AuthorizationManagement::authorize(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry) { - if (this->persistence == nullptr) { - RAISE_ERROR( - "AuthorizationManagement::authorize() requires AuthorizationPersistence; " - "this atomdb has no authorization storage"); - } this->persistence->save(public_key, entry); this->manifest.add(public_key, entry); } -void AuthorizationManagement::revoke(const string& public_key, const string& handle) { - if (this->persistence == nullptr) { - RAISE_ERROR( - "AuthorizationManagement::revoke() requires AuthorizationPersistence; " - "this atomdb has no authorization storage"); - } - this->persistence->remove(public_key, handle); - this->manifest.remove(public_key, handle); +void AuthorizationManagement::revoke(const string& public_key, + const atomdb_api_types::AccessPermissionEntry& entry) { + this->persistence->remove(public_key, entry); + this->manifest.remove(public_key, entry); } void AuthorizationManagement::revoke_all(const string& public_key) { - if (this->persistence == nullptr) { - RAISE_ERROR( - "AuthorizationManagement::revoke_all() requires AuthorizationPersistence; " - "this atomdb has no authorization storage"); - } this->persistence->remove_all(public_key); this->manifest.remove_all(public_key); } @@ -110,22 +98,23 @@ bool AuthorizationManagement::allows(const atomdb_api_types::AccessPermissionEnt return false; } -bool AuthorizationManagement::matches_entry(const atomdb_api_types::AccessPermissionEntry& entry, - const Atom& atom, - HandleDecoder& decoder) const { +// ??? +bool AuthorizationManagement::matches_schema(const LinkSchema& schema, + const Atom& atom, + HandleDecoder& decoder) const { Assignment assignment; - LinkSchema schema = entry.schema; - // Prefer matching against the in-memory atom so WRITE checks work for atoms not yet stored. + LinkSchema local_schema(schema); if (Atom::is_link(atom)) { - return schema.match(const_cast(static_cast(atom)), assignment, decoder); + return local_schema.match( + const_cast(static_cast(atom)), assignment, decoder); } - return schema.match(atom.handle(), assignment, decoder); + return local_schema.match(atom.handle(), assignment, decoder); } -bool AuthorizationManagement::matches_entry(const atomdb_api_types::AccessPermissionEntry& entry, - const string& handle, - HandleDecoder& decoder) const { +bool AuthorizationManagement::matches_schema(const LinkSchema& schema, + const string& handle, + HandleDecoder& decoder) const { Assignment assignment; - LinkSchema schema = entry.schema; - return schema.match(handle, assignment, decoder); + LinkSchema local_schema(schema); + return local_schema.match(handle, assignment, decoder); } diff --git a/src/atomdb/auth/AuthorizationManagement.h b/src/atomdb/auth/AuthorizationManagement.h index 64cfc2c1..fc6d9892 100644 --- a/src/atomdb/auth/AuthorizationManagement.h +++ b/src/atomdb/auth/AuthorizationManagement.h @@ -27,11 +27,9 @@ enum class AuthorizationOperation { READ, WRITE }; class AuthorizationManagement { public: /** - * @param atomdb AtomDB used as HandleDecoder. - * @param persistence Storage used by authorize() and revoke*(). May be null when the atomdb - * has no authorization storage; administration then fails. + * @param persistence Storage used by authorize() and revoke*(). */ - AuthorizationManagement(shared_ptr atomdb, shared_ptr persistence); + AuthorizationManagement(shared_ptr persistence); /** * @brief Returns true when public_key is registered with full_access. @@ -40,15 +38,14 @@ class AuthorizationManagement { /** * @brief Checks whether public_key may perform operation on atom. - * - * @return true if full_access or the atom matches at least one entry allowing operation. */ - bool is_authorized(const Atom& atom, const string& public_key, AuthorizationOperation operation); + bool is_authorized(const Atom& atom, + const string& public_key, + AuthorizationOperation operation, + HandleDecoder& decoder); /** * @brief Checks whether public_key may perform operation on handle. - * - * @param decoder HandleDecoder from the atomdb (required by LinkSchema::match). */ bool is_authorized(const string& handle, const string& public_key, @@ -61,11 +58,9 @@ class AuthorizationManagement { void authorize(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry); /** - * @brief Revokes one entry from public_key. - * - * @param handle AccessPermissionEntry::schema.handle(), i.e. the LinkSchema handle. + * @brief Revokes one entry from public_key. Updates storage and the in-RAM manifest. */ - void revoke(const string& public_key, const string& handle); + void revoke(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry); /** * @brief Revokes every entry of public_key. Updates storage and the in-RAM manifest. @@ -73,16 +68,11 @@ class AuthorizationManagement { void revoke_all(const string& public_key); private: - shared_ptr atomdb; shared_ptr persistence; AuthorizationManifest manifest; - bool matches_entry(const atomdb_api_types::AccessPermissionEntry& entry, - const Atom& atom, - HandleDecoder& decoder) const; - bool matches_entry(const atomdb_api_types::AccessPermissionEntry& entry, - const string& handle, - HandleDecoder& decoder) const; + bool matches_schema(const LinkSchema& schema, const Atom& atom, HandleDecoder& decoder) const; + bool matches_schema(const LinkSchema& schema, const string& handle, HandleDecoder& decoder) const; static bool allows(const atomdb_api_types::AccessPermissionEntry& entry, AuthorizationOperation operation); diff --git a/src/atomdb/auth/AuthorizationManifest.cc b/src/atomdb/auth/AuthorizationManifest.cc index 9b66d9bc..56247028 100644 --- a/src/atomdb/auth/AuthorizationManifest.cc +++ b/src/atomdb/auth/AuthorizationManifest.cc @@ -8,34 +8,33 @@ using namespace atomdb; -const vector AuthorizationManifest::EMPTY_ENTRIES; - // -------------------------------------------------------------------------------- // Public methods void AuthorizationManifest::set(const atomdb_api_types::AccessPermissionDocument& document) { - auto it = this->documents.find(document.public_key); + auto it = this->documents.find(document.access_key); if (it == this->documents.end()) { - this->documents.emplace(document.public_key, document); + this->documents.emplace(document.access_key, + atomdb_api_types::AccessPermissionDocument(document)); } else { it->second = document; } } -void AuthorizationManifest::add(const atomdb_api_types::PublicKey& public_key, +void AuthorizationManifest::add(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry) { - atomdb_api_types::AccessPermissionDocument* document = this->find_document(public_key, "add"); + auto document = this->get_document(public_key); if (document == nullptr) { - this->create_document(public_key, entry); + this->documents.emplace(public_key, + atomdb_api_types::AccessPermissionDocument(public_key, false, {entry})); return; } - string entry_handle = entry.schema.handle(); vector& entries = document->entries; for (auto& existing : entries) { - if (existing.schema.handle() == entry_handle) { + if (existing.schema.handle() == entry.schema.handle()) { existing = entry; return; } @@ -44,62 +43,39 @@ void AuthorizationManifest::add(const atomdb_api_types::PublicKey& public_key, entries.push_back(entry); } -void AuthorizationManifest::remove(const atomdb_api_types::PublicKey& public_key, const string& handle) { - atomdb_api_types::AccessPermissionDocument* document = this->find_document(public_key, "remove"); +void AuthorizationManifest::remove(const string& public_key, + const atomdb_api_types::AccessPermissionEntry& entry) { + auto document = this->get_document(public_key); if (document == nullptr) return; vector& entries = document->entries; for (auto it = entries.begin(); it != entries.end(); ++it) { - if (it->schema.handle() == handle) { + if (it->schema.handle() == entry.schema.handle()) { entries.erase(it); return; } } } -void AuthorizationManifest::remove_all(const atomdb_api_types::PublicKey& public_key) { - this->documents.erase(public_key); -} +void AuthorizationManifest::remove_all(const string& public_key) { this->documents.erase(public_key); } -bool AuthorizationManifest::is_registered(const atomdb_api_types::PublicKey& public_key) const { +bool AuthorizationManifest::is_registered(const string& public_key) const { return this->documents.find(public_key) != this->documents.end(); } -bool AuthorizationManifest::full_access(const atomdb_api_types::PublicKey& public_key) { - atomdb_api_types::AccessPermissionDocument* document = - this->find_document(public_key, "full_access"); - if (document == nullptr) { - return false; - } +bool AuthorizationManifest::full_access(const string& public_key) { + auto document = this->get_document(public_key); + if (document == nullptr) return false; return document->full_access; } -const vector& AuthorizationManifest::entries( - const atomdb_api_types::PublicKey& public_key) { - atomdb_api_types::AccessPermissionDocument* document = this->find_document(public_key, "entries"); - if (document == nullptr) { - return EMPTY_ENTRIES; - } - return document->entries; -} - -// -------------------------------------------------------------------------------- -// Private methods - -void AuthorizationManifest::create_document(const atomdb_api_types::PublicKey& public_key, - const atomdb_api_types::AccessPermissionEntry& entry) { - this->documents.emplace(public_key, - atomdb_api_types::AccessPermissionDocument(public_key, false, {entry})); -} - -atomdb_api_types::AccessPermissionDocument* AuthorizationManifest::find_document( - const atomdb_api_types::PublicKey& public_key, const string& caller) { +atomdb_api_types::AccessPermissionDocument* AuthorizationManifest::get_document( + const string& public_key) { auto it = this->documents.find(public_key); + if (it == this->documents.end()) { - LOG_INFO("AuthorizationManifest::" << caller - << "() called for unregistered public_key: " << public_key); return nullptr; } return &it->second; diff --git a/src/atomdb/auth/AuthorizationManifest.h b/src/atomdb/auth/AuthorizationManifest.h index 76505820..0ec7a41f 100644 --- a/src/atomdb/auth/AuthorizationManifest.h +++ b/src/atomdb/auth/AuthorizationManifest.h @@ -20,37 +20,28 @@ class AuthorizationManifest { public: AuthorizationManifest() = default; - /** @brief Replaces (or inserts) the document for document.public_key. */ + /** @brief Replaces (or inserts) the document for document.access_key. */ void set(const atomdb_api_types::AccessPermissionDocument& document); /** @brief Adds one entry to public_key, creating the document if needed. */ - void add(const atomdb_api_types::PublicKey& public_key, - const atomdb_api_types::AccessPermissionEntry& entry); + void add(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry); /** @brief Removes the entry whose schema.handle() == handle from public_key. No-op if absent. */ - void remove(const atomdb_api_types::PublicKey& public_key, const string& handle); + void remove(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry); /** @brief Removes the whole document for public_key. No-op if not registered. */ - void remove_all(const atomdb_api_types::PublicKey& public_key); + void remove_all(const string& public_key); /** @brief Returns true if public_key is registered. */ - bool is_registered(const atomdb_api_types::PublicKey& public_key) const; + bool is_registered(const string& public_key) const; /** @brief Returns true if public_key is registered with full_access. */ - bool full_access(const atomdb_api_types::PublicKey& public_key); + bool full_access(const string& public_key); - /** @brief Returns the entries for public_key, or an empty vector if not registered. */ - const vector& entries( - const atomdb_api_types::PublicKey& public_key); + atomdb_api_types::AccessPermissionDocument* get_document(const string& public_key); private: map documents; - static const vector EMPTY_ENTRIES; - - void create_document(const atomdb_api_types::PublicKey& public_key, - const atomdb_api_types::AccessPermissionEntry& entry); - atomdb_api_types::AccessPermissionDocument* find_document( - const atomdb_api_types::PublicKey& public_key, const string& caller); }; } // namespace atomdb diff --git a/src/atomdb/auth/AuthorizationPersistence.h b/src/atomdb/auth/AuthorizationPersistence.h index ba366505..312ad0ee 100644 --- a/src/atomdb/auth/AuthorizationPersistence.h +++ b/src/atomdb/auth/AuthorizationPersistence.h @@ -15,7 +15,7 @@ class AuthorizationPersistence { /** @brief Persists one entry under public_key (creating the document if needed). */ virtual void save(const string& public_key, - const atomdb_api_types::AccessPermissionDocument& entry) = 0; + const atomdb_api_types::AccessPermissionEntry& entry) = 0; /** @brief Removes the entry identified */ virtual void remove(const string& public_key, diff --git a/src/atomdb/auth/BUILD b/src/atomdb/auth/BUILD index 9da9f015..12216a57 100644 --- a/src/atomdb/auth/BUILD +++ b/src/atomdb/auth/BUILD @@ -2,6 +2,16 @@ load("@rules_cc//cc:cc_library.bzl", "cc_library") package(default_visibility = ["//visibility:public"]) +cc_library( + name = "auth_lib", + includes = ["."], + deps = [ + ":authorization_management", + ":authorization_manifest", + ":mongo_authorization_persistence", + ], +) + cc_library( name = "authorization_persistence", hdrs = ["AuthorizationPersistence.h"], @@ -22,6 +32,7 @@ cc_library( "//commons:commons_lib", "//commons/atoms:atoms_lib", "//hasher:hasher_lib", + "@nlohmann_json//:json", ], ) @@ -31,8 +42,8 @@ cc_library( hdrs = ["AuthorizationManagement.h"], includes = ["."], deps = [ - ":authorization_persistence", - ":authorization_types", + ":authorization_manifest", + ":mongo_authorization_persistence", "//atomdb", "//atomdb:atomdb_api_types", "//commons:commons_lib", diff --git a/src/atomdb/auth/MongoAuthorizationPersistence.cc b/src/atomdb/auth/MongoAuthorizationPersistence.cc index 0d3fe2a1..35238441 100644 --- a/src/atomdb/auth/MongoAuthorizationPersistence.cc +++ b/src/atomdb/auth/MongoAuthorizationPersistence.cc @@ -3,6 +3,7 @@ #include "Hasher.h" #include "Utils.h" #include "expression_hasher.h" +#include "nlohmann/json.hpp" using namespace atomdb; using namespace commons; @@ -68,13 +69,12 @@ void MongoAuthorizationPersistence::save(const string& public_key, bsoncxx::builder::basic::kvp("full_access", full_access), bsoncxx::builder::basic::kvp("allowed_schemas", schemas)); - bsoncxx::builder::stream::document filter; - filter << "_id" << id; + auto filter = bsoncxx::builder::basic::make_document(bsoncxx::builder::basic::kvp("_id", id)); mongocxx::options::replace opts; opts.upsert(true); - auto reply = collection.replace_one(filter, new_access_document, opts); + auto reply = collection.replace_one(filter.view(), new_access_document.view(), opts); if (!reply) { RAISE_ERROR("Failed to update authorization entry in MongoDB"); @@ -88,7 +88,7 @@ void MongoAuthorizationPersistence::remove(const string& public_key, auto access_document = this->get_document(collection, public_key); - if (!access_document) continue; + if (!access_document) return; auto schemas = bsoncxx::builder::basic::array{}; for (const auto& document_entry : access_document->entries) { @@ -107,10 +107,9 @@ void MongoAuthorizationPersistence::remove(const string& public_key, bsoncxx::builder::basic::kvp("full_access", access_document->full_access), bsoncxx::builder::basic::kvp("allowed_schemas", schemas)); - bsoncxx::builder::stream::document filter; - filter << "_id" << id; + auto filter = bsoncxx::builder::basic::make_document(bsoncxx::builder::basic::kvp("_id", id)); - auto reply = collection.replace_one(filter, new_access_document); + auto reply = collection.replace_one(filter.view(), new_access_document.view()); if (!reply) { RAISE_ERROR("Failed to update authorization entry in MongoDB"); @@ -133,7 +132,8 @@ void MongoAuthorizationPersistence::remove_all(const string& public_key) { bsoncxx::document::value MongoAuthorizationPersistence::entry_to_document( const atomdb_api_types::AccessPermissionEntry& entry) { auto tokens_array = bsoncxx::builder::basic::array{}; - for (const auto& token : entry.schema.tokenize()) { + auto local_schema = entry.schema; + for (const auto& token : local_schema.tokenize()) { tokens_array.append(token); } return bsoncxx::builder::basic::make_document(bsoncxx::builder::basic::kvp("tokens", tokens_array), @@ -148,7 +148,7 @@ shared_ptr MongoAuthorizationPersist if (!reply) return nullptr; - auto document_json = nlohmann::json::parse(bsoncxx::to_json(reply->value().view())); + auto document_json = nlohmann::json::parse(bsoncxx::to_json(reply.value().view())); vector entries; for (const auto& item : document_json["allowed_schemas"]) { diff --git a/src/atomdb/auth/MongoAuthorizationPersistence.h b/src/atomdb/auth/MongoAuthorizationPersistence.h index 55253cb9..ab38c714 100644 --- a/src/atomdb/auth/MongoAuthorizationPersistence.h +++ b/src/atomdb/auth/MongoAuthorizationPersistence.h @@ -2,6 +2,9 @@ #include #include +#include +#include +#include #include #include #include From f04b5cc28c5dc18b5299b76cda9f222a199cbeaa Mon Sep 17 00:00:00 2001 From: marcocapozzoli Date: Wed, 19 Aug 2026 13:54:06 -0300 Subject: [PATCH 10/16] WIP --- src/atomdb/auth/AuthorizationManagement.cc | 11 ++---- src/atomdb/auth/AuthorizationManagement.h | 5 --- .../auth/MongoAuthorizationPersistence.cc | 37 ++++++++++++++----- .../auth/MongoAuthorizationPersistence.h | 10 +++-- src/tests/cpp/authorization_test.cc | 4 +- 5 files changed, 38 insertions(+), 29 deletions(-) diff --git a/src/atomdb/auth/AuthorizationManagement.cc b/src/atomdb/auth/AuthorizationManagement.cc index edb1b018..d2b4b513 100644 --- a/src/atomdb/auth/AuthorizationManagement.cc +++ b/src/atomdb/auth/AuthorizationManagement.cc @@ -14,7 +14,7 @@ using namespace commons; // Constructors AuthorizationManagement::AuthorizationManagement(shared_ptr persistence) - : persistence(persistence) { + : persistence(persistence), manifest() { if (this->persistence == nullptr) { RAISE_ERROR("AuthorizationManagement requires a non-null persistence"); } @@ -23,10 +23,6 @@ AuthorizationManagement::AuthorizationManagement(shared_ptrmanifest.is_registered(public_key) && this->manifest.full_access(public_key); -} - bool AuthorizationManagement::is_authorized(const Atom& atom, const string& public_key, AuthorizationOperation operation, @@ -98,15 +94,14 @@ bool AuthorizationManagement::allows(const atomdb_api_types::AccessPermissionEnt return false; } -// ??? bool AuthorizationManagement::matches_schema(const LinkSchema& schema, const Atom& atom, HandleDecoder& decoder) const { Assignment assignment; LinkSchema local_schema(schema); if (Atom::is_link(atom)) { - return local_schema.match( - const_cast(static_cast(atom)), assignment, decoder); + auto& link = const_cast(static_cast(atom)); + return local_schema.match(link, assignment, decoder); } return local_schema.match(atom.handle(), assignment, decoder); } diff --git a/src/atomdb/auth/AuthorizationManagement.h b/src/atomdb/auth/AuthorizationManagement.h index fc6d9892..f8444f77 100644 --- a/src/atomdb/auth/AuthorizationManagement.h +++ b/src/atomdb/auth/AuthorizationManagement.h @@ -31,11 +31,6 @@ class AuthorizationManagement { */ AuthorizationManagement(shared_ptr persistence); - /** - * @brief Returns true when public_key is registered with full_access. - */ - bool has_full_access(const string& public_key); - /** * @brief Checks whether public_key may perform operation on atom. */ diff --git a/src/atomdb/auth/MongoAuthorizationPersistence.cc b/src/atomdb/auth/MongoAuthorizationPersistence.cc index 35238441..8e66dfe4 100644 --- a/src/atomdb/auth/MongoAuthorizationPersistence.cc +++ b/src/atomdb/auth/MongoAuthorizationPersistence.cc @@ -1,6 +1,7 @@ #include "MongoAuthorizationPersistence.h" #include "Hasher.h" +#include "JsonConfig.h" #include "Utils.h" #include "expression_hasher.h" #include "nlohmann/json.hpp" @@ -11,24 +12,40 @@ using namespace commons; // -------------------------------------------------------------------------------- // Constructors -MongoAuthorizationPersistence::MongoAuthorizationPersistence(mongocxx::pool* pool, +MongoAuthorizationPersistence::MongoAuthorizationPersistence(const string& endpoint, + const string& username, + const string& password, const string& database_name, - const string& collection_name) - : pool(pool), database_name(database_name), collection_name(collection_name) { - if (this->pool == nullptr) { - RAISE_ERROR("MongoAuthorizationPersistence requires a non-null MongoDB pool"); + const string& collection_name) { + if (endpoint.empty() || endpoint == ":" || username.empty() || password.empty()) { + RAISE_ERROR("Invalid MongoDB configuration: need non-empty username, username, and password."); } - if (this->database_name.empty() || this->collection_name.empty()) { - RAISE_ERROR("MongoAuthorizationPersistence requires a non-empty database and collection names"); + + string url = "mongodb://" + username + ":" + password + "@" + endpoint; + + MongoInitializer::initialize(); + + try { + auto uri = mongocxx::uri{url}; + this->mongodb_pool = new mongocxx::pool(uri); + auto conn = this->mongodb_pool->acquire(); + auto mongodb = (*conn)[database_name]; + const auto ping_cmd = bsoncxx::builder::basic::make_document(bsoncxx::builder::basic::kvp("ping", 1)); + mongodb.run_command(ping_cmd.view()); + LOG_DEBUG("MongoAuthorizationPersistence connected to MongoDB at " << endpoint << " (db=" << database_name << ")"); + } catch (const exception& e) { + RAISE_ERROR(e.what()); } } +MongoAuthorizationPersistence::~MongoAuthorizationPersistence() { delete this->mongodb_pool; } + // -------------------------------------------------------------------------------- // Public methods void MongoAuthorizationPersistence::save(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry) { - auto conn = this->pool->acquire(); + auto conn = this->mongodb_pool->acquire(); auto collection = (*conn)[this->database_name][this->collection_name]; auto access_document = this->get_document(collection, public_key); @@ -83,7 +100,7 @@ void MongoAuthorizationPersistence::save(const string& public_key, void MongoAuthorizationPersistence::remove(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry) { - auto conn = this->pool->acquire(); + auto conn = this->mongodb_pool->acquire(); auto collection = (*conn)[this->database_name][this->collection_name]; auto access_document = this->get_document(collection, public_key); @@ -117,7 +134,7 @@ void MongoAuthorizationPersistence::remove(const string& public_key, } void MongoAuthorizationPersistence::remove_all(const string& public_key) { - auto conn = this->pool->acquire(); + auto conn = this->mongodb_pool->acquire(); auto collection = (*conn)[this->database_name][this->collection_name]; auto reply = collection.delete_one(bsoncxx::builder::basic::make_document( bsoncxx::builder::basic::kvp("_id", Hasher::plain_string_hash(public_key)))); diff --git a/src/atomdb/auth/MongoAuthorizationPersistence.h b/src/atomdb/auth/MongoAuthorizationPersistence.h index ab38c714..970095ed 100644 --- a/src/atomdb/auth/MongoAuthorizationPersistence.h +++ b/src/atomdb/auth/MongoAuthorizationPersistence.h @@ -20,26 +20,30 @@ namespace atomdb { class MongoAuthorizationPersistence : public AuthorizationPersistence { public: /** - * @param pool Mongo pool + * @param endpoint ip:port * @param database_name Mongo database name * @param collection_name access_permissions collection name */ - MongoAuthorizationPersistence(mongocxx::pool* pool, + MongoAuthorizationPersistence(const string& endpoint, + const string& username, + const string& password, const string& database_name, const string& collection_name); + ~MongoAuthorizationPersistence(); void save(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry) override; void remove(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry) override; void remove_all(const string& public_key) override; private: - mongocxx::pool* pool; + mongocxx::pool* mongodb_pool; string database_name; string collection_name; bsoncxx::document::value entry_to_document(const atomdb_api_types::AccessPermissionEntry& entry); shared_ptr get_document(mongocxx::collection& collection, const string& public_key); + mongocxx::collection get_mongodb_collection(); }; } // namespace atomdb diff --git a/src/tests/cpp/authorization_test.cc b/src/tests/cpp/authorization_test.cc index d410b911..0e09d7e4 100644 --- a/src/tests/cpp/authorization_test.cc +++ b/src/tests/cpp/authorization_test.cc @@ -191,7 +191,5 @@ TEST(AuthorizationManagementTest, DoesNotLoadPermissionsFromAtomDB) { auto db = make_shared("auth_noload_"); auto persistence = make_shared(); - AuthorizationManagement management(db, persistence); - - EXPECT_FALSE(management.has_full_access("pk")); + AuthorizationManagement management(persistence); } From b0ee315d957135dacf73fb4712fcb6f3e2297145 Mon Sep 17 00:00:00 2001 From: marcocapozzoli Date: Thu, 20 Aug 2026 13:36:52 -0300 Subject: [PATCH 11/16] Re-design --- src/atomdb/AtomDBFactory.cc | 11 +- src/atomdb/ProtectedAtomDB.cc | 6 +- src/atomdb/ProtectedAtomDB.h | 7 +- src/atomdb/auth/AuthorizationManagement.cc | 115 ----------- src/atomdb/auth/AuthorizationManagement.h | 76 ------- src/atomdb/auth/AuthorizationManager.cc | 32 +++ src/atomdb/auth/AuthorizationManager.h | 49 +++++ src/atomdb/auth/AuthorizationManifest.cc | 8 +- src/atomdb/auth/AuthorizationManifest.h | 39 +++- src/atomdb/auth/AuthorizationPersistence.h | 22 +- src/atomdb/auth/BUILD | 24 ++- src/atomdb/auth/ManifestAuthorizer.cc | 94 +++++++++ src/atomdb/auth/ManifestAuthorizer.h | 51 +++++ .../auth/MongoAuthorizationPersistence.cc | 15 +- .../auth/MongoAuthorizationPersistence.h | 28 ++- src/tests/cpp/BUILD | 5 +- src/tests/cpp/authorization_test.cc | 193 +++++++++--------- src/tests/cpp/redis_mongodb_test.cc | 53 ----- 18 files changed, 431 insertions(+), 397 deletions(-) delete mode 100644 src/atomdb/auth/AuthorizationManagement.cc delete mode 100644 src/atomdb/auth/AuthorizationManagement.h create mode 100644 src/atomdb/auth/AuthorizationManager.cc create mode 100644 src/atomdb/auth/AuthorizationManager.h create mode 100644 src/atomdb/auth/ManifestAuthorizer.cc create mode 100644 src/atomdb/auth/ManifestAuthorizer.h diff --git a/src/atomdb/AtomDBFactory.cc b/src/atomdb/AtomDBFactory.cc index 35f768fd..dcb16398 100644 --- a/src/atomdb/AtomDBFactory.cc +++ b/src/atomdb/AtomDBFactory.cc @@ -2,7 +2,6 @@ #include "AdapterDB.h" #include "InMemoryDB.h" -#include "MongoAuthorizationPersistence.h" #include "MorkDB.h" #include "ProtectedAtomDB.h" #include "RedisMongoDB.h" @@ -112,13 +111,5 @@ shared_ptr AtomDBFactory::wrap_if_protected(shared_ptr atomdb) { dynamic_pointer_cast(atomdb)) { return atomdb; } - - // TODO: Decide where the MongoDB connection parameters should come from - // auto persistence = - // make_shared(/* parameters for MongoAuthorizationPersistence - // */); - - auto auth = make_shared(nullptr); - - return make_shared(atomdb, auth); + return make_shared(atomdb); } \ No newline at end of file diff --git a/src/atomdb/ProtectedAtomDB.cc b/src/atomdb/ProtectedAtomDB.cc index 24852dcd..9066ffa1 100644 --- a/src/atomdb/ProtectedAtomDB.cc +++ b/src/atomdb/ProtectedAtomDB.cc @@ -9,11 +9,13 @@ using namespace commons; // -------------------------------------------------------------------------------- // Constructors and destructors -ProtectedAtomDB::ProtectedAtomDB(shared_ptr backend, shared_ptr auth) - : backend(backend), auth(auth) { +ProtectedAtomDB::ProtectedAtomDB(shared_ptr backend) : backend(backend) { if (this->backend == nullptr) { RAISE_ERROR("ProtectedAtomDB requires a non-null backend AtomDB"); } + + // TODO: initialize ManifestAuthorizer + LOG_INFO("ProtectedAtomDB initialized"); } diff --git a/src/atomdb/ProtectedAtomDB.h b/src/atomdb/ProtectedAtomDB.h index c03b5715..354ab822 100644 --- a/src/atomdb/ProtectedAtomDB.h +++ b/src/atomdb/ProtectedAtomDB.h @@ -6,7 +6,7 @@ #include #include "AtomDB.h" -#include "AuthorizationManagement.h" +#include "ManifestAuthorizer.h" using namespace std; using namespace atoms; @@ -27,9 +27,8 @@ class ProtectedAtomDB : public AtomDB { public: /** * @param backend Shared concrete AtomDB to wrap. - * @param auth Shared authorization management instance. */ - ProtectedAtomDB(shared_ptr backend, shared_ptr auth); + ProtectedAtomDB(shared_ptr backend); bool allow_nested_indexing() override; bool composite_type_enabled() const override; @@ -165,7 +164,7 @@ class ProtectedAtomDB : public AtomDB { private: shared_ptr backend; - shared_ptr auth; + shared_ptr auth; [[noreturn]] static void raise_public_key_required(const string& method_name); }; diff --git a/src/atomdb/auth/AuthorizationManagement.cc b/src/atomdb/auth/AuthorizationManagement.cc deleted file mode 100644 index d2b4b513..00000000 --- a/src/atomdb/auth/AuthorizationManagement.cc +++ /dev/null @@ -1,115 +0,0 @@ -#include "AuthorizationManagement.h" - -#include "Assignment.h" -#include "Atom.h" -#include "AtomDB.h" -#include "Link.h" -#include "Utils.h" - -using namespace atomdb; -using namespace atoms; -using namespace commons; - -// -------------------------------------------------------------------------------- -// Constructors - -AuthorizationManagement::AuthorizationManagement(shared_ptr persistence) - : persistence(persistence), manifest() { - if (this->persistence == nullptr) { - RAISE_ERROR("AuthorizationManagement requires a non-null persistence"); - } -} - -// -------------------------------------------------------------------------------- -// Public methods - -bool AuthorizationManagement::is_authorized(const Atom& atom, - const string& public_key, - AuthorizationOperation operation, - HandleDecoder& decoder) { - if (!this->manifest.is_registered(public_key)) { - return false; - } - if (this->manifest.full_access(public_key)) { - return true; - } - - auto document = this->manifest.get_document(public_key); - for (const auto& entry : document->entries) { - if (this->allows(entry, operation) && this->matches_schema(entry.schema, atom, decoder)) { - return true; - } - } - return false; -} - -bool AuthorizationManagement::is_authorized(const string& handle, - const string& public_key, - AuthorizationOperation operation, - HandleDecoder& decoder) { - if (!this->manifest.is_registered(public_key)) { - return false; - } - if (this->manifest.full_access(public_key)) { - return true; - } - - auto document = this->manifest.get_document(public_key); - for (const auto& entry : document->entries) { - if (allows(entry, operation) && this->matches_schema(entry.schema, handle, decoder)) { - return true; - } - } - return false; -} - -void AuthorizationManagement::authorize(const string& public_key, - const atomdb_api_types::AccessPermissionEntry& entry) { - this->persistence->save(public_key, entry); - this->manifest.add(public_key, entry); -} - -void AuthorizationManagement::revoke(const string& public_key, - const atomdb_api_types::AccessPermissionEntry& entry) { - this->persistence->remove(public_key, entry); - this->manifest.remove(public_key, entry); -} - -void AuthorizationManagement::revoke_all(const string& public_key) { - this->persistence->remove_all(public_key); - this->manifest.remove_all(public_key); -} - -// -------------------------------------------------------------------------------- -// Private methods - -bool AuthorizationManagement::allows(const atomdb_api_types::AccessPermissionEntry& entry, - AuthorizationOperation operation) { - switch (operation) { - case AuthorizationOperation::READ: - return entry.read; - case AuthorizationOperation::WRITE: - return entry.write; - } - return false; -} - -bool AuthorizationManagement::matches_schema(const LinkSchema& schema, - const Atom& atom, - HandleDecoder& decoder) const { - Assignment assignment; - LinkSchema local_schema(schema); - if (Atom::is_link(atom)) { - auto& link = const_cast(static_cast(atom)); - return local_schema.match(link, assignment, decoder); - } - return local_schema.match(atom.handle(), assignment, decoder); -} - -bool AuthorizationManagement::matches_schema(const LinkSchema& schema, - const string& handle, - HandleDecoder& decoder) const { - Assignment assignment; - LinkSchema local_schema(schema); - return local_schema.match(handle, assignment, decoder); -} diff --git a/src/atomdb/auth/AuthorizationManagement.h b/src/atomdb/auth/AuthorizationManagement.h deleted file mode 100644 index f8444f77..00000000 --- a/src/atomdb/auth/AuthorizationManagement.h +++ /dev/null @@ -1,76 +0,0 @@ -#pragma once - -#include -#include - -#include "Atom.h" -#include "AtomDBAPITypes.h" -#include "AuthorizationManifest.h" -#include "AuthorizationPersistence.h" -#include "HandleDecoder.h" - -using namespace std; -using namespace atoms; - -namespace atomdb { - -class AtomDB; - -enum class AuthorizationOperation { READ, WRITE }; - -/** - * @brief Authorization queries and administration. - * - * The manifest is kept in RAM; authorize() and revoke*() update both the storage (through - * AuthorizationPersistence) and the in-RAM manifest. - */ -class AuthorizationManagement { - public: - /** - * @param persistence Storage used by authorize() and revoke*(). - */ - AuthorizationManagement(shared_ptr persistence); - - /** - * @brief Checks whether public_key may perform operation on atom. - */ - bool is_authorized(const Atom& atom, - const string& public_key, - AuthorizationOperation operation, - HandleDecoder& decoder); - - /** - * @brief Checks whether public_key may perform operation on handle. - */ - bool is_authorized(const string& handle, - const string& public_key, - AuthorizationOperation operation, - HandleDecoder& decoder); - - /** - * @brief Grants one entry to public_key. Updates storage and the in-RAM manifest. - */ - void authorize(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry); - - /** - * @brief Revokes one entry from public_key. Updates storage and the in-RAM manifest. - */ - void revoke(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry); - - /** - * @brief Revokes every entry of public_key. Updates storage and the in-RAM manifest. - */ - void revoke_all(const string& public_key); - - private: - shared_ptr persistence; - AuthorizationManifest manifest; - - bool matches_schema(const LinkSchema& schema, const Atom& atom, HandleDecoder& decoder) const; - bool matches_schema(const LinkSchema& schema, const string& handle, HandleDecoder& decoder) const; - - static bool allows(const atomdb_api_types::AccessPermissionEntry& entry, - AuthorizationOperation operation); -}; - -} // namespace atomdb diff --git a/src/atomdb/auth/AuthorizationManager.cc b/src/atomdb/auth/AuthorizationManager.cc new file mode 100644 index 00000000..4803e33c --- /dev/null +++ b/src/atomdb/auth/AuthorizationManager.cc @@ -0,0 +1,32 @@ + +#include "AuthorizationManager.h" + +using namespace atomdb; + +// -------------------------------------------------------------------------------- +// Constructor + +AuthorizationManager::AuthorizationManager(shared_ptr persistence) + : persistence(persistence) {} + +// -------------------------------------------------------------------------------- +// Public methods + +vector atomdb::AuthorizationManager::list( + const string& public_key) { + return this->persistence->list(public_key); +} + +void AuthorizationManager::authorize(const string& public_key, + const atomdb_api_types::AccessPermissionEntry& entry) { + this->persistence->save(public_key, entry); +} + +void AuthorizationManager::revoke(const string& public_key, + const atomdb_api_types::AccessPermissionEntry& entry) { + this->persistence->remove(public_key, entry); +} + +void AuthorizationManager::revoke_all(const string& public_key) { + this->persistence->remove_all(public_key); +} diff --git a/src/atomdb/auth/AuthorizationManager.h b/src/atomdb/auth/AuthorizationManager.h new file mode 100644 index 00000000..d75d3b13 --- /dev/null +++ b/src/atomdb/auth/AuthorizationManager.h @@ -0,0 +1,49 @@ + +#pragma once + +#include +#include + +#include "AtomDBAPITypes.h" +#include "AuthorizationPersistence.h" + +using namespace std; +using namespace atoms; + +namespace atomdb { + +/** + * @brief Manages authorization permissions through persistent storage. + */ +class AuthorizationManager { + public: + /** + * @param persistence Storage used to manage authorization permissions. + */ + AuthorizationManager(shared_ptr persistence); + + /** + * @brief Lists all permissions granted to public_key. + */ + vector list(const string& public_key); + + /** + * @brief Grants an authorization entry to public_key. + */ + void authorize(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry); + + /** + * @brief Revokes an authorization entry from public_key. + */ + void revoke(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry); + + /** + * @brief Revokes all authorization entries from public_key. + */ + void revoke_all(const string& public_key); + + private: + shared_ptr persistence; +}; + +} // namespace atomdb \ No newline at end of file diff --git a/src/atomdb/auth/AuthorizationManifest.cc b/src/atomdb/auth/AuthorizationManifest.cc index 56247028..3d81e63c 100644 --- a/src/atomdb/auth/AuthorizationManifest.cc +++ b/src/atomdb/auth/AuthorizationManifest.cc @@ -12,13 +12,7 @@ using namespace atomdb; // Public methods void AuthorizationManifest::set(const atomdb_api_types::AccessPermissionDocument& document) { - auto it = this->documents.find(document.access_key); - if (it == this->documents.end()) { - this->documents.emplace(document.access_key, - atomdb_api_types::AccessPermissionDocument(document)); - } else { - it->second = document; - } + this->documents.insert_or_assign(document.access_key, document); } void AuthorizationManifest::add(const string& public_key, diff --git a/src/atomdb/auth/AuthorizationManifest.h b/src/atomdb/auth/AuthorizationManifest.h index 0ec7a41f..700d3046 100644 --- a/src/atomdb/auth/AuthorizationManifest.h +++ b/src/atomdb/auth/AuthorizationManifest.h @@ -11,33 +11,54 @@ using namespace std; namespace atomdb { /** - * @brief In-RAM image of the whole access_permissions collection. + * @brief In-memory representation of the authorization state. * - * AuthorizationManagement keeps exactly one of these. Each AccessPermissionDocument maps 1:1 to one - * MongoDB document. + * Stores the authorization documents used by the authorization checks. + * The manifest is independent of the underlying persistence mechanism. */ class AuthorizationManifest { public: AuthorizationManifest() = default; - /** @brief Replaces (or inserts) the document for document.access_key. */ + /** + * @brief Replaces or inserts the authorization document. + */ void set(const atomdb_api_types::AccessPermissionDocument& document); - /** @brief Adds one entry to public_key, creating the document if needed. */ + /** + * @brief Adds an authorization entry for public_key. + * + * Creates the authorization document if public_key is not registered. + */ void add(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry); - /** @brief Removes the entry whose schema.handle() == handle from public_key. No-op if absent. */ + /** + * @brief Removes an authorization entry from public_key. + * + * Does nothing if public_key or the specified entry is not present. + */ void remove(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry); - /** @brief Removes the whole document for public_key. No-op if not registered. */ + /** + * @brief Removes all authorization entries for public_key. + * + * Does nothing if public_key is not registered. + */ void remove_all(const string& public_key); - /** @brief Returns true if public_key is registered. */ + /** + * @brief Returns whether public_key has an authorization document. + */ bool is_registered(const string& public_key) const; - /** @brief Returns true if public_key is registered with full_access. */ + /** + * @brief Returns whether public_key has full access. + */ bool full_access(const string& public_key); + /** + * @brief Returns a document from public_key. + */ atomdb_api_types::AccessPermissionDocument* get_document(const string& public_key); private: diff --git a/src/atomdb/auth/AuthorizationPersistence.h b/src/atomdb/auth/AuthorizationPersistence.h index 312ad0ee..69e525e5 100644 --- a/src/atomdb/auth/AuthorizationPersistence.h +++ b/src/atomdb/auth/AuthorizationPersistence.h @@ -8,21 +8,33 @@ using namespace std; namespace atomdb { -/** @brief Storage interface for authorization writes. */ +/** + * @brief Persistence interface for authorization data. + */ class AuthorizationPersistence { public: virtual ~AuthorizationPersistence() = default; - /** @brief Persists one entry under public_key (creating the document if needed). */ + /** + * @brief Lists all authorization entries for public_key. + */ + virtual vector list(const string& public_key) = 0; + + /** + * @brief Persists an authorization entry for public_key. + */ virtual void save(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry) = 0; - /** @brief Removes the entry identified */ + /** + * @brief Removes an authorization entry from public_key. + */ virtual void remove(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry) = 0; - /** @brief Removes the whole document for public_key. */ + /** + * @brief Removes all authorization entries for public_key. + */ virtual void remove_all(const string& public_key) = 0; }; - } // namespace atomdb diff --git a/src/atomdb/auth/BUILD b/src/atomdb/auth/BUILD index 12216a57..bae7380e 100644 --- a/src/atomdb/auth/BUILD +++ b/src/atomdb/auth/BUILD @@ -6,8 +6,9 @@ cc_library( name = "auth_lib", includes = ["."], deps = [ - ":authorization_management", + ":authorization_manager", ":authorization_manifest", + ":manifest_authorizer", ":mongo_authorization_persistence", ], ) @@ -37,12 +38,11 @@ cc_library( ) cc_library( - name = "authorization_management", - srcs = ["AuthorizationManagement.cc"], - hdrs = ["AuthorizationManagement.h"], + name = "authorization_manager", + srcs = ["AuthorizationManager.cc"], + hdrs = ["AuthorizationManager.h"], includes = ["."], deps = [ - ":authorization_manifest", ":mongo_authorization_persistence", "//atomdb", "//atomdb:atomdb_api_types", @@ -51,6 +51,20 @@ cc_library( ], ) +cc_library( + name = "manifest_authorizer", + srcs = ["ManifestAuthorizer.cc"], + hdrs = ["ManifestAuthorizer.h"], + includes = ["."], + deps = [ + ":authorization_manifest", + "//atomdb", + "//atomdb:atomdb_api_types", + "//commons:commons_lib", + "//commons/atoms:atoms_lib", + ], +) + cc_library( name = "authorization_manifest", srcs = ["AuthorizationManifest.cc"], diff --git a/src/atomdb/auth/ManifestAuthorizer.cc b/src/atomdb/auth/ManifestAuthorizer.cc new file mode 100644 index 00000000..a832f423 --- /dev/null +++ b/src/atomdb/auth/ManifestAuthorizer.cc @@ -0,0 +1,94 @@ +#include "ManifestAuthorizer.h" + +#include "Assignment.h" +#include "Atom.h" +#include "AtomDB.h" +#include "Link.h" +#include "Utils.h" + +using namespace atomdb; +using namespace atoms; +using namespace commons; + +// -------------------------------------------------------------------------------- +// Constructor + +ManifestAuthorizer::ManifestAuthorizer(shared_ptr manifest) + : manifest(manifest) {} + +// -------------------------------------------------------------------------------- +// Public methods + +bool ManifestAuthorizer::is_authorized(const Atom& atom, + const string& public_key, + AuthorizationOperation operation, + HandleDecoder& decoder) { + if (!this->manifest->is_registered(public_key)) { + return false; + } + if (this->manifest->full_access(public_key)) { + return true; + } + + auto document = this->manifest->get_document(public_key); + for (const auto& entry : document->entries) { + if (this->allows(entry, operation) && this->matches_schema(entry.schema, atom, decoder)) { + return true; + } + } + return false; +} + +bool ManifestAuthorizer::is_authorized(const string& handle, + const string& public_key, + AuthorizationOperation operation, + HandleDecoder& decoder) { + if (!this->manifest->is_registered(public_key)) { + return false; + } + if (this->manifest->full_access(public_key)) { + return true; + } + + auto document = this->manifest->get_document(public_key); + for (const auto& entry : document->entries) { + if (allows(entry, operation) && this->matches_schema(entry.schema, handle, decoder)) { + return true; + } + } + return false; +} + +// -------------------------------------------------------------------------------- +// Private methods + +bool ManifestAuthorizer::allows(const atomdb_api_types::AccessPermissionEntry& entry, + AuthorizationOperation operation) { + switch (operation) { + case AuthorizationOperation::READ: + return entry.read; + case AuthorizationOperation::WRITE: + return entry.write; + } + return false; +} + +bool ManifestAuthorizer::matches_schema(const LinkSchema& schema, + const Atom& atom, + HandleDecoder& decoder) const { + Assignment assignment; + LinkSchema local_schema(schema); + if (Atom::is_link(atom)) { + auto& link = const_cast(static_cast(atom)); + return local_schema.match(link, assignment, decoder); + } + return local_schema.match(atom.handle(), assignment, decoder); +} + +bool ManifestAuthorizer::matches_schema(const LinkSchema& schema, + const string& handle, + HandleDecoder& decoder) const { + Assignment assignment; + LinkSchema local_schema(schema); + return local_schema.match(handle, assignment, decoder); +} \ No newline at end of file diff --git a/src/atomdb/auth/ManifestAuthorizer.h b/src/atomdb/auth/ManifestAuthorizer.h new file mode 100644 index 00000000..4fcfa25b --- /dev/null +++ b/src/atomdb/auth/ManifestAuthorizer.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include + +#include "Atom.h" +#include "AtomDBAPITypes.h" +#include "AuthorizationManifest.h" +#include "HandleDecoder.h" + +using namespace std; +using namespace atoms; + +namespace atomdb { + +enum class AuthorizationOperation { READ, WRITE }; + +/** + * @brief Evaluates authorization requests using an in-memory AuthorizationManifest. + */ +class ManifestAuthorizer { + public: + explicit ManifestAuthorizer(shared_ptr manifest); + + /** + * @brief Checks whether public_key is authorized to perform an operation on an atom. + */ + bool is_authorized(const Atom& atom, + const string& public_key, + AuthorizationOperation operation, + HandleDecoder& decoder); + + /** + * @brief Checks whether public_key is authorized to perform an operation on a handle. + */ + bool is_authorized(const string& handle, + const string& public_key, + AuthorizationOperation operation, + HandleDecoder& decoder); + + private: + shared_ptr manifest; + + bool matches_schema(const LinkSchema& schema, const Atom& atom, HandleDecoder& decoder) const; + bool matches_schema(const LinkSchema& schema, const string& handle, HandleDecoder& decoder) const; + + static bool allows(const atomdb_api_types::AccessPermissionEntry& entry, + AuthorizationOperation operation); +}; + +} // namespace atomdb diff --git a/src/atomdb/auth/MongoAuthorizationPersistence.cc b/src/atomdb/auth/MongoAuthorizationPersistence.cc index 8e66dfe4..fa6bd134 100644 --- a/src/atomdb/auth/MongoAuthorizationPersistence.cc +++ b/src/atomdb/auth/MongoAuthorizationPersistence.cc @@ -2,6 +2,7 @@ #include "Hasher.h" #include "JsonConfig.h" +#include "MongoInitializer.h" #include "Utils.h" #include "expression_hasher.h" #include "nlohmann/json.hpp" @@ -30,9 +31,11 @@ MongoAuthorizationPersistence::MongoAuthorizationPersistence(const string& endpo this->mongodb_pool = new mongocxx::pool(uri); auto conn = this->mongodb_pool->acquire(); auto mongodb = (*conn)[database_name]; - const auto ping_cmd = bsoncxx::builder::basic::make_document(bsoncxx::builder::basic::kvp("ping", 1)); + const auto ping_cmd = + bsoncxx::builder::basic::make_document(bsoncxx::builder::basic::kvp("ping", 1)); mongodb.run_command(ping_cmd.view()); - LOG_DEBUG("MongoAuthorizationPersistence connected to MongoDB at " << endpoint << " (db=" << database_name << ")"); + LOG_DEBUG("MongoAuthorizationPersistence connected to MongoDB at " + << endpoint << " (db=" << database_name << ")"); } catch (const exception& e) { RAISE_ERROR(e.what()); } @@ -43,6 +46,14 @@ MongoAuthorizationPersistence::~MongoAuthorizationPersistence() { delete this->m // -------------------------------------------------------------------------------- // Public methods +vector MongoAuthorizationPersistence::list( + const string& public_key) { + auto conn = this->mongodb_pool->acquire(); + auto collection = (*conn)[this->database_name][this->collection_name]; + auto document = this->get_document(collection, public_key); + return document->entries; +} + void MongoAuthorizationPersistence::save(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry) { auto conn = this->mongodb_pool->acquire(); diff --git a/src/atomdb/auth/MongoAuthorizationPersistence.h b/src/atomdb/auth/MongoAuthorizationPersistence.h index 970095ed..9c96f76a 100644 --- a/src/atomdb/auth/MongoAuthorizationPersistence.h +++ b/src/atomdb/auth/MongoAuthorizationPersistence.h @@ -17,12 +17,17 @@ using namespace std; namespace atomdb { +/** + * @brief MongoDB implementation of the authorization persistence interface. + */ class MongoAuthorizationPersistence : public AuthorizationPersistence { public: /** - * @param endpoint ip:port - * @param database_name Mongo database name - * @param collection_name access_permissions collection name + * @param endpoint MongoDB server endpoint in the format ip:port. + * @param username MongoDB username. + * @param password MongoDB password. + * @param database_name MongoDB database name. + * @param collection_name MongoDB collection used to store authorization data. */ MongoAuthorizationPersistence(const string& endpoint, const string& username, @@ -31,8 +36,24 @@ class MongoAuthorizationPersistence : public AuthorizationPersistence { const string& collection_name); ~MongoAuthorizationPersistence(); + /** + * @brief Lists all authorization entries for public_key. + */ + vector list(const string& public_key) override; + + /** + * @brief Persists an authorization entry for public_key. + */ void save(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry) override; + + /** + * @brief Removes an authorization entry from public_key. + */ void remove(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry) override; + + /** + * @brief Removes all authorization entries for public_key. + */ void remove_all(const string& public_key) override; private: @@ -43,7 +64,6 @@ class MongoAuthorizationPersistence : public AuthorizationPersistence { bsoncxx::document::value entry_to_document(const atomdb_api_types::AccessPermissionEntry& entry); shared_ptr get_document(mongocxx::collection& collection, const string& public_key); - mongocxx::collection get_mongodb_collection(); }; } // namespace atomdb diff --git a/src/tests/cpp/BUILD b/src/tests/cpp/BUILD index bc4b1494..b2d0f0be 100644 --- a/src/tests/cpp/BUILD +++ b/src/tests/cpp/BUILD @@ -773,7 +773,6 @@ cc_test( linkstatic = 1, deps = [ "//atomdb:atomdb_singleton", - "//atomdb/auth:mongo_authorization_persistence", "//tests/cpp/test_commons:mock_animals_data_lib", "//tests/cpp/test_commons:test_atomdb_json_config", "@com_github_google_googletest//:gtest_main", @@ -878,9 +877,7 @@ cc_test( ], linkstatic = 1, deps = [ - "//atomdb/auth:authorization_management", - "//atomdb/auth:authorization_persistence", - "//atomdb/auth:authorization_types", + "//atomdb/auth:auth_lib", "//atomdb/inmemorydb:inmemorydb_lib", "//commons/atoms:atoms_lib", "@com_github_google_googletest//:gtest_main", diff --git a/src/tests/cpp/authorization_test.cc b/src/tests/cpp/authorization_test.cc index 0e09d7e4..7eff7717 100644 --- a/src/tests/cpp/authorization_test.cc +++ b/src/tests/cpp/authorization_test.cc @@ -1,15 +1,17 @@ #include +#include #include #include #include -#include "AuthorizationManagement.h" +#include "AuthorizationManager.h" #include "AuthorizationManifest.h" #include "AuthorizationPersistence.h" #include "InMemoryDB.h" #include "Link.h" #include "LinkSchema.h" +#include "ManifestAuthorizer.h" #include "Node.h" using namespace atomdb; @@ -37,30 +39,34 @@ AccessPermissionEntry read_only_inheritance_entry() { return AccessPermissionEntry(inheritance_mammal_tokens(), true, false); } -class FakePersistence : public AuthorizationPersistence { +class DummyPersistence : public AuthorizationPersistence { public: - int save_count = 0; - int remove_count = 0; - int remove_all_count = 0; - string last_key; - string last_handle; - - void save(const string& public_key, const AccessPermissionEntry& entry) override { - this->save_count++; - this->last_key = public_key; - this->last_handle = entry.schema.handle(); + map> documents; + + vector list(const string& public_key) override { + return {}; } - void remove(const string& public_key, const string& handle) override { - this->remove_count++; - this->last_key = public_key; - this->last_handle = handle; + void save(const string& public_key, const atomdb_api_types::AccessPermissionEntry& entry) override { + auto& handles = documents[public_key]; + handles.push_back(entry.schema.handle()); } - void remove_all(const string& public_key) override { - this->remove_all_count++; - this->last_key = public_key; + void remove(const string& public_key, + const atomdb_api_types::AccessPermissionEntry& entry) override { + auto it = documents.find(public_key); + if (it == documents.end()) { + return; + } + + auto& handles = it->second; + + auto handle = entry.schema.handle(); + + handles.erase(std::remove(handles.begin(), handles.end(), handle), handles.end()); } + + void remove_all(const string& public_key) override { documents.erase(public_key); } }; shared_ptr db_with_inheritance_link(string* link_handle) { @@ -82,114 +88,99 @@ shared_ptr db_with_inheritance_link(string* link_handle) { } // namespace -TEST(AuthorizationManifestTest, SetAddRemoveAndFullAccess) { +TEST(AuthorizationManifestTest, ManagesAuthorizationEntriesAndAccess) { AuthorizationManifest manifest; - string key = "pk1"; - EXPECT_FALSE(manifest.is_registered(key)); - EXPECT_FALSE(manifest.full_access(key)); - EXPECT_TRUE(manifest.entries(key).empty()); + string public_key = "pk1"; + + EXPECT_FALSE(manifest.is_registered(public_key)); + EXPECT_FALSE(manifest.full_access(public_key)); + + auto entries = manifest.get_document(public_key)->entries; + EXPECT_TRUE(entries.empty()); AccessPermissionEntry entry = read_only_inheritance_entry(); - manifest.add(key, entry); - ASSERT_TRUE(manifest.is_registered(key)); - ASSERT_EQ(manifest.entries(key).size(), 1u); - EXPECT_TRUE(manifest.entries(key)[0].read); - EXPECT_FALSE(manifest.entries(key)[0].write); - EXPECT_EQ(manifest.entries(key)[0].schema.handle(), entry.schema.handle()); - - manifest.remove(key, entry.schema.handle()); - EXPECT_TRUE(manifest.is_registered(key)); - EXPECT_TRUE(manifest.entries(key).empty()); - - manifest.set(AccessPermissionDocument(key, true, {})); - EXPECT_TRUE(manifest.full_access(key)); - - manifest.remove_all(key); - EXPECT_FALSE(manifest.is_registered(key)); + manifest.add(public_key, entry); + + ASSERT_TRUE(manifest.is_registered(public_key)); + + ASSERT_EQ(entries.size(), 1u); + EXPECT_TRUE(entries[0].read); + EXPECT_FALSE(entries[0].write); + EXPECT_EQ(entries[0].schema.handle(), entry.schema.handle()); + + manifest.remove(public_key, entry); + EXPECT_TRUE(manifest.is_registered(public_key)); + EXPECT_TRUE(entries.empty()); + + manifest.set(AccessPermissionDocument(public_key, true, {})); + EXPECT_TRUE(manifest.full_access(public_key)); + + manifest.remove_all(public_key); + EXPECT_FALSE(manifest.is_registered(public_key)); } TEST(AuthorizationManifestTest, AddReplacesEntryWithSameSchemaHandle) { AuthorizationManifest manifest; - string key = "pk1"; - manifest.add(key, AccessPermissionEntry(inheritance_mammal_tokens(), true, false)); - manifest.add(key, AccessPermissionEntry(inheritance_mammal_tokens(), false, true)); + string public_key = "pk1"; + manifest.add(public_key, AccessPermissionEntry(inheritance_mammal_tokens(), true, false)); + manifest.add(public_key, AccessPermissionEntry(inheritance_mammal_tokens(), false, true)); - ASSERT_EQ(manifest.entries(key).size(), 1u); - EXPECT_FALSE(manifest.entries(key)[0].read); - EXPECT_TRUE(manifest.entries(key)[0].write); -} + auto entries = manifest.get_document(public_key)->entries; -TEST(AuthorizationManagementTest, RejectsNullAtomDB) { - EXPECT_THROW(AuthorizationManagement(nullptr, nullptr), runtime_error); + ASSERT_EQ(entries.size(), 1u); + EXPECT_FALSE(entries[0].read); + EXPECT_TRUE(entries[0].write); } -TEST(AuthorizationManagementTest, AdministrationRequiresPersistence) { - auto db = make_shared("auth_admin_"); - AuthorizationManagement management(db, nullptr); - AccessPermissionEntry entry = read_only_inheritance_entry(); - EXPECT_THROW(management.authorize("pk", entry), runtime_error); - EXPECT_THROW(management.revoke("pk", entry.schema.handle()), runtime_error); - EXPECT_THROW(management.revoke_all("pk"), runtime_error); -} - -TEST(AuthorizationManagementTest, UnregisteredKeyIsDenied) { +TEST(ManifestAuthorizerTest, IsAuthorized) { string link_handle; auto db = db_with_inheritance_link(&link_handle); - auto persistence = make_shared(); - AuthorizationManagement management(db, persistence); + + auto manifest = make_shared(); + auto authorizer = make_shared(manifest); auto link = db->get_link(link_handle); ASSERT_NE(link, nullptr); - EXPECT_FALSE(management.is_authorized(*link, "unknown", AuthorizationOperation::READ)); - EXPECT_FALSE(management.is_authorized(link_handle, "unknown", AuthorizationOperation::READ, *db)); -} + EXPECT_FALSE(authorizer->is_authorized(*link, "unknown", AuthorizationOperation::READ, *db)); + EXPECT_FALSE(authorizer->is_authorized(link_handle, "unknown", AuthorizationOperation::READ, *db)); -TEST(AuthorizationManagementTest, AuthorizeThenReadAndWriteFlags) { - string link_handle; - auto db = db_with_inheritance_link(&link_handle); - auto persistence = make_shared(); - AuthorizationManagement management(db, persistence); AccessPermissionEntry entry = read_only_inheritance_entry(); + manifest->add("pk", entry); + EXPECT_TRUE(authorizer->is_authorized(*link, "pk", AuthorizationOperation::READ, *db)); + EXPECT_TRUE(authorizer->is_authorized(link_handle, "pk", AuthorizationOperation::READ, *db)); - management.authorize("pk", entry); - EXPECT_EQ(persistence->save_count, 1); - EXPECT_EQ(persistence->last_key, "pk"); + EXPECT_FALSE(authorizer->is_authorized(*link, "pk", AuthorizationOperation::WRITE, *db)); + EXPECT_FALSE(authorizer->is_authorized(link_handle, "pk", AuthorizationOperation::WRITE, *db)); +} - auto link = db->get_link(link_handle); - ASSERT_NE(link, nullptr); - EXPECT_TRUE(management.is_authorized(*link, "pk", AuthorizationOperation::READ)); - EXPECT_FALSE(management.is_authorized(*link, "pk", AuthorizationOperation::WRITE)); - EXPECT_TRUE(management.is_authorized(link_handle, "pk", AuthorizationOperation::READ, *db)); - EXPECT_FALSE(management.is_authorized(link_handle, "pk", AuthorizationOperation::WRITE, *db)); +TEST(AuthorizationManagerTest, AdministrationRequiresPersistence) { + AuthorizationManager manager(nullptr); + AccessPermissionEntry entry = read_only_inheritance_entry(); + EXPECT_THROW(manager.authorize("pk", entry), runtime_error); + EXPECT_THROW(manager.revoke("pk", entry), runtime_error); + EXPECT_THROW(manager.revoke_all("pk"), runtime_error); } -TEST(AuthorizationManagementTest, RevokeRemovesAccess) { +TEST(AuthorizationManagerTest, AuthorizeThenReadAndWriteFlags) { string link_handle; auto db = db_with_inheritance_link(&link_handle); - auto persistence = make_shared(); - AuthorizationManagement management(db, persistence); + + auto persistence = make_shared(); + AuthorizationManager manager(persistence); AccessPermissionEntry entry = read_only_inheritance_entry(); - management.authorize("pk", entry); - management.revoke("pk", entry.schema.handle()); - EXPECT_EQ(persistence->remove_count, 1); + manager.authorize("pk", entry); + manager.authorize("pk2", entry); - auto link = db->get_link(link_handle); - ASSERT_NE(link, nullptr); - EXPECT_FALSE(management.is_authorized(*link, "pk", AuthorizationOperation::READ)); -} + EXPECT_EQ(persistence->documents.size(), 2); + EXPECT_EQ(persistence->documents["pk"], vector{entry.schema.handle()}); + EXPECT_EQ(persistence->documents["pk2"], vector{entry.schema.handle()}); -TEST(AuthorizationManagementTest, DoesNotLoadPermissionsFromAtomDB) { - class PermissionsInMemoryDB : public InMemoryDB { - public: - explicit PermissionsInMemoryDB(const string& context) : InMemoryDB(context) {} - vector get_access_permissions( - const PublicKey& public_key) const override { - return {AccessPermissionDocument("pk", true, {})}; - } - }; + manager.revoke("pk", entry); + EXPECT_EQ(persistence->documents.size(), 2); + EXPECT_EQ(persistence->documents["pk"], vector{}); + EXPECT_EQ(persistence->documents["pk2"], vector{entry.schema.handle()}); - auto db = make_shared("auth_noload_"); - auto persistence = make_shared(); - AuthorizationManagement management(persistence); -} + manager.revoke_all("pk"); + EXPECT_EQ(persistence->documents.size(), 1); +} \ No newline at end of file diff --git a/src/tests/cpp/redis_mongodb_test.cc b/src/tests/cpp/redis_mongodb_test.cc index 83bf460f..23c7a14c 100644 --- a/src/tests/cpp/redis_mongodb_test.cc +++ b/src/tests/cpp/redis_mongodb_test.cc @@ -20,7 +20,6 @@ #include "Merger.h" #include "MettaMapping.h" #include "MockAnimalsData.h" -#include "MongoAuthorizationPersistence.h" #include "Node.h" #include "RedisMongoDB.h" #include "TestAtomDBJsonConfig.h" @@ -1485,58 +1484,6 @@ TEST_F(RedisMongoDBTest, GetAccessPermissionsRejectsInvalidDocument) { collection.delete_many({}); } -TEST_F(RedisMongoDBTest, MongoAuthorizationPersistenceRoundTrip) { - using bsoncxx::builder::basic::kvp; - using bsoncxx::builder::basic::make_document; - - auto pool = db->get_mongo_pool(); - auto conn = pool->acquire(); - auto collection = - (*conn)[RedisMongoDB::MONGODB_DB_NAME][RedisMongoDB::MONGODB_ACCESS_PERMISSIONS_COLLECTION_NAME]; - collection.delete_many({}); - - MongoAuthorizationPersistence persistence( - pool, RedisMongoDB::MONGODB_DB_NAME, RedisMongoDB::MONGODB_ACCESS_PERMISSIONS_COLLECTION_NAME); - - vector tokens = { - "LINK_TEMPLATE", "Expression", "2", "NODE", "Symbol", "Similarity", "VARIABLE", "VARIABLE"}; - AccessPermissionEntry entry(tokens, true, false); - persistence.save("key_reader", entry); - - string id = compute_hash((char*) "key_reader"); - auto stored = collection.find_one(make_document(kvp("_id", id))); - ASSERT_TRUE(stored); - EXPECT_FALSE(stored->view()["public_key"]); - EXPECT_EQ(string(stored->view()["_id"].get_string().value), id); - EXPECT_FALSE(stored->view()["full_access"].get_bool().value); - - auto docs = db->get_access_permissions(PublicKey("key_reader")); - ASSERT_EQ(docs.size(), 1u); - EXPECT_EQ(docs[0].public_key, "key_reader"); - EXPECT_FALSE(docs[0].full_access); - ASSERT_EQ(docs[0].entries.size(), 1u); - EXPECT_TRUE(docs[0].entries[0].read); - EXPECT_FALSE(docs[0].entries[0].write); - EXPECT_EQ(docs[0].entries[0].schema.handle(), entry.schema.handle()); - - persistence.save("key_reader", AccessPermissionEntry(tokens, false, true)); - docs = db->get_access_permissions(PublicKey("key_reader")); - ASSERT_EQ(docs.size(), 1u); - ASSERT_EQ(docs[0].entries.size(), 1u); - EXPECT_FALSE(docs[0].entries[0].read); - EXPECT_TRUE(docs[0].entries[0].write); - - persistence.remove("key_reader", entry.schema.handle()); - docs = db->get_access_permissions(PublicKey("key_reader")); - ASSERT_EQ(docs.size(), 1u); - EXPECT_TRUE(docs[0].entries.empty()); - - persistence.remove_all("key_reader"); - EXPECT_TRUE(db->get_access_permissions(PublicKey("key_reader")).empty()); - - collection.delete_many({}); -} - TEST_F(RedisMongoDBTest, IsProtectedWhenPersistedConfigIsTrue) { using bsoncxx::builder::basic::kvp; using bsoncxx::builder::basic::make_document; From e9a6d5b4bdf86a9c8d0d37b46f5696553dc3a338 Mon Sep 17 00:00:00 2001 From: marcocapozzoli Date: Thu, 20 Aug 2026 13:37:58 -0300 Subject: [PATCH 12/16] Add explicit --- src/atomdb/ProtectedAtomDB.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/atomdb/ProtectedAtomDB.h b/src/atomdb/ProtectedAtomDB.h index 354ab822..e21036eb 100644 --- a/src/atomdb/ProtectedAtomDB.h +++ b/src/atomdb/ProtectedAtomDB.h @@ -28,7 +28,7 @@ class ProtectedAtomDB : public AtomDB { /** * @param backend Shared concrete AtomDB to wrap. */ - ProtectedAtomDB(shared_ptr backend); + explicit ProtectedAtomDB(shared_ptr backend); bool allow_nested_indexing() override; bool composite_type_enabled() const override; From 006c3a0253c55bcf6a760c7376b8eb95e3cf5104 Mon Sep 17 00:00:00 2001 From: marcocapozzoli Date: Thu, 20 Aug 2026 13:49:03 -0300 Subject: [PATCH 13/16] Add destructor and fix test --- src/atomdb/auth/AuthorizationManager.h | 2 ++ src/atomdb/auth/AuthorizationManifest.h | 1 + src/atomdb/auth/ManifestAuthorizer.h | 1 + src/tests/cpp/authorization_test.cc | 18 +++++++----------- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/atomdb/auth/AuthorizationManager.h b/src/atomdb/auth/AuthorizationManager.h index d75d3b13..2f5f6309 100644 --- a/src/atomdb/auth/AuthorizationManager.h +++ b/src/atomdb/auth/AuthorizationManager.h @@ -22,6 +22,8 @@ class AuthorizationManager { */ AuthorizationManager(shared_ptr persistence); + ~AuthorizationManager() = default; + /** * @brief Lists all permissions granted to public_key. */ diff --git a/src/atomdb/auth/AuthorizationManifest.h b/src/atomdb/auth/AuthorizationManifest.h index 700d3046..6030e746 100644 --- a/src/atomdb/auth/AuthorizationManifest.h +++ b/src/atomdb/auth/AuthorizationManifest.h @@ -19,6 +19,7 @@ namespace atomdb { class AuthorizationManifest { public: AuthorizationManifest() = default; + ~AuthorizationManifest() = default; /** * @brief Replaces or inserts the authorization document. diff --git a/src/atomdb/auth/ManifestAuthorizer.h b/src/atomdb/auth/ManifestAuthorizer.h index 4fcfa25b..6c1fae44 100644 --- a/src/atomdb/auth/ManifestAuthorizer.h +++ b/src/atomdb/auth/ManifestAuthorizer.h @@ -21,6 +21,7 @@ enum class AuthorizationOperation { READ, WRITE }; class ManifestAuthorizer { public: explicit ManifestAuthorizer(shared_ptr manifest); + ~ManifestAuthorizer() = default; /** * @brief Checks whether public_key is authorized to perform an operation on an atom. diff --git a/src/tests/cpp/authorization_test.cc b/src/tests/cpp/authorization_test.cc index 7eff7717..4370b9b6 100644 --- a/src/tests/cpp/authorization_test.cc +++ b/src/tests/cpp/authorization_test.cc @@ -95,14 +95,17 @@ TEST(AuthorizationManifestTest, ManagesAuthorizationEntriesAndAccess) { EXPECT_FALSE(manifest.is_registered(public_key)); EXPECT_FALSE(manifest.full_access(public_key)); - auto entries = manifest.get_document(public_key)->entries; - EXPECT_TRUE(entries.empty()); + EXPECT_EQ(manifest.get_document(public_key), nullptr); AccessPermissionEntry entry = read_only_inheritance_entry(); manifest.add(public_key, entry); ASSERT_TRUE(manifest.is_registered(public_key)); + auto doc = manifest.get_document(public_key); + ASSERT_NE(doc, nullptr); + + auto entries = doc->entries; ASSERT_EQ(entries.size(), 1u); EXPECT_TRUE(entries[0].read); EXPECT_FALSE(entries[0].write); @@ -110,7 +113,8 @@ TEST(AuthorizationManifestTest, ManagesAuthorizationEntriesAndAccess) { manifest.remove(public_key, entry); EXPECT_TRUE(manifest.is_registered(public_key)); - EXPECT_TRUE(entries.empty()); + + EXPECT_TRUE(manifest.get_document(public_key)->entries.empty()); manifest.set(AccessPermissionDocument(public_key, true, {})); EXPECT_TRUE(manifest.full_access(public_key)); @@ -153,14 +157,6 @@ TEST(ManifestAuthorizerTest, IsAuthorized) { EXPECT_FALSE(authorizer->is_authorized(link_handle, "pk", AuthorizationOperation::WRITE, *db)); } -TEST(AuthorizationManagerTest, AdministrationRequiresPersistence) { - AuthorizationManager manager(nullptr); - AccessPermissionEntry entry = read_only_inheritance_entry(); - EXPECT_THROW(manager.authorize("pk", entry), runtime_error); - EXPECT_THROW(manager.revoke("pk", entry), runtime_error); - EXPECT_THROW(manager.revoke_all("pk"), runtime_error); -} - TEST(AuthorizationManagerTest, AuthorizeThenReadAndWriteFlags) { string link_handle; auto db = db_with_inheritance_link(&link_handle); From 396d89f2baf1e99cead2db00757f38acec0d2433 Mon Sep 17 00:00:00 2001 From: Marco Capozzoli <55926220+marcocapozzoli@users.noreply.github.com> Date: Thu, 20 Aug 2026 13:56:37 -0300 Subject: [PATCH 14/16] Update src/atomdb/auth/AuthorizationManager.cc Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/atomdb/auth/AuthorizationManager.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/atomdb/auth/AuthorizationManager.cc b/src/atomdb/auth/AuthorizationManager.cc index 4803e33c..de923681 100644 --- a/src/atomdb/auth/AuthorizationManager.cc +++ b/src/atomdb/auth/AuthorizationManager.cc @@ -7,7 +7,11 @@ using namespace atomdb; // Constructor AuthorizationManager::AuthorizationManager(shared_ptr persistence) - : persistence(persistence) {} + : persistence(persistence) { + if (!this->persistence) { + RAISE_ERROR("Authorization persistence is required"); + } +} // -------------------------------------------------------------------------------- // Public methods From 46c762714be2d8a3a64b965fb3318dcfd69f4b75 Mon Sep 17 00:00:00 2001 From: marcocapozzoli Date: Thu, 20 Aug 2026 14:10:32 -0300 Subject: [PATCH 15/16] Add namespace --- src/atomdb/auth/AuthorizationManager.cc | 1 + src/atomdb/auth/AuthorizationManifest.cc | 1 + 2 files changed, 2 insertions(+) diff --git a/src/atomdb/auth/AuthorizationManager.cc b/src/atomdb/auth/AuthorizationManager.cc index de923681..24bd5bdf 100644 --- a/src/atomdb/auth/AuthorizationManager.cc +++ b/src/atomdb/auth/AuthorizationManager.cc @@ -1,6 +1,7 @@ #include "AuthorizationManager.h" +using namespace std; using namespace atomdb; // -------------------------------------------------------------------------------- diff --git a/src/atomdb/auth/AuthorizationManifest.cc b/src/atomdb/auth/AuthorizationManifest.cc index 3d81e63c..0cdf87eb 100644 --- a/src/atomdb/auth/AuthorizationManifest.cc +++ b/src/atomdb/auth/AuthorizationManifest.cc @@ -6,6 +6,7 @@ #include "Logger.h" #include "Utils.h" +using namespace std; using namespace atomdb; // -------------------------------------------------------------------------------- From 2a4439d4d257ace6fe2ebd4e7305ccee99f8303d Mon Sep 17 00:00:00 2001 From: marcocapozzoli Date: Thu, 20 Aug 2026 21:54:14 -0300 Subject: [PATCH 16/16] Add includes; Reject null manifest in ManifestAuthorizer() --- src/atomdb/auth/ManifestAuthorizer.cc | 7 +++++-- src/atomdb/auth/MongoAuthorizationPersistence.cc | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/atomdb/auth/ManifestAuthorizer.cc b/src/atomdb/auth/ManifestAuthorizer.cc index a832f423..91aebf05 100644 --- a/src/atomdb/auth/ManifestAuthorizer.cc +++ b/src/atomdb/auth/ManifestAuthorizer.cc @@ -13,8 +13,11 @@ using namespace commons; // -------------------------------------------------------------------------------- // Constructor -ManifestAuthorizer::ManifestAuthorizer(shared_ptr manifest) - : manifest(manifest) {} +ManifestAuthorizer::ManifestAuthorizer(shared_ptr manifest) : manifest(manifest) { + if (this->manifest == nullptr) { + RAISE_ERROR("ManifestAuthorizer requires a non-null AuthorizationManifest"); + } +} // -------------------------------------------------------------------------------- // Public methods diff --git a/src/atomdb/auth/MongoAuthorizationPersistence.cc b/src/atomdb/auth/MongoAuthorizationPersistence.cc index fa6bd134..3c691c9c 100644 --- a/src/atomdb/auth/MongoAuthorizationPersistence.cc +++ b/src/atomdb/auth/MongoAuthorizationPersistence.cc @@ -5,6 +5,8 @@ #include "MongoInitializer.h" #include "Utils.h" #include "expression_hasher.h" +#define LOG_LEVEL INFO_LEVEL +#include "Logger.h" #include "nlohmann/json.hpp" using namespace atomdb; @@ -51,6 +53,7 @@ vector MongoAuthorizationPersistence::l auto conn = this->mongodb_pool->acquire(); auto collection = (*conn)[this->database_name][this->collection_name]; auto document = this->get_document(collection, public_key); + if (!document) return {}; return document->entries; }