From 81b017cf0b856f6c18264345f62a9a25cb328402 Mon Sep 17 00:00:00 2001 From: marcocapozzoli Date: Thu, 22 Oct 2026 22:00:35 -0300 Subject: [PATCH 1/4] Skip per-handle auth and cache re-query for unrestricted RemoteAtomDB pattern fetches --- src/atomdb/ProtectedAtomDB.cc | 23 ++++++++++++---- src/atomdb/ProtectedAtomDB.h | 6 +++- src/atomdb/auth/AuthorizationManifest.cc | 7 +++++ src/atomdb/auth/AuthorizationManifest.h | 8 ++++++ src/atomdb/remotedb/RemoteAtomDBPeer.cc | 35 ++++++++++++++---------- src/tests/cpp/authorization_test.cc | 18 ++++++++++++ 6 files changed, 77 insertions(+), 20 deletions(-) diff --git a/src/atomdb/ProtectedAtomDB.cc b/src/atomdb/ProtectedAtomDB.cc index 402aaf3b6..7120f3c08 100644 --- a/src/atomdb/ProtectedAtomDB.cc +++ b/src/atomdb/ProtectedAtomDB.cc @@ -359,6 +359,7 @@ optional ProtectedAtomDB::authorize_read(const string& handle, optional ProtectedAtomDB::authorize_read(const shared_ptr& atom, const shared_ptr& keychain) { + if (atom == nullptr) return nullopt; auto public_key = this->try_get_public_key(keychain); if (!public_key || !this->can_read(atom, *public_key)) return nullopt; return public_key; @@ -374,9 +375,16 @@ set ProtectedAtomDB::check_handles(shared_ptr keychain, shared_ptr ProtectedAtomDB::filter_handle_set( const optional& public_key, const function()>& query) { - if (!public_key) return make_shared(); + if (!public_key) { + LOG_DEBUG("[ProtectedAtomDB(" << this->uid_ << ")] filter_handle_set: skipped, no public key"); + return make_shared(); + } + LOG_DEBUG("[ProtectedAtomDB(" << this->uid_ << ")] filter_handle_set: querying backend"); auto original_handle_set = query(); + LOG_DEBUG("[ProtectedAtomDB(" << this->uid_ << ")] filter_handle_set: backend returned " + << (original_handle_set ? original_handle_set->size() : 0) + << " handles"); auto authorized_handle_set = make_shared(); @@ -384,6 +392,12 @@ shared_ptr ProtectedAtomDB::filter_handle_set( return authorized_handle_set; } + if (this->manifest->is_unrestricted(*public_key)) { + LOG_DEBUG("[ProtectedAtomDB(" << this->uid_ + << ")] filter_handle_set: unrestricted, skipping authorization"); + return original_handle_set; + } + auto it = original_handle_set->get_iterator(); while (true) { @@ -399,10 +413,9 @@ shared_ptr ProtectedAtomDB::filter_handle_set( } } - LOG_DEBUG("ProtectedAtomDB::filter_handle_set() - original handle_set: " + - std::to_string(original_handle_set->size())); - LOG_DEBUG("ProtectedAtomDB::filter_handle_set() - authorized handle_set: " + - std::to_string(authorized_handle_set->size())); + LOG_DEBUG("[ProtectedAtomDB(" << this->uid_ + << ")] filter_handle_set: " << original_handle_set->size() << " -> " + << authorized_handle_set->size() << " authorized"); return authorized_handle_set; } diff --git a/src/atomdb/ProtectedAtomDB.h b/src/atomdb/ProtectedAtomDB.h index 34a4cbc08..438154ad4 100644 --- a/src/atomdb/ProtectedAtomDB.h +++ b/src/atomdb/ProtectedAtomDB.h @@ -208,7 +208,11 @@ class ProtectedAtomDB : public AtomDB, public KeySensitiveAtomDB { */ inline bool can_read(const string& handle, const string& public_key) { if (public_key.empty()) return false; - return this->manifest->is_granted(public_key, handle, AuthorizationOperation::READ); + if (this->manifest->is_unrestricted(public_key)) { + return true; + } else { + return this->can_read(this->backend->get_atom(handle), public_key); + } } /** diff --git a/src/atomdb/auth/AuthorizationManifest.cc b/src/atomdb/auth/AuthorizationManifest.cc index d5c802423..c59548ca8 100644 --- a/src/atomdb/auth/AuthorizationManifest.cc +++ b/src/atomdb/auth/AuthorizationManifest.cc @@ -92,6 +92,13 @@ void AuthorizationManifest::count_matching_atoms(size_t& node_count, profile->count_matching_atoms(node_count, link_count, atom_count, keychain); } +bool AuthorizationManifest::is_unrestricted(const string& public_key) { + lock_guard lock(this->profiles_mutex); + auto it = this->profiles.find(public_key); + if (it == this->profiles.end() || it->second == nullptr) return false; + return it->second->is_unrestricted(); +} + // -------------------------------------------------------------------------------- // Private methods diff --git a/src/atomdb/auth/AuthorizationManifest.h b/src/atomdb/auth/AuthorizationManifest.h index 90bf9cb3a..3195e88b1 100644 --- a/src/atomdb/auth/AuthorizationManifest.h +++ b/src/atomdb/auth/AuthorizationManifest.h @@ -51,6 +51,14 @@ class AuthorizationManifest { size_t& atom_count, shared_ptr keychain); + /** + * @brief Returns whether public_key's loaded profile grants unrestricted access. + * + * @return true if the profile is cached and has full access; false if it is + * missing, not loaded, or schema-restricted. + */ + bool is_unrestricted(const string& public_key); + private: shared_ptr atomdb; map> profiles; diff --git a/src/atomdb/remotedb/RemoteAtomDBPeer.cc b/src/atomdb/remotedb/RemoteAtomDBPeer.cc index 3cf773fec..6b4753475 100644 --- a/src/atomdb/remotedb/RemoteAtomDBPeer.cc +++ b/src/atomdb/remotedb/RemoteAtomDBPeer.cc @@ -728,20 +728,20 @@ shared_ptr RemoteAtomDBPeer::query_for_pattern( auto result = make_shared(); set seen; - auto merge_memory = [&]() { + auto merge_write_buffer = [&]() { auto pwb = protected_write_buffer(); - auto wb = write_buffer(); - merge_handle_set( - pwb ? pwb->query_for_pattern(link_schema, keychain) : wb->query_for_pattern(link_schema), - result, - seen); + merge_handle_set(pwb ? pwb->query_for_pattern(link_schema, keychain) + : write_buffer()->query_for_pattern(link_schema), + result, + seen); + }; + auto merge_read_cache = [&]() { auto prc = protected_read_cache(); - auto rc = read_cache(); - merge_handle_set( - prc ? prc->query_for_pattern(link_schema, keychain) : rc->query_for_pattern(link_schema), - result, - seen); + merge_handle_set(prc ? prc->query_for_pattern(link_schema, keychain) + : read_cache()->query_for_pattern(link_schema), + result, + seen); }; auto merge_local_persistence = [&]() { @@ -758,7 +758,8 @@ shared_ptr RemoteAtomDBPeer::query_for_pattern( if (cache_hit) { LOG_DEBUG("[RemoteDB(" << uid_ << ")] query_for_pattern(" << link_schema.handle() << ") cache-hit"); - merge_memory(); + merge_write_buffer(); + merge_read_cache(); merge_local_persistence(); LOG_DEBUG("[RemoteDB(" << uid_ << ")] query_for_pattern(" << link_schema.handle() << ") -> " << result->size() << " handles"); @@ -777,12 +778,18 @@ shared_ptr RemoteAtomDBPeer::query_for_pattern( // Merge remote first when it carries nested metadata. InMemoryDB has no // metta/assignments — if it fills `seen` first, the remote metadata is skipped. merge_handle_set(remote_handle_set, result, seen); + + merge_write_buffer(); + // Do not query the read cache here. It has just been populated + // from remote_handle_set, which is already present in result + } else { + merge_write_buffer(); + merge_read_cache(); } - merge_memory(); merge_local_persistence(); - if (!this->protected_atomdb_ || keychain) { + if (remote_handle_set && (!this->protected_atomdb_ || keychain)) { lock_guard lock(peer_mutex_); fetched_link_templates_.insert(link_schema.handle()); } diff --git a/src/tests/cpp/authorization_test.cc b/src/tests/cpp/authorization_test.cc index 48df43ec7..fbf8e8723 100644 --- a/src/tests/cpp/authorization_test.cc +++ b/src/tests/cpp/authorization_test.cc @@ -199,6 +199,24 @@ TEST(AuthorizationManifestTest, IsGranted) { EXPECT_FALSE(manifest.is_granted("pk", string(32, '0'), AuthorizationOperation::WRITE)); } +TEST(AuthorizationManifestTest, IsUnrestricted) { + auto db = make_shared(); + db->permissions["full"] = make_document("full", true, {}); + db->permissions["restricted"] = make_document("restricted", false, {read_only_inheritance_schema()}); + AuthorizationManifest manifest(db); + + EXPECT_FALSE(manifest.is_unrestricted("full")); + EXPECT_FALSE(manifest.is_unrestricted("restricted")); + EXPECT_FALSE(manifest.is_unrestricted("unknown")); + EXPECT_FALSE(manifest.is_unrestricted("")); + + ASSERT_TRUE(manifest.ensure_profile_loaded("full")); + ASSERT_TRUE(manifest.ensure_profile_loaded("restricted")); + EXPECT_TRUE(manifest.is_unrestricted("full")); + EXPECT_FALSE(manifest.is_unrestricted("restricted")); + EXPECT_FALSE(manifest.is_unrestricted("unknown")); +} + TEST(AuthorizationManifestTest, FullAccessGrantsAllOperations) { string link_handle; auto db = db_with_inheritance_link(&link_handle); From db8320a4d69c231a6ce2bcc5f7ad9d8c944d547c Mon Sep 17 00:00:00 2001 From: marcocapozzoli Date: Fri, 23 Oct 2026 08:48:59 -0300 Subject: [PATCH 2/4] Fix query_for_targets --- src/atomdb/ProtectedAtomDB.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/atomdb/ProtectedAtomDB.cc b/src/atomdb/ProtectedAtomDB.cc index 7120f3c08..078a3a8a7 100644 --- a/src/atomdb/ProtectedAtomDB.cc +++ b/src/atomdb/ProtectedAtomDB.cc @@ -78,7 +78,11 @@ shared_ptr ProtectedAtomDB::query_for_targets( if (!this->authorize_read(handle, keychain)) { return make_shared(); } - return this->backend->query_for_targets(handle); + auto targets = this->backend->query_for_targets(handle); + if (targets == nullptr) { + return make_shared(); + } + return targets; } shared_ptr ProtectedAtomDB::query_for_incoming_set( From 5e5ebd46bd4707d5fa9dc847047027e80ae0e3f9 Mon Sep 17 00:00:00 2001 From: marcocapozzoli Date: Fri, 23 Oct 2026 09:47:23 -0300 Subject: [PATCH 3/4] Return atom_exists() to unrestricted key --- src/atomdb/ProtectedAtomDB.cc | 9 ++++----- src/atomdb/ProtectedAtomDB.h | 9 ++++++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/atomdb/ProtectedAtomDB.cc b/src/atomdb/ProtectedAtomDB.cc index 078a3a8a7..ee9393a14 100644 --- a/src/atomdb/ProtectedAtomDB.cc +++ b/src/atomdb/ProtectedAtomDB.cc @@ -78,11 +78,7 @@ shared_ptr ProtectedAtomDB::query_for_targets( if (!this->authorize_read(handle, keychain)) { return make_shared(); } - auto targets = this->backend->query_for_targets(handle); - if (targets == nullptr) { - return make_shared(); - } - return targets; + return this->backend->query_for_targets(handle); } shared_ptr ProtectedAtomDB::query_for_incoming_set( @@ -426,6 +422,9 @@ shared_ptr ProtectedAtomDB::filter_handle_set( set ProtectedAtomDB::filter_handles(const set& original_handles, const string& public_key) { + if (this->manifest->is_unrestricted(public_key)) { + return original_handles; + } set authorized_handles; for (const auto& handle : original_handles) { if (this->can_read(handle, public_key)) { diff --git a/src/atomdb/ProtectedAtomDB.h b/src/atomdb/ProtectedAtomDB.h index 438154ad4..ed5d91fbf 100644 --- a/src/atomdb/ProtectedAtomDB.h +++ b/src/atomdb/ProtectedAtomDB.h @@ -205,14 +205,17 @@ class ProtectedAtomDB : public AtomDB, public KeySensitiveAtomDB { /** * @brief Returns whether the caller may READ the specified handle. + * + * Unrestricted profiles skip loading the atom and skip Manifest matching; + * existence is still required (atom_exists), so a missing handle is not readable. + * Restricted profiles load the atom here and send it to the Manifest. */ inline bool can_read(const string& handle, const string& public_key) { if (public_key.empty()) return false; if (this->manifest->is_unrestricted(public_key)) { - return true; - } else { - return this->can_read(this->backend->get_atom(handle), public_key); + return this->backend->atom_exists(handle); } + return this->can_read(this->backend->get_atom(handle), public_key); } /** From c4d5697be4a9d74c9c5426d2cf120600ebb6817a Mon Sep 17 00:00:00 2001 From: marcocapozzoli Date: Fri, 23 Oct 2026 14:06:35 -0300 Subject: [PATCH 4/4] Revert changes in RemoteAtomDBPeer::query_for_pattern --- src/atomdb/ProtectedAtomDB.cc | 5 ++-- src/atomdb/remotedb/RemoteAtomDBPeer.cc | 35 ++++++++++--------------- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/src/atomdb/ProtectedAtomDB.cc b/src/atomdb/ProtectedAtomDB.cc index ee9393a14..eb8634004 100644 --- a/src/atomdb/ProtectedAtomDB.cc +++ b/src/atomdb/ProtectedAtomDB.cc @@ -386,10 +386,8 @@ shared_ptr ProtectedAtomDB::filter_handle_set( << (original_handle_set ? original_handle_set->size() : 0) << " handles"); - auto authorized_handle_set = make_shared(); - if (original_handle_set == nullptr) { - return authorized_handle_set; + return make_shared(); } if (this->manifest->is_unrestricted(*public_key)) { @@ -399,6 +397,7 @@ shared_ptr ProtectedAtomDB::filter_handle_set( } auto it = original_handle_set->get_iterator(); + auto authorized_handle_set = make_shared(); while (true) { char* handle_cstr = it->next(); diff --git a/src/atomdb/remotedb/RemoteAtomDBPeer.cc b/src/atomdb/remotedb/RemoteAtomDBPeer.cc index 86e110c46..81cce879c 100644 --- a/src/atomdb/remotedb/RemoteAtomDBPeer.cc +++ b/src/atomdb/remotedb/RemoteAtomDBPeer.cc @@ -748,20 +748,20 @@ shared_ptr RemoteAtomDBPeer::query_for_pattern( auto result = make_shared(); set seen; - auto merge_write_buffer = [&]() { + auto merge_memory = [&]() { auto pwb = protected_write_buffer(); - merge_handle_set(pwb ? pwb->query_for_pattern(link_schema, keychain) - : write_buffer()->query_for_pattern(link_schema), - result, - seen); - }; + auto wb = write_buffer(); + merge_handle_set( + pwb ? pwb->query_for_pattern(link_schema, keychain) : wb->query_for_pattern(link_schema), + result, + seen); - auto merge_read_cache = [&]() { auto prc = protected_read_cache(); - merge_handle_set(prc ? prc->query_for_pattern(link_schema, keychain) - : read_cache()->query_for_pattern(link_schema), - result, - seen); + auto rc = read_cache(); + merge_handle_set( + prc ? prc->query_for_pattern(link_schema, keychain) : rc->query_for_pattern(link_schema), + result, + seen); }; auto merge_local_persistence = [&]() { @@ -778,8 +778,7 @@ shared_ptr RemoteAtomDBPeer::query_for_pattern( if (cache_hit) { LOG_DEBUG("[RemoteDB(" << uid_ << ")] query_for_pattern(" << link_schema.handle() << ") cache-hit"); - merge_write_buffer(); - merge_read_cache(); + merge_memory(); merge_local_persistence(); LOG_DEBUG("[RemoteDB(" << uid_ << ")] query_for_pattern(" << link_schema.handle() << ") -> " << result->size() << " handles"); @@ -798,18 +797,12 @@ shared_ptr RemoteAtomDBPeer::query_for_pattern( // Merge remote first when it carries nested metadata. InMemoryDB has no // metta/assignments — if it fills `seen` first, the remote metadata is skipped. merge_handle_set(remote_handle_set, result, seen); - - merge_write_buffer(); - // Do not query the read cache here. It has just been populated - // from remote_handle_set, which is already present in result - } else { - merge_write_buffer(); - merge_read_cache(); } + merge_memory(); merge_local_persistence(); - if (remote_handle_set && (!this->protected_atomdb_ || keychain)) { + if (!this->protected_atomdb_ || keychain) { lock_guard lock(peer_mutex_); fetched_link_templates_.insert(link_schema.handle()); }